LoopStructural 1.6.12__tar.gz → 1.6.13__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.

Potentially problematic release.


This version of LoopStructural might be problematic. Click here for more details.

Files changed (144) hide show
  1. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datatypes/_bounding_box.py +1 -1
  2. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/_base_geological_feature.py +1 -1
  3. loopstructural-1.6.13/LoopStructural/modelling/features/_lambda_geological_feature.py +161 -0
  4. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/builders/_fault_builder.py +131 -144
  5. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fault/_fault_function.py +4 -2
  6. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fault/_fault_segment.py +1 -1
  7. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/_surface.py +1 -1
  8. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/regions.py +45 -41
  9. loopstructural-1.6.13/LoopStructural/version.py +1 -0
  10. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural.egg-info/PKG-INFO +1 -1
  11. {loopstructural-1.6.12 → loopstructural-1.6.13}/PKG-INFO +1 -1
  12. loopstructural-1.6.12/LoopStructural/modelling/features/_lambda_geological_feature.py +0 -103
  13. loopstructural-1.6.12/LoopStructural/version.py +0 -1
  14. {loopstructural-1.6.12 → loopstructural-1.6.13}/LICENSE +0 -0
  15. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/__init__.py +0 -0
  16. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/__init__.py +0 -0
  17. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/_base.py +0 -0
  18. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/_example_models.py +0 -0
  19. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/claudius.csv +0 -0
  20. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/claudiusbb.txt +0 -0
  21. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/duplex.csv +0 -0
  22. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/duplexbb.txt +0 -0
  23. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/fault_trace/fault_trace.cpg +0 -0
  24. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
  25. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/fault_trace/fault_trace.prj +0 -0
  26. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
  27. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
  28. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/geological_map_data/bbox.csv +0 -0
  29. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/geological_map_data/contacts.csv +0 -0
  30. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +0 -0
  31. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/geological_map_data/fault_edges.txt +0 -0
  32. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/geological_map_data/fault_locations.csv +0 -0
  33. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +0 -0
  34. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +0 -0
  35. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +0 -0
  36. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +0 -0
  37. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/intrusion.csv +0 -0
  38. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/intrusionbb.txt +0 -0
  39. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/onefoldbb.txt +0 -0
  40. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/onefolddata.csv +0 -0
  41. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/refolded_bb.txt +0 -0
  42. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/refolded_fold.csv +0 -0
  43. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datasets/data/tabular_intrusion.csv +0 -0
  44. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datatypes/__init__.py +0 -0
  45. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datatypes/_point.py +0 -0
  46. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datatypes/_structured_grid.py +0 -0
  47. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/datatypes/_surface.py +0 -0
  48. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/export/exporters.py +0 -0
  49. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/export/file_formats.py +0 -0
  50. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/export/geoh5.py +0 -0
  51. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/export/gocad.py +0 -0
  52. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/export/omf_wrapper.py +0 -0
  53. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/__init__.py +0 -0
  54. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_api.py +0 -0
  55. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_builders.py +0 -0
  56. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_cython/__init__.py +0 -0
  57. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_discrete_fold_interpolator.py +0 -0
  58. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_discrete_interpolator.py +0 -0
  59. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_finite_difference_interpolator.py +0 -0
  60. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_geological_interpolator.py +0 -0
  61. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_interpolator_builder.py +0 -0
  62. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_interpolator_factory.py +0 -0
  63. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_operator.py +0 -0
  64. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_p1interpolator.py +0 -0
  65. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_p2interpolator.py +0 -0
  66. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/_surfe_wrapper.py +0 -0
  67. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_2d_base_unstructured.py +0 -0
  68. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_2d_p1_unstructured.py +0 -0
  69. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_2d_p2_unstructured.py +0 -0
  70. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_2d_structured_grid.py +0 -0
  71. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
  72. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_3d_base_structured.py +0 -0
  73. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_3d_p2_tetra.py +0 -0
  74. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_3d_structured_grid.py +0 -0
  75. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_3d_structured_tetra.py +0 -0
  76. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +0 -0
  77. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/__init__.py +0 -0
  78. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_aabb.py +0 -0
  79. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_base_support.py +0 -0
  80. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_face_table.py +0 -0
  81. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/interpolators/supports/_support_factory.py +0 -0
  82. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/__init__.py +0 -0
  83. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/core/__init__.py +0 -0
  84. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/core/geological_model.py +0 -0
  85. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/__init__.py +0 -0
  86. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/_analytical_feature.py +0 -0
  87. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/_cross_product_geological_feature.py +0 -0
  88. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/_geological_feature.py +0 -0
  89. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/_projected_vector_feature.py +0 -0
  90. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/_region.py +0 -0
  91. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/_structural_frame.py +0 -0
  92. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/_unconformity_feature.py +0 -0
  93. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/builders/__init__.py +0 -0
  94. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/builders/_base_builder.py +0 -0
  95. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/builders/_folded_feature_builder.py +0 -0
  96. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/builders/_geological_feature_builder.py +0 -0
  97. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/builders/_structural_frame_builder.py +0 -0
  98. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fault/__init__.py +0 -0
  99. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fault/_fault_function_feature.py +0 -0
  100. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/__init__.py +0 -0
  101. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/_fold.py +0 -0
  102. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +0 -0
  103. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/_foldframe.py +0 -0
  104. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/_svariogram.py +0 -0
  105. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/fold_function/__init__.py +0 -0
  106. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +0 -0
  107. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/fold_function/_fourier_series_fold_rotation_angle.py +0 -0
  108. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +0 -0
  109. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +0 -0
  110. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/input/__init__.py +0 -0
  111. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/input/fault_network.py +0 -0
  112. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/input/map2loop_processor.py +0 -0
  113. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/input/process_data.py +0 -0
  114. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/input/project_file.py +0 -0
  115. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/intrusions/__init__.py +0 -0
  116. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/intrusions/geom_conceptual_models.py +0 -0
  117. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/intrusions/geometric_scaling_functions.py +0 -0
  118. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/intrusions/intrusion_builder.py +0 -0
  119. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/intrusions/intrusion_feature.py +0 -0
  120. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/intrusions/intrusion_frame_builder.py +0 -0
  121. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/modelling/intrusions/intrusion_support_functions.py +0 -0
  122. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/__init__.py +0 -0
  123. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/_transformation.py +0 -0
  124. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/colours.py +0 -0
  125. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/config.py +0 -0
  126. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/dtm_creator.py +0 -0
  127. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/exceptions.py +0 -0
  128. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/features.py +0 -0
  129. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/helper.py +0 -0
  130. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/json_encoder.py +0 -0
  131. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/linalg.py +0 -0
  132. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/logging.py +0 -0
  133. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/maths.py +0 -0
  134. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/typing.py +0 -0
  135. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/utils/utils.py +0 -0
  136. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural/visualisation/__init__.py +0 -0
  137. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural.egg-info/SOURCES.txt +0 -0
  138. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural.egg-info/dependency_links.txt +0 -0
  139. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural.egg-info/requires.txt +0 -0
  140. {loopstructural-1.6.12 → loopstructural-1.6.13}/LoopStructural.egg-info/top_level.txt +0 -0
  141. {loopstructural-1.6.12 → loopstructural-1.6.13}/README.md +0 -0
  142. {loopstructural-1.6.12 → loopstructural-1.6.13}/pyproject.toml +0 -0
  143. {loopstructural-1.6.12 → loopstructural-1.6.13}/setup.cfg +0 -0
  144. {loopstructural-1.6.12 → loopstructural-1.6.13}/setup.py +0 -0
@@ -387,7 +387,7 @@ class BoundingBox:
387
387
 
388
388
  if not local:
389
389
  coordinates = [
390
- np.linspace(self.global_origin[i], self.global_maximum[i], nsteps[i])
390
+ np.linspace(self.global_origin[i]+self.origin[i], self.global_maximum[i], nsteps[i])
391
391
  for i in range(self.dimensions)
392
392
  ]
393
393
  coordinate_grid = np.meshgrid(*coordinates, indexing="ij")
@@ -229,7 +229,7 @@ class BaseFeature(metaclass=ABCMeta):
229
229
  """
230
230
  Evaluate the gradient of the feature at a given position.
231
231
  """
232
-
232
+
233
233
  raise NotImplementedError
234
234
 
235
235
  def min(self):
@@ -0,0 +1,161 @@
1
+ """
2
+ Geological features
3
+ """
4
+ from LoopStructural.utils.maths import regular_tetraherdron_for_points, gradient_from_tetrahedron
5
+ from ...modelling.features import BaseFeature
6
+ from ...utils import getLogger
7
+ from ...modelling.features import FeatureType
8
+ import numpy as np
9
+ from typing import Callable, Optional
10
+ from ...utils import LoopValueError
11
+
12
+ logger = getLogger(__name__)
13
+
14
+
15
+ class LambdaGeologicalFeature(BaseFeature):
16
+ def __init__(
17
+ self,
18
+ function: Optional[Callable[[np.ndarray], np.ndarray]] = None,
19
+ name: str = "unnamed_lambda",
20
+ gradient_function: Optional[Callable[[np.ndarray], np.ndarray]] = None,
21
+ model=None,
22
+ regions: Optional[list] = None,
23
+ faults: Optional[list] = None,
24
+ builder=None,
25
+ ):
26
+ """A lambda geological feature is a wrapper for a geological
27
+ feature that has a function at the base. This can be then used
28
+ in place of a geological feature.
29
+
30
+ Parameters
31
+ ----------
32
+ function : _type_, optional
33
+ _description_, by default None
34
+ name : str, optional
35
+ _description_, by default "unnamed_lambda"
36
+ gradient_function : _type_, optional
37
+ _description_, by default None
38
+ model : _type_, optional
39
+ _description_, by default None
40
+ regions : list, optional
41
+ _description_, by default []
42
+ faults : list, optional
43
+ _description_, by default []
44
+ builder : _type_, optional
45
+ _description_, by default None
46
+ """
47
+ BaseFeature.__init__(self, name, model, faults if faults is not None else [], regions if regions is not None else [], builder)
48
+ self.type = FeatureType.LAMBDA
49
+ self.function = function
50
+ self.gradient_function = gradient_function
51
+ self.regions = regions if regions is not None else []
52
+
53
+ def evaluate_value(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray:
54
+ """_summary_
55
+
56
+ Parameters
57
+ ----------
58
+ xyz : np.ndarray
59
+ _description_
60
+
61
+ Returns
62
+ -------
63
+ np.ndarray
64
+ _description_
65
+ """
66
+ v = np.zeros((pos.shape[0]))
67
+ v[:] = np.nan
68
+
69
+ mask = self._calculate_mask(pos, ignore_regions=ignore_regions)
70
+ pos = self._apply_faults(pos)
71
+ if self.function is not None:
72
+
73
+ v[mask] = self.function(pos[mask,:])
74
+ return v
75
+
76
+ def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False,element_scale_parameter=None) -> np.ndarray:
77
+ """_summary_
78
+
79
+ Parameters
80
+ ----------
81
+ xyz : np.ndarray
82
+ _description_
83
+
84
+ Returns
85
+ -------
86
+ np.ndarray
87
+ _description_
88
+ """
89
+ if pos.shape[1] != 3:
90
+ raise LoopValueError("Need Nx3 array of xyz points to evaluate gradient")
91
+ logger.info(f'Calculating gradient for {self.name}')
92
+ if element_scale_parameter is None:
93
+ if self.model is not None:
94
+ element_scale_parameter = np.min(self.model.bounding_box.step_vector) / 10
95
+ else:
96
+ element_scale_parameter = 1
97
+ else:
98
+ try:
99
+ element_scale_parameter = float(element_scale_parameter)
100
+ except ValueError:
101
+ logger.error("element_scale_parameter must be a float")
102
+ element_scale_parameter = 1
103
+ v = np.zeros((pos.shape[0], 3))
104
+ v = np.zeros(pos.shape)
105
+ v[:] = np.nan
106
+ mask = self._calculate_mask(pos, ignore_regions=ignore_regions)
107
+ # evaluate the faults on the nodes of the faulted feature support
108
+ # then evaluate the gradient at these points
109
+ if len(self.faults) > 0:
110
+ # generate a regular tetrahedron for each point
111
+ # we will then move these points by the fault and then recalculate the gradient.
112
+ # this should work...
113
+ resolved = False
114
+ tetrahedron = regular_tetraherdron_for_points(pos, element_scale_parameter)
115
+
116
+ while resolved:
117
+ for f in self.faults:
118
+ v = (
119
+ f[0]
120
+ .evaluate_value(tetrahedron.reshape(-1, 3), fillnan='nearest')
121
+ .reshape(tetrahedron.shape[0], 4)
122
+ )
123
+ flag = np.logical_or(np.all(v > 0, axis=1), np.all(v < 0, axis=1))
124
+ if np.any(~flag):
125
+ logger.warning(
126
+ f"Points are too close to fault {f[0].name}. Refining the tetrahedron"
127
+ )
128
+ element_scale_parameter *= 0.5
129
+ tetrahedron = regular_tetraherdron_for_points(pos, element_scale_parameter)
130
+
131
+ resolved = True
132
+
133
+ tetrahedron_faulted = self._apply_faults(np.array(tetrahedron.reshape(-1, 3))).reshape(
134
+ tetrahedron.shape
135
+ )
136
+
137
+ values = self.function(tetrahedron_faulted.reshape(-1, 3)).reshape(
138
+ (-1, 4)
139
+ )
140
+ v[mask, :] = gradient_from_tetrahedron(tetrahedron[mask, :, :], values[mask])
141
+
142
+ return v
143
+ if self.gradient_function is None:
144
+ v[:, :] = np.nan
145
+ else:
146
+ v[:, :] = self.gradient_function(pos)
147
+ return v
148
+
149
+ def get_data(self, value_map: Optional[dict] = None):
150
+ return
151
+
152
+ def copy(self, name: Optional[str] = None):
153
+ return LambdaGeologicalFeature(
154
+ self.function,
155
+ name if name is not None else f'{self.name}_copy',
156
+ self.gradient_function,
157
+ self.model,
158
+ self.regions,
159
+ self.faults,
160
+ self.builder,
161
+ )
@@ -3,7 +3,6 @@ from typing import Union
3
3
  from LoopStructural.utils.maths import rotation
4
4
  from ._structural_frame_builder import StructuralFrameBuilder
5
5
  from .. import AnalyticalGeologicalFeature
6
- from LoopStructural.utils import get_vectors
7
6
  import numpy as np
8
7
  import pandas as pd
9
8
  from ....utils import getLogger
@@ -52,8 +51,11 @@ class FaultBuilder(StructuralFrameBuilder):
52
51
  self.frame.model = model
53
52
  self.origin = np.array([np.nan, np.nan, np.nan])
54
53
  self.maximum = np.array([np.nan, np.nan, np.nan]) # self.model.bounding_box[1, :]
54
+
55
+ if bounding_box is None:
56
+ raise ValueError("BoundingBox cannot be None")
57
+
55
58
  # define a maximum area to mesh adding buffer to model
56
- # buffer = .2
57
59
  self.minimum_origin = bounding_box.with_buffer(fault_bounding_box_buffer).origin
58
60
  self.maximum_maximum = bounding_box.with_buffer(fault_bounding_box_buffer).maximum
59
61
 
@@ -66,8 +68,23 @@ class FaultBuilder(StructuralFrameBuilder):
66
68
  self.fault_centre = None
67
69
 
68
70
  def update_geometry(self, points):
71
+ """
72
+ Update the geometry of the fault by adjusting the origin and maximum bounds
73
+ based on the provided points.
74
+
75
+ Parameters
76
+ ----------
77
+ points : numpy.ndarray
78
+ Array of points used to update the fault geometry.
79
+ """
80
+ if self.origin is None or self.maximum is None:
81
+ raise ValueError("Origin and maximum must be initialized before updating geometry.")
82
+
69
83
  self.origin = np.nanmin(np.array([np.min(points, axis=0), self.origin]), axis=0)
70
84
  self.maximum = np.nanmax(np.array([np.max(points, axis=0), self.maximum]), axis=0)
85
+ # add a small buffer 10% of current length to the origin and maximum
86
+ self.origin = self.origin - 0.1 * (self.maximum - self.origin)
87
+ self.maximum = self.maximum + 0.1 * (self.maximum - self.origin)
71
88
  self.origin[self.origin < self.minimum_origin] = self.minimum_origin[
72
89
  self.origin < self.minimum_origin
73
90
  ]
@@ -93,144 +110,109 @@ class FaultBuilder(StructuralFrameBuilder):
93
110
  fault_dip_anisotropy=1.0,
94
111
  fault_pitch=None,
95
112
  ):
96
- """Generate the required data for building a fault frame for a fault with the
97
- specified parameters
113
+ """
114
+ Generate the required data for building a fault frame with the specified parameters.
98
115
 
99
116
  Parameters
100
117
  ----------
101
- data : DataFrame,
102
- model data
103
- fault_center : np.array(3)
104
- x,y,z coordinates of the fault center
105
- normal_vector : np.array(3)
106
- x,y,z components of normal vector to fault, single observation usually
107
- average direction
108
- slip_vector : np.array(3)
109
- x,y,z components of slip vector for the fault, single observation usually
110
- average direction
111
- minor_axis : double
112
- distance away from fault for the fault volume
113
- major_axis : double
114
- fault extent
115
- intermediate_axis : double
116
- fault volume radius in the slip direction
118
+ fault_frame_data : pandas.DataFrame
119
+ DataFrame containing fault frame data.
120
+ fault_center : array-like, optional
121
+ Coordinates of the fault center.
122
+ fault_normal_vector : array-like, optional
123
+ Normal vector of the fault.
124
+ fault_slip_vector : array-like, optional
125
+ Slip vector of the fault.
126
+ minor_axis : float, optional
127
+ Minor axis length of the fault.
128
+ major_axis : float, optional
129
+ Major axis length of the fault.
130
+ intermediate_axis : float, optional
131
+ Intermediate axis length of the fault.
132
+ w : float, default=1.0
133
+ Weighting factor for the fault data.
134
+ points : bool, default=False
135
+ Whether to include points in the fault data.
136
+ force_mesh_geometry : bool, default=False
137
+ Whether to force the use of mesh geometry.
138
+ fault_buffer : float, default=0.2
139
+ Buffer size around the fault.
140
+ fault_trace_anisotropy : float, default=1.0
141
+ Anisotropy factor for the fault trace.
142
+ fault_dip : float, default=90
143
+ Dip angle of the fault in degrees.
144
+ fault_dip_anisotropy : float, default=1.0
145
+ Anisotropy factor for the fault dip.
146
+ fault_pitch : float, optional
147
+ Pitch angle of the fault.
117
148
  """
118
- trace_mask = np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["val"] == 0)
119
- logger.info(f"There are {np.sum(trace_mask)} points on the fault trace")
120
- if np.sum(trace_mask) == 0 and fault_center is None:
121
- logger.error("You cannot model a fault without defining the location of the fault")
122
- raise ValueError("There are no points on the fault trace")
123
- # find the middle point on the fault trace if center is not provided
124
- if fault_center is None:
125
- trace_mask = np.logical_and(
126
- fault_frame_data["coord"] == 0, fault_frame_data["val"] == 0
127
- )
128
- fault_center = fault_frame_data.loc[trace_mask, ["X", "Y", "Z"]].mean(axis=0).to_numpy()
129
- dist = np.linalg.norm(
130
- fault_center - fault_frame_data.loc[trace_mask, ["X", "Y", "Z"]].to_numpy(), axis=1
131
- )
132
- # make the nan points greater than the max dist 10 is arbitrary and doesn't matter
133
- dist[np.isnan(dist)] = np.nanmax(dist) + 10
134
- fault_center = fault_frame_data.loc[trace_mask, ["X", "Y", "Z"]].to_numpy()[
135
- np.argmin(dist), :
136
- ]
137
- # get all of the gradient data associated with the fault trace
149
+ fault_trace = fault_frame_data.loc[
150
+ np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["val"] == 0),
151
+ ["X", "Y"],
152
+ ].to_numpy()
138
153
  if fault_normal_vector is None:
139
- gradient_mask = np.logical_and(
140
- fault_frame_data["coord"] == 0, ~np.isnan(fault_frame_data["gz"])
141
- )
142
- vector_data = fault_frame_data.loc[gradient_mask, ["gx", "gy", "gz"]].to_numpy()
143
- normal_mask = np.logical_and(
144
- fault_frame_data["coord"] == 0, ~np.isnan(fault_frame_data["nz"])
145
- )
146
- vector_data = np.vstack(
147
- [
148
- vector_data,
149
- fault_frame_data.loc[normal_mask, ["nx", "ny", "nz"]].to_numpy(),
150
- ]
151
- )
154
+ if fault_frame_data.loc[
155
+ np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["nx"].notna())].shape[0]>0:
156
+ fault_normal_vector = fault_frame_data.loc[
157
+ np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["nx"].notna()),
158
+ ["nx", "ny", "nz"],
159
+ ].to_numpy().mean(axis=0)
152
160
 
153
- if len(vector_data) == 0:
154
- logger.warning(
155
- f"No orientation data for fault\n\
156
- Defaulting to a dip of {fault_dip}vertical fault"
157
- )
158
- # if the line is long enough, estimate the normal vector
159
- # by finding the centre point of the line and calculating the tangnent
160
- # of the two points
161
- if fault_frame_data.loc[trace_mask, :].shape[0] > 3:
162
-
163
- pts = fault_frame_data.loc[trace_mask, ["X", "Y", "Z"]].to_numpy()
164
- dist = np.abs(np.linalg.norm(fault_center - pts, axis=1))
165
- # any nans just make them max distance + a bit
166
- dist[np.isnan(dist)] = np.nanmax(dist) + 10
167
- # idx = np.argsort(dist)
168
- # direction_vector = pts[idx[-1]] - pts[idx[-2]]
169
- # coefficients = np.polyfit(
170
- # fault_frame_data.loc[trace_mask, "X"],
171
- # fault_frame_data.loc[trace_mask, "Y"],
172
- # 1,
173
- # )
174
- # slope, intercept = coefficients
175
- slope, intercept = np.polyfit(
176
- pts[dist < 0.25 * np.nanmax(dist), 0],
177
- pts[dist < 0.25 * np.nanmax(dist), 1],
178
- 1,
179
- )
180
-
181
- # # Create a direction vector using the slope
182
- direction_vector = np.array([1, slope, 0])
183
- direction_vector /= np.linalg.norm(direction_vector)
184
- rotation_matrix = rotation(direction_vector[None, :], [90 - fault_dip])
185
- vector_data = np.array(
186
- [
187
- [
188
- direction_vector[1],
189
- -direction_vector[0],
190
- 0,
191
- ]
192
- ]
193
- )
194
- vector_data /= np.linalg.norm(vector_data, axis=1)
195
- vector_data = np.einsum("ijk,ik->ij", rotation_matrix, vector_data)
196
-
197
- vector_data /= np.linalg.norm(vector_data, axis=1)
198
- fault_normal_vector = np.mean(vector_data, axis=0)
161
+ else:
199
162
 
200
- logger.info(f"Fault normal vector: {fault_normal_vector}")
163
+ # Calculate fault strike using eigenvectors
164
+ pts = fault_trace - fault_trace.mean(axis=0)
165
+ # Calculate covariance matrix
166
+ cov_matrix = pts.T @ pts
167
+ # Get eigenvectors and eigenvalues
168
+ eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
169
+ # Use eigenvector with largest eigenvalue as strike direction
170
+ strike_vector = eigenvectors[:, np.argmax(eigenvalues)]
171
+ strike_vector = np.append(strike_vector, 0) # Add z component
172
+ strike_vector /= np.linalg.norm(strike_vector)
173
+
174
+ fault_normal_vector = np.cross(strike_vector, [0, 0, 1])
175
+ # Rotate the fault normal vector according to the fault dip
176
+ rotation_matrix = rotation(strike_vector[None, :], np.array([90 - fault_dip]))
177
+ fault_normal_vector = np.einsum("ijk,ik->ij", rotation_matrix, fault_normal_vector[None, :])[0]
178
+
179
+ if not isinstance(fault_normal_vector, np.ndarray):
180
+ fault_normal_vector = np.array(fault_normal_vector)
181
+
182
+ if fault_pitch is not None:
183
+ rotation_matrix = rotation(fault_normal_vector[None, :], np.array([fault_pitch]))
184
+ fault_slip_vector = np.einsum("ijk,ik->ij", rotation_matrix, fault_normal_vector[None, :])[0]
201
185
 
202
- # estimate the fault slip vector
203
186
  if fault_slip_vector is None:
204
- slip_mask = np.logical_and(
205
- fault_frame_data["coord"] == 1, ~np.isnan(fault_frame_data["gz"])
206
- )
207
- fault_slip_data = fault_frame_data.loc[slip_mask, ["gx", "gy", "gz"]]
208
-
209
- if len(fault_slip_data) == 0:
210
- logger.warning(
211
- "There is no slip vector data for the fault, using vertical slip vector\n\
212
- projected onto fault surface estimating from fault normal"
213
- )
214
- strike_vector, dip_vector = get_vectors(fault_normal_vector[None, :])
215
- fault_slip_vector = dip_vector[:, 0]
216
- if fault_pitch is not None:
217
- print('using pitch')
218
- rotm = rotation(fault_normal_vector[None,:],[fault_pitch])
219
- print(rotm.shape,fault_slip_vector.shape)
220
- fault_slip_vector = np.einsum("ijk,k->ij", rotm, fault_slip_vector)[0,:]
221
- logger.info(f"Estimated fault slip vector: {fault_slip_vector}")
222
- else:
223
- fault_slip_vector = fault_slip_data.mean(axis=0).to_numpy()
187
+ if fault_frame_data.loc[
188
+ np.logical_and(fault_frame_data["coord"] == 1, fault_frame_data["nx"].notna())].shape[0]>0:
189
+ fault_slip_vector = fault_frame_data.loc[
190
+ np.logical_and(fault_frame_data["coord"] == 1, fault_frame_data["nx"].notna()),
191
+ ["nx", "ny", "nz"],
192
+ ].to_numpy().mean(axis=0)
224
193
 
225
- self.fault_normal_vector = fault_normal_vector
226
- self.fault_slip_vector = fault_slip_vector
227
-
228
- self.fault_centre = fault_center
229
- if major_axis is None:
194
+ else:
195
+ fault_slip_vector = np.cross(fault_normal_vector, [1., 0., 0.])
196
+ if np.linalg.norm(fault_slip_vector) == 0:
197
+ fault_slip_vector = np.cross(fault_normal_vector, [0., 1., 0.])
198
+ fault_slip_vector /= np.linalg.norm(fault_slip_vector)
199
+ if fault_center is None:
230
200
  fault_trace = fault_frame_data.loc[
231
201
  np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["val"] == 0),
232
202
  ["X", "Y"],
233
203
  ].to_numpy()
204
+ fault_center = fault_trace.mean(axis=0)
205
+ fault_center = np.array([fault_center[0], fault_center[1], 0.0])
206
+ if not isinstance(fault_center, np.ndarray):
207
+ fault_center = np.array(fault_center)
208
+ if fault_center.shape[0] != 3:
209
+ raise ValueError("fault_center must be a 3 element array")
210
+ self.fault_normal_vector = fault_normal_vector / np.linalg.norm(fault_normal_vector)
211
+ self.fault_slip_vector = fault_slip_vector / np.linalg.norm(fault_slip_vector)
212
+
213
+ self.fault_centre = fault_center
214
+ if major_axis is None:
215
+
234
216
  distance = np.linalg.norm(fault_trace[:, None, :] - fault_trace[None, :, :], axis=2)
235
217
  if len(distance) == 0 or np.sum(distance) == 0:
236
218
  logger.warning("There is no fault trace for {}".format(self.name))
@@ -382,6 +364,7 @@ class FaultBuilder(StructuralFrameBuilder):
382
364
  0,
383
365
  w,
384
366
  ]
367
+
385
368
  if major_axis is not None:
386
369
  fault_tips[0, :] = fault_center[:3] + strike_vector * 0.5 * major_axis
387
370
  fault_tips[1, :] = fault_center[:3] - strike_vector * 0.5 * major_axis
@@ -503,16 +486,14 @@ class FaultBuilder(StructuralFrameBuilder):
503
486
  self.origin - length * buffer, self.maximum + length * buffer, rotation
504
487
  )
505
488
 
506
- def add_splay(self, splay, splayregion=None):
507
- if splayregion is None:
489
+ def add_splay(self, splay, splay_region=None):
490
+ if splay_region is None:
508
491
 
509
- def splayregion(xyz):
492
+ def default_splay_region(xyz):
510
493
  pts = (
511
- self.builders[0].data[["X", "Y", "Z", "val"]].to_numpy()
494
+ self.builders[0].data["X", "Y", "Z", "val"].to_numpy()
512
495
  ) # get_value_constraints()
513
496
  pts = pts[pts[:, 3] == 0, :]
514
- # check whether the fault is on the hanging wall or footwall of splay fault
515
-
516
497
  ext_field = splay[2].evaluate_value(pts[:, :3])
517
498
  surf_field = splay[0].evaluate_value(pts[:, :3])
518
499
  intersection_value = ext_field[np.nanargmin(np.abs(surf_field))]
@@ -531,19 +512,24 @@ class FaultBuilder(StructuralFrameBuilder):
531
512
  )
532
513
  return mask
533
514
 
515
+ splay_region = default_splay_region
516
+
534
517
  scalefactor = splay.fault_major_axis / self.fault_major_axis
535
- self.builders[0].add_equality_constraints(splay, splayregion, scalefactor)
536
- return splayregion
518
+ self.builders[0].add_equality_constraints(splay, splay_region, scalefactor)
519
+ return splay_region
537
520
 
538
521
  def add_fault_trace_anisotropy(self, w: float = 1.0):
539
- """_summary_
522
+ """
523
+ Add fault trace anisotropy to the model.
540
524
 
541
525
  Parameters
542
526
  ----------
543
527
  w : float, optional
544
- _description_, by default 1.0
528
+ Weighting factor for the anisotropy, by default 1.0
545
529
  """
546
530
  if w > 0:
531
+ if self.fault_normal_vector is None:
532
+ raise ValueError("fault_normal_vector must be initialized before adding anisotropy.")
547
533
 
548
534
  plane = np.array([0, 0, 1])
549
535
  strike_vector = (
@@ -553,24 +539,25 @@ class FaultBuilder(StructuralFrameBuilder):
553
539
  strike_vector = np.array([strike_vector[1], -strike_vector[0], 0])
554
540
 
555
541
  anisotropy_feature = AnalyticalGeologicalFeature(
556
- vector=strike_vector, origin=[0, 0, 0], name="fault_trace_anisotropy"
542
+ vector=strike_vector, origin=np.array([0, 0, 0]), name="fault_trace_anisotropy"
557
543
  )
558
- # print('adding fault trace anisotropy')
559
544
  self.builders[0].add_orthogonal_feature(
560
545
  anisotropy_feature, w=w, region=None, step=1, B=0
561
546
  )
562
547
 
563
548
  def add_fault_dip_anisotropy(self, w: float = 1.0):
564
- """_summary_
549
+ """
550
+ Add fault dip anisotropy to the model.
565
551
 
566
552
  Parameters
567
553
  ----------
568
- dip : np.ndarray
569
- _description_
570
554
  w : float, optional
571
- _description_, by default 1.0
555
+ Weighting factor for the anisotropy, by default 1.0
572
556
  """
573
557
  if w > 0:
558
+ if self.fault_normal_vector is None:
559
+ raise ValueError("fault_normal_vector must be initialized before adding anisotropy.")
560
+
574
561
  plane = np.array([0, 0, 1])
575
562
  strike_vector = (
576
563
  self.fault_normal_vector - np.dot(self.fault_normal_vector, plane) * plane
@@ -579,11 +566,11 @@ class FaultBuilder(StructuralFrameBuilder):
579
566
  strike_vector = np.array([strike_vector[1], -strike_vector[0], 0])
580
567
 
581
568
  dip_vector = np.cross(strike_vector, self.fault_normal_vector)
569
+ dip_vector /= np.linalg.norm(dip_vector)
582
570
 
583
571
  anisotropy_feature = AnalyticalGeologicalFeature(
584
- vector=dip_vector, origin=[0, 0, 0], name="fault_dip_anisotropy"
572
+ vector=dip_vector, origin=np.array([0, 0, 0]), name="fault_dip_anisotropy"
585
573
  )
586
- # print(f'adding fault dip anisotropy {anisotropy_feature.name}')
587
574
  self.builders[0].add_orthogonal_feature(
588
575
  anisotropy_feature, w=w, region=None, step=1, B=0
589
576
  )
@@ -10,8 +10,10 @@ logger = getLogger(__name__)
10
10
 
11
11
 
12
12
  def smooth_peak(x):
13
- return 0.25 * x**6 + 0.5 * x**4 - 1.75 * x**2 + 1
14
-
13
+ v = np.zeros(x.shape)
14
+ mask = np.logical_and(x >= -1, x <= 1)
15
+ v[mask] = 0.25 * x[mask] ** 2 + 0.5 * x[mask] ** 4 - 1.75 * x[mask] ** 2 + 1
16
+ return v
15
17
 
16
18
  class FaultProfileFunction(metaclass=ABCMeta):
17
19
  def __init__(self):
@@ -467,7 +467,7 @@ class FaultSegment(StructuralFrame):
467
467
  )
468
468
  self.abut[abutting_fault_feature.name] = abutting_region
469
469
  self.__getitem__(0).add_region(abutting_region)
470
-
470
+ return abutting_region
471
471
  def save(self, filename, scalar_field=True, slip_vector=True, surface=True):
472
472
  """
473
473
  Save the fault to a file
@@ -145,7 +145,7 @@ class LoopIsosurfacer:
145
145
  values = np.zeros(verts.shape[0]) + isovalue
146
146
  # need to add both global and local origin. If the bb is a buffer the local
147
147
  # origin may not be 0
148
- verts += self.bounding_box.global_origin
148
+ verts += self.bounding_box.global_origin+self.bounding_box.origin
149
149
  surfaces.append(
150
150
  Surface(
151
151
  vertices=verts,