LoopStructural 1.0.4__zip

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 (119) hide show
  1. Miniconda/envs/loop/Lib/site-packages/LoopStructural/__init__.py +33 -0
  2. Miniconda/envs/loop/Lib/site-packages/LoopStructural/__pycache__/__init__.cpython-37.pyc +0 -0
  3. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/__init__.py +12 -0
  4. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/__pycache__/__init__.cpython-37.pyc +0 -0
  5. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/__pycache__/_base.cpython-37.pyc +0 -0
  6. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/_base.py +65 -0
  7. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/claudius.csv +21049 -0
  8. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/claudiusbb.txt +2 -0
  9. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/duplex.csv +126 -0
  10. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/duplexbb.txt +2 -0
  11. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/intrusion.csv +1017 -0
  12. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/intrusionbb.txt +2 -0
  13. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/onefoldbb.txt +2 -0
  14. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/onefolddata.csv +2226 -0
  15. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/refolded_bb.txt +2 -0
  16. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/refolded_fold.csv +2126 -0
  17. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__init__.py +31 -0
  18. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/__init__.cpython-37.pyc +0 -0
  19. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/discrete_fold_interpolator.cpython-37.pyc +0 -0
  20. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/discrete_interpolator.cpython-37.pyc +0 -0
  21. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/finite_difference_interpolator.cpython-37.pyc +0 -0
  22. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/geological_interpolator.cpython-37.pyc +0 -0
  23. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/operator.cpython-37.pyc +0 -0
  24. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/piecewiselinear_interpolator.cpython-37.pyc +0 -0
  25. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/structured_grid.cpython-37.pyc +0 -0
  26. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/structured_tetra.cpython-37.pyc +0 -0
  27. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/surfe_wrapper.cpython-37.pyc +0 -0
  28. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/__init__.py +0 -0
  29. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/__pycache__/__init__.cpython-37.pyc +0 -0
  30. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/dsi_helper.c +27782 -0
  31. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/dsi_helper.cp37-win_amd64.pyd +0 -0
  32. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/discrete_fold_interpolator.py +171 -0
  33. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/discrete_interpolator.py +551 -0
  34. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/finite_difference_interpolator.py +342 -0
  35. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/geological_interpolator.py +190 -0
  36. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/operator.py +60 -0
  37. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/piecewiselinear_interpolator.py +348 -0
  38. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/structured_grid.py +466 -0
  39. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/structured_tetra.py +638 -0
  40. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/surfe_wrapper.py +117 -0
  41. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/__init__.py +46 -0
  42. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/__pycache__/__init__.cpython-37.pyc +0 -0
  43. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__init__.py +0 -0
  44. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__pycache__/__init__.cpython-37.pyc +0 -0
  45. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__pycache__/geological_model.cpython-37.pyc +0 -0
  46. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/geological_model.py +1351 -0
  47. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__init__.py +3 -0
  48. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/__init__.cpython-37.pyc +0 -0
  49. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_function.cpython-37.pyc +0 -0
  50. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_function_feature.cpython-37.pyc +0 -0
  51. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_segment.cpython-37.pyc +0 -0
  52. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_function.py +187 -0
  53. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_function_feature.py +75 -0
  54. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_segment.py +270 -0
  55. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__init__.py +7 -0
  56. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/__init__.cpython-37.pyc +0 -0
  57. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/cross_product_geological_feature.cpython-37.pyc +0 -0
  58. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/geological_feature.cpython-37.pyc +0 -0
  59. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/geological_feature_builder.cpython-37.pyc +0 -0
  60. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/region_feature.cpython-37.pyc +0 -0
  61. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/structural_frame.cpython-37.pyc +0 -0
  62. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/structural_frame_builder.cpython-37.pyc +0 -0
  63. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/unconformity_feature.cpython-37.pyc +0 -0
  64. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/cross_product_geological_feature.py +77 -0
  65. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/geological_feature.py +286 -0
  66. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/geological_feature_builder.py +329 -0
  67. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/region_feature.py +34 -0
  68. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/structural_frame.py +116 -0
  69. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/structural_frame_builder.py +179 -0
  70. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/unconformity_feature.py +69 -0
  71. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__init__.py +8 -0
  72. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/__init__.cpython-37.pyc +0 -0
  73. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/fold.cpython-37.pyc +0 -0
  74. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/fold_rotation_angle.cpython-37.pyc +0 -0
  75. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/fold_rotation_angle_feature.cpython-37.pyc +0 -0
  76. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/foldframe.cpython-37.pyc +0 -0
  77. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/svariogram.cpython-37.pyc +0 -0
  78. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/fold.py +135 -0
  79. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/fold_rotation_angle.py +132 -0
  80. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/fold_rotation_angle_feature.py +57 -0
  81. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/foldframe.py +192 -0
  82. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/svariogram.py +179 -0
  83. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__init__.py +14 -0
  84. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/__init__.cpython-37.pyc +0 -0
  85. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/exceptions.cpython-37.pyc +0 -0
  86. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/helper.cpython-37.pyc +0 -0
  87. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/map2loop.cpython-37.pyc +0 -0
  88. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/utils.cpython-37.pyc +0 -0
  89. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/exceptions.py +9 -0
  90. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/helper.py +378 -0
  91. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/map2loop.py +314 -0
  92. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/utils.py +120 -0
  93. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__init__.py +19 -0
  94. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/__init__.cpython-37.pyc +0 -0
  95. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/map_viewer.cpython-37.pyc +0 -0
  96. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/model_plotter.cpython-37.pyc +0 -0
  97. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/model_visualisation.cpython-37.pyc +0 -0
  98. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/rotation_angle_plotter.cpython-37.pyc +0 -0
  99. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/sphinx_scraper.cpython-37.pyc +0 -0
  100. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/map_viewer.py +307 -0
  101. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/model_plotter.py +16 -0
  102. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/model_visualisation.py +1012 -0
  103. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/rotation_angle_plotter.py +82 -0
  104. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/sphinx_scraper.py +34 -0
  105. Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/PKG-INFO +10 -0
  106. Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/SOURCES.txt +60 -0
  107. Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/dependency_links.txt +1 -0
  108. Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/requires.txt +8 -0
  109. Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/top_level.txt +2 -0
  110. Miniconda/envs/loop/Lib/site-packages/tests/__init__.py +0 -0
  111. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/__init__.cpython-37.pyc +0 -0
  112. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_faults.cpython-37.pyc +0 -0
  113. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_fold.cpython-37.pyc +0 -0
  114. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_interpolator.cpython-37.pyc +0 -0
  115. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_refolded.cpython-37.pyc +0 -0
  116. Miniconda/envs/loop/Lib/site-packages/tests/test_faults.py +17 -0
  117. Miniconda/envs/loop/Lib/site-packages/tests/test_fold.py +57 -0
  118. Miniconda/envs/loop/Lib/site-packages/tests/test_interpolator.py +88 -0
  119. Miniconda/envs/loop/Lib/site-packages/tests/test_refolded.py +22 -0
@@ -0,0 +1,1351 @@
1
+ """
2
+ Main entry point for creating a geological model
3
+ """
4
+ import logging
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ from LoopStructural.datasets import normal_vector_headers
10
+ from LoopStructural.interpolators.discrete_fold_interpolator import \
11
+ DiscreteFoldInterpolator as DFI
12
+ from LoopStructural.interpolators.finite_difference_interpolator import \
13
+ FiniteDifferenceInterpolator as FDI
14
+ from LoopStructural.interpolators.piecewiselinear_interpolator import \
15
+ PiecewiseLinearInterpolator as PLI
16
+
17
+ try:
18
+ from LoopStructural.interpolators.surfe_wrapper import \
19
+ SurfeRBFInterpolator as Surfe
20
+
21
+ surfe = True
22
+
23
+ except ImportError:
24
+ surfe = False
25
+
26
+ from LoopStructural.utils.helper import all_heading, gradient_vec_names, \
27
+ strike_dip_vector
28
+ from LoopStructural.modelling.fault.fault_segment import FaultSegment
29
+ from LoopStructural.modelling.features import \
30
+ GeologicalFeatureInterpolator
31
+ from LoopStructural.modelling.features import RegionFeature
32
+ from LoopStructural.modelling.features import \
33
+ StructuralFrameBuilder
34
+ from LoopStructural.modelling.features import UnconformityFeature
35
+ from LoopStructural.modelling.fold.fold import FoldEvent
36
+ from LoopStructural.modelling.fold import FoldRotationAngle
37
+ from LoopStructural.modelling.fold.foldframe import FoldFrame
38
+ from LoopStructural.interpolators.structured_grid import StructuredGrid
39
+ from LoopStructural.interpolators.structured_tetra import TetMesh
40
+ from LoopStructural.utils.exceptions import LoopBaseException
41
+
42
+ logger = logging.getLogger(__name__)
43
+ if not surfe:
44
+ logger.warning("Cannot import Surfe")
45
+
46
+
47
+ def _calculate_average_intersection(series_builder, fold_frame, fold,
48
+ **kwargs):
49
+ """
50
+
51
+ Parameters
52
+ ----------
53
+ series_builder
54
+ fold_frame
55
+ fold
56
+
57
+ Returns
58
+ -------
59
+
60
+ """
61
+ l2 = fold_frame.calculate_intersection_lineation(
62
+ series_builder)
63
+ fold.fold_axis = np.mean(l2, axis=0)
64
+
65
+
66
+ class GeologicalModel:
67
+ """
68
+ A geological model is the recipe for building a 3D model and can include
69
+ the rescaling of the model between 0 and 1.
70
+
71
+ Attributes
72
+ ----------
73
+ features : list
74
+ Contains all features youngest to oldest
75
+ feature_name_index : dict
76
+ maps feature name to the list index of the features
77
+ data : pandas dataframe
78
+ the dataframe used for building the geological model
79
+ nsteps : tuple/np.array(3,dtype=int)
80
+ the number of steps x,y,z to evaluate the model
81
+ origin : tuple/np.array(3,dtype=doubles)
82
+ the origin of the model box
83
+ parameters : dict
84
+ a dictionary tracking the parameters used to build the model
85
+ scale_factor : double
86
+ the scale factor used to rescale the model
87
+
88
+
89
+ """
90
+ def __init__(self, origin, maximum, rescale=True, nsteps=(40, 40, 40),
91
+ reuse_supports=False):
92
+ """
93
+ Parameters
94
+ ----------
95
+ origin : numpy array
96
+ specifying the origin of the model
97
+ maximum : numpy array
98
+ specifying the maximum extent of the model
99
+ rescale : bool
100
+ whether to rescale the model to between 0/1
101
+
102
+ Examples
103
+ --------
104
+ Demo data
105
+
106
+ >>> from LoopStructural.datasets import load_claudius
107
+ >>> from LoopStructural import GeologicalModel
108
+
109
+ >>> data, bb = load_claudius()
110
+
111
+ >>> model = GeologicalModel(bb[:,0],bb[:,1]
112
+ >>> model.set_model_data(data)
113
+ >>> model.create_and_add_foliation('strati')
114
+
115
+ >>> y = np.linspace(model.bounding_box[0, 1], model.bounding_box[1, 1],
116
+ nsteps[1])
117
+ >>> z = np.linspace(model.bounding_box[1, 2], model.bounding_box[0, 2],
118
+ nsteps[2])
119
+ >>> xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
120
+ >>> xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
121
+ >>> model.evaluate_feature_value('strati',xyz,scale=False)
122
+
123
+
124
+ """
125
+ self.features = []
126
+ self.feature_name_index = {}
127
+ self.data = None
128
+ self.nsteps = nsteps
129
+
130
+ # we want to rescale the model area so that the maximum length is
131
+ # 1
132
+ self.origin = np.array(origin).astype(float)
133
+
134
+ self.maximum = np.array(maximum).astype(float)
135
+ lengths = self.maximum - self.origin
136
+ self.scale_factor = 1.
137
+ self.bounding_box = np.zeros((2, 3))
138
+ self.bounding_box[1, :] = self.maximum - self.origin
139
+ self.bounding_box[1, :] = self.maximum - self.origin
140
+ if rescale:
141
+ self.scale_factor = np.max(lengths)
142
+
143
+ self.bounding_box /= self.scale_factor
144
+ self.support = {}
145
+ self.reuse_supports = reuse_supports
146
+ self.stratigraphic_column = None
147
+ self.parameters = {'features': [], 'model': {'bounding_box': self.origin.tolist() + self.maximum.tolist(),
148
+ 'rescale': rescale,
149
+ 'nsteps': nsteps,
150
+ 'reuse_supports': reuse_supports}}
151
+
152
+
153
+ @classmethod
154
+ def from_map2loop_directory(cls, m2l_directory,**kwargs):
155
+ """Alternate constructor for a geological model using m2l output
156
+
157
+ Uses the information saved in the map2loop files to build a geological model.
158
+ You can specify kwargs for building foliation using foliation_params and for
159
+ faults using fault_params. skip_faults is a flag that allows for the faults to be skipped.
160
+
161
+ Parameters
162
+ ----------
163
+ m2l_directory : string
164
+ path to map2loop directory
165
+
166
+ Returns
167
+ -------
168
+ (GeologicalModel, dict)
169
+ the created geological model and a dictionary of the map2loop data
170
+ """
171
+ from LoopStructural.utils import process_map2loop, build_model
172
+ m2lflags = kwargs.pop('m2lflags',{})
173
+ m2l_data = process_map2loop(m2l_directory,m2lflags)
174
+ return build_model(m2l_data,**kwargs), m2l_data
175
+
176
+ @classmethod
177
+ def from_file(cls, file):
178
+ """Load a geological model from file
179
+
180
+ Parameters
181
+ ----------
182
+ file : string
183
+ path to the file
184
+
185
+ Returns
186
+ -------
187
+ GeologicalModel
188
+ the geological model object
189
+ """
190
+ try:
191
+ import dill as pickle
192
+ except ImportError:
193
+ logger.error("Cannot import from file, dill not installed")
194
+ return None
195
+ model = pickle.load(open(file,'rb'))
196
+ if type(model) == GeologicalModel:
197
+ return model
198
+ else:
199
+ logger.error('{} does not contain a geological model'.format(file))
200
+ return None
201
+
202
+ def to_file(self, file):
203
+ """Save a model to a pickle file requires dill
204
+
205
+ Parameters
206
+ ----------
207
+ file : string
208
+ path to file location
209
+ """
210
+ try:
211
+ import dill as pickle
212
+ except ImportError:
213
+ logger.error("Cannot write to file, dill not installed \n"
214
+ "pip install dill")
215
+ return
216
+ try:
217
+ pickle.dump(self,open(file,'wb'))
218
+ except pickle.PicklingError:
219
+ logger.error('Error saving file')
220
+
221
+ def _add_feature(self, feature):
222
+ """
223
+ Add a feature to the model stack
224
+
225
+ Parameters
226
+ ----------
227
+ feature : GeologicalFeature
228
+ the geological feature to add
229
+
230
+ """
231
+
232
+ if feature.name in self.feature_name_index:
233
+ logger.info("Feature %s already exists at %i, overwriting" %
234
+ (feature.name, self.feature_name_index[feature.name]))
235
+ self.features[self.feature_name_index[feature.name]] = feature
236
+ else:
237
+ self.features.append(feature)
238
+ self.feature_name_index[feature.name] = len(self.features) - 1
239
+ logger.info("Adding %s to model at location %i" % (
240
+ feature.name, len(self.features)))
241
+ self._add_domain_fault_above(feature)
242
+ self._add_unconformity_above(feature)
243
+ feature.set_model(self)
244
+
245
+ def set_model_data(self, data):
246
+ """
247
+ Set the data array for the model
248
+
249
+ Parameters
250
+ ----------
251
+ data : pandas data frame
252
+ with column headers corresponding to the
253
+ type, X, Y, Z, nx, ny, nz, val, strike, dip, dip_dir, plunge,
254
+ plunge_dir, azimuth
255
+
256
+ Returns
257
+ -------
258
+ Note
259
+ ----
260
+ Type can be any unique identifier for the feature the data point
261
+ 'eg' 'S0', 'S2', 'F1_axis'
262
+ it is then used by the create functions to get the correct data
263
+ """
264
+ if type(data) != pd.DataFrame:
265
+ logger.warning(
266
+ "Data is not a pandas data frame, trying to read data frame "
267
+ "from csv")
268
+ try:
269
+ data = pd.read_csv(data)
270
+ except:
271
+ logger.error("Could not load pandas data frame from data")
272
+
273
+ self.data = data.copy()
274
+ self.data['X'] -= self.origin[0]
275
+ self.data['Y'] -= self.origin[1]
276
+ self.data['Z'] -= self.origin[2]
277
+ self.data['X'] /= self.scale_factor
278
+ self.data['Y'] /= self.scale_factor
279
+ self.data['Z'] /= self.scale_factor
280
+ if 'type' in self.data:
281
+ logger.warning("'type' is being replaced with 'feature_name' \n")
282
+ self.data.rename(columns={'type':'feature_name'},inplace=True)
283
+ for h in all_heading():
284
+ if h not in self.data:
285
+ self.data[h] = np.nan
286
+ if h == 'w':
287
+ self.data[h] = 1.
288
+ if h == 'coord':
289
+ self.data[h] = 0
290
+
291
+ if 'strike' in self.data and 'dip' in self.data:
292
+ mask = np.all(~np.isnan(self.data.loc[:, ['strike', 'dip']]),
293
+ axis=1)
294
+ self.data.loc[mask, gradient_vec_names()] = strike_dip_vector(
295
+ self.data.loc[mask, 'strike'], self.data.loc[mask, 'dip'])
296
+ self.data.drop(['strike', 'dip'], axis=1, inplace=True)
297
+ # self.data.loc
298
+ # if 'nx' in self.data and 'ny' in self.data and 'nz' in self.data:
299
+ # mask = np.all(~np.isnan(self.data.loc[:, ['nx', 'ny','nz']]),
300
+ # axis=1)
301
+ # self.data.loc[mask,['nx', 'ny','nz']] /= self.scale_factor
302
+ def extend_model_data(self, newdata):
303
+ """
304
+ Extends the data frame
305
+
306
+ Parameters
307
+ ----------
308
+ newdata : pandas data frame
309
+ data to add to the existing dataframe
310
+ Returns
311
+ -------
312
+ """
313
+ logger.warning("Extend data is untested and may have unexpected consequences")
314
+ data_temp = newdata.copy()
315
+ data_temp['X'] -= self.origin[0]
316
+ data_temp['Y'] -= self.origin[1]
317
+ data_temp['Z'] -= self.origin[2]
318
+ data_temp['X'] /= self.scale_factor
319
+ data_temp['Y'] /= self.scale_factor
320
+ data_temp['Z'] /= self.scale_factor
321
+ self.data.concat([self.data, data_temp], sort=True)
322
+
323
+ def set_stratigraphic_column(self, stratigraphic_column,cmap='tab20'):
324
+ """
325
+ Adds a stratigraphic column to the model
326
+
327
+ Parameters
328
+ ----------
329
+ stratigraphic_column : dictionary
330
+ cmap : matplotlib.cmap
331
+ Returns
332
+ -------
333
+
334
+ Notes
335
+ -----
336
+ stratigraphic_column is a nested dictionary with the format
337
+ {'group':
338
+ {'series1':
339
+ {'min':0., 'max':10.,'id':0,'colour':}
340
+ }
341
+ }
342
+
343
+ """
344
+ # if the colour for a unit hasn't been specified we can just sample from
345
+ # a colour map e.g. tab20
346
+ random_colour = True
347
+ n_units=0
348
+ for g in stratigraphic_column.keys():
349
+ for u in stratigraphic_column[g].keys():
350
+ if 'colour' in stratigraphic_column[g][u]:
351
+ random_colour = False
352
+ break
353
+ n_units+=1
354
+ if random_colour:
355
+ import matplotlib.cm as cm
356
+ cmap = cm.get_cmap(cmap,n_units)
357
+ cmap_colours = cmap.colors
358
+ ci = 0
359
+ for g in stratigraphic_column.keys():
360
+ for u in stratigraphic_column[g].keys():
361
+ stratigraphic_column[g][u]['colour'] = cmap_colours[ci,:]
362
+
363
+ self.stratigraphic_column = stratigraphic_column
364
+
365
+ def create_from_feature_list(self, features):
366
+ """Initialises a model from a dictionary containing the features
367
+
368
+ Parameters
369
+ ----------
370
+ features : [type]
371
+ [description]
372
+
373
+ Raises
374
+ ------
375
+ LoopBaseException
376
+ [description]
377
+ """
378
+ for f in features:
379
+ featuretype = f.pop('featuretype', None)
380
+ if featuretype is None:
381
+ raise LoopBaseException
382
+ if featuretype == 'strati':
383
+ self.create_and_add_foliation(f)
384
+ # if featuretype == 'fault':
385
+ # self.create_and_add_fault(f)
386
+ if featuretype == 'folded_strati':
387
+ self.create_and_add_folded_foliation(f)
388
+
389
+ def get_interpolator(self, interpolatortype='PLI', nelements=1e5,
390
+ buffer=0.2, **kwargs):
391
+ """
392
+ Returns an interpolator given the arguments, also constructs a
393
+ support for a discrete interpolator
394
+
395
+ Parameters
396
+ ----------
397
+ interpolatortype : string
398
+ define the interpolator type
399
+ nelements : int
400
+ number of elements in the interpolator
401
+ buffer : double or numpy array 3x1
402
+ value(s) between 0,1 specifying the buffer around the bounding box
403
+ data_bb : bool
404
+ whether to use the model boundary or the boundary around
405
+ kwargs : no kwargs used, this just catches any additional arguments
406
+
407
+ Returns
408
+ -------
409
+ """
410
+ # get an interpolator for
411
+ interpolator = None
412
+ bb = np.copy(self.bounding_box)
413
+ # add a buffer to the interpolation domain, this is necessary for
414
+ # faults but also generally a good
415
+ # idea to avoid boundary problems
416
+ # buffer = bb[1, :]
417
+ buffer = (bb[1,:]-bb[0,:])*buffer
418
+ bb[0, :] -= buffer # *(bb[1,:]-bb[0,:])
419
+ bb[1, :] += buffer # *(bb[1,:]-bb[0,:])
420
+ box_vol = (bb[1, 0]-bb[0, 0]) * (bb[1, 1]-bb[0, 1]) * (bb[1, 2]-bb[0, 2])
421
+ if interpolatortype == "PLI":
422
+ nelements /= 5
423
+ ele_vol = box_vol / nelements
424
+ # calculate the step vector of a regular cube
425
+ step_vector = np.zeros(3)
426
+ step_vector[:] = ele_vol ** (1. / 3.)
427
+ # step_vector /= np.array([1,1,2])
428
+ # number of steps is the length of the box / step vector
429
+ nsteps = np.ceil((bb[1, :] - bb[0, :]) / step_vector).astype(int)
430
+ # create a structured grid using the origin and number of steps
431
+ mesh_id = 'mesh_{}'.format(nelements)
432
+ mesh = self.support.get(mesh_id,
433
+ TetMesh(origin=bb[0, :], nsteps=nsteps,
434
+ step_vector=step_vector))
435
+ if mesh_id not in self.support:
436
+ self.support[mesh_id] = mesh
437
+ logger.info("Creating regular tetrahedron mesh with %i elements \n"
438
+ "for modelling using PLI" % (mesh.ntetra))
439
+
440
+ return PLI(mesh)
441
+
442
+ if interpolatortype == 'FDI':
443
+ # find the volume of one element
444
+ ele_vol = box_vol / nelements
445
+ # calculate the step vector of a regular cube
446
+ step_vector = np.zeros(3)
447
+ step_vector[:] = ele_vol ** (1. / 3.)
448
+ # number of steps is the length of the box / step vector
449
+ nsteps = np.ceil((bb[1, :] - bb[0, :]) / step_vector).astype(int)
450
+ if np.any(np.less(nsteps, 3)):
451
+ logger.error("Cannot create interpolator: number of steps is too small")
452
+ return None
453
+ # create a structured grid using the origin and number of steps
454
+ grid_id = 'grid_{}'.format(nelements)
455
+ grid = self.support.get(grid_id, StructuredGrid(origin=bb[0, :],
456
+ nsteps=nsteps,
457
+ step_vector=step_vector))
458
+ if grid_id not in self.support:
459
+ self.support[grid_id] = grid
460
+ logger.info("Creating regular grid with %i elements \n"
461
+ "for modelling using FDI" % grid.n_elements)
462
+ return FDI(grid)
463
+
464
+ if interpolatortype == "DFI": # "fold" in kwargs:
465
+ nelements /= 5
466
+ ele_vol = box_vol / nelements
467
+ # calculate the step vector of a regular cube
468
+ step_vector = np.zeros(3)
469
+ step_vector[:] = ele_vol ** (1. / 3.)
470
+ # number of steps is the length of the box / step vector
471
+ nsteps = np.ceil((bb[1, :] - bb[0, :]) / step_vector).astype(int)
472
+ # create a structured grid using the origin and number of steps
473
+ mesh = kwargs.get('mesh', TetMesh(origin=bb[0, :], nsteps=nsteps,
474
+ step_vector=step_vector))
475
+ logger.info("Creating regular tetrahedron mesh with %i elements \n"
476
+ "for modelling using DFI" % mesh.ntetra)
477
+ return DFI(mesh, kwargs['fold'])
478
+ if interpolatortype == 'Surfe' or interpolatortype == 'surfe' and \
479
+ surfe:
480
+ method = kwargs.get('method', 'single_surface')
481
+ logger.info("Using surfe interpolator")
482
+ return Surfe(method)
483
+ logger.warning("No interpolator")
484
+ return interpolator
485
+
486
+ def create_and_add_foliation(self, series_surface_data, **kwargs):
487
+ """
488
+ Parameters
489
+ ----------
490
+ series_surface_data : string
491
+ corresponding to the feature_name in the data
492
+ kwargs
493
+
494
+ Returns
495
+ -------
496
+ feature : GeologicalFeature
497
+ the created geological feature
498
+ """
499
+ self.parameters['features'].append({'feature_type': 'foliation', 'feature_name': series_surface_data, **kwargs})
500
+ interpolator = self.get_interpolator(**kwargs)
501
+ series_builder = GeologicalFeatureInterpolator(interpolator,
502
+ name=series_surface_data,
503
+ **kwargs)
504
+ # add data
505
+ series_data = self.data[self.data['feature_name'] == series_surface_data]
506
+ if series_data.shape[0] == 0:
507
+ logger.warning("No data for %s, skipping" % series_surface_data)
508
+ return
509
+ series_builder.add_data_from_data_frame(series_data)
510
+ self._add_faults(series_builder)
511
+
512
+ # build feature
513
+ series_feature = series_builder.build(**kwargs)
514
+ series_feature.type = 'series'
515
+ # see if any unconformities are above this feature if so add region
516
+ # self._add_unconformity_above(series_feature)self._add_feature(series_feature)
517
+ self._add_feature(series_feature)
518
+ return series_feature
519
+
520
+ def create_and_add_fold_frame(self, foldframe_data, **kwargs):
521
+ """
522
+ Parameters
523
+ ----------
524
+ foldframe_data : string
525
+ unique string in feature_name column
526
+
527
+ kwargs
528
+
529
+ Returns
530
+ -------
531
+ fold_frame : FoldFrame
532
+ the created fold frame
533
+ """
534
+ self.parameters['features'].append({'feature_type': 'fold_frame', 'feature_name': foldframe_data, **kwargs})
535
+ # create fault frame
536
+ interpolator = self.get_interpolator(**kwargs)
537
+ #
538
+ fold_frame_builder = StructuralFrameBuilder(interpolator,
539
+ name=foldframe_data,
540
+ **kwargs)
541
+ # add data
542
+ fold_frame_data = self.data[self.data['feature_name'] == foldframe_data]
543
+ fold_frame_builder.add_data_from_data_frame(fold_frame_data)
544
+ self._add_faults(fold_frame_builder[0])
545
+ self._add_faults(fold_frame_builder[1])
546
+ self._add_faults(fold_frame_builder[2])
547
+
548
+ fold_frame = fold_frame_builder.build(frame=FoldFrame, **kwargs)
549
+ # for i in range(3):
550
+ # self._add_unconformity_above(fold_frame[i])
551
+ fold_frame.type = 'structuralframe'
552
+ self._add_feature(fold_frame)
553
+
554
+ return fold_frame
555
+
556
+ def create_and_add_folded_foliation(self, foliation_data, fold_frame=None,
557
+ **kwargs):
558
+ """
559
+ Create a folded foliation field from data and a fold frame
560
+
561
+ Parameters
562
+ ----------
563
+ foliation_data : str
564
+ unique string in type column of data frame
565
+ fold_frame : FoldFrame
566
+ kwargs
567
+ additional kwargs to be passed through to other functions
568
+
569
+ Returns
570
+ -------
571
+ feature : GeologicalFeature
572
+ created geological feature
573
+ """
574
+ self.parameters['features'].append(
575
+ {'feature_type': 'fold_foliation', 'feature_name': foliation_data, 'fold_frame': fold_frame, **kwargs})
576
+ if fold_frame is None:
577
+ logger.info("Using last feature as fold frame")
578
+ fold_frame = self.features[-1]
579
+ assert type(fold_frame) == FoldFrame, "Please specify a FoldFrame"
580
+ fold = FoldEvent(fold_frame,name='Fold_{}'.format(foliation_data))
581
+ fold_interpolator = self.get_interpolator("DFI", fold=fold, **kwargs)
582
+ series_builder = GeologicalFeatureInterpolator(
583
+ interpolator=fold_interpolator,
584
+ name=foliation_data)
585
+
586
+ series_builder.add_data_from_data_frame(
587
+ self.data[self.data['feature_name'] == foliation_data])
588
+ self._add_faults(series_builder)
589
+
590
+ series_builder.add_data_to_interpolator(True)
591
+ if "fold_axis" in kwargs:
592
+ fold.fold_axis = kwargs['fold_axis']
593
+ if "av_fold_axis" in kwargs:
594
+ _calculate_average_intersection(series_builder, fold_frame, fold)
595
+ if fold.fold_axis is None:
596
+ far, fad = fold_frame.calculate_fold_axis_rotation(
597
+ series_builder)
598
+ fold_axis_rotation = FoldRotationAngle(far, fad)
599
+ a_wl = kwargs.get("axis_wl", None)
600
+ if 'axis_function' in kwargs:
601
+ # allow predefined function to be used
602
+ fold_axis_rotation.set_function(kwargs['axis_function'])
603
+ else:
604
+ fold_axis_rotation.fit_fourier_series(wl=a_wl)
605
+ fold.fold_axis_rotation = fold_axis_rotation
606
+ # give option of passing own fold limb rotation function
607
+ flr, fld = fold_frame.calculate_fold_limb_rotation(
608
+ series_builder)
609
+ fold_limb_rotation = FoldRotationAngle(flr, fld)
610
+ l_wl = kwargs.get("limb_wl", None)
611
+ if 'limb_function' in kwargs:
612
+ # allow for predefined functions to be used
613
+ fold_limb_rotation.set_function(kwargs['limb_function'])
614
+ else:
615
+ fold_limb_rotation.fit_fourier_series(wl=l_wl)
616
+ fold.fold_limb_rotation = fold_limb_rotation
617
+ # fold_limb_fitter = kwargs.get("fold_limb_function",
618
+ # _interpolate_fold_limb_rotation_angle)
619
+ # fold_limb_fitter(series_builder, fold_frame, fold, result, **kwargs)
620
+ kwargs['fold_weights'] = kwargs.get('fold_weights', None)
621
+
622
+ self._add_faults(series_builder)
623
+ # build feature
624
+ kwargs['cgw'] = 0.
625
+ kwargs['fold'] = fold
626
+ series_feature = series_builder.build(**kwargs)
627
+ series_feature.type = 'series'
628
+ # see if any unconformities are above this feature if so add region
629
+ # self._add_unconformity_above(series_feature)self._add_feature(series_feature)
630
+ # result['support'] = series_feature.get_interpolator().support
631
+ self._add_feature(series_feature)
632
+ return series_feature
633
+
634
+ def create_and_add_folded_fold_frame(self, fold_frame_data,
635
+ fold_frame=None,
636
+ **kwargs):
637
+ """
638
+
639
+ Parameters
640
+ ----------
641
+ fold_frame_data : string
642
+ name of the feature to be added
643
+
644
+ fold_frame : StructuralFrame, optional
645
+ the fold frame for the fold if not specified uses last feature added
646
+
647
+ kwargs
648
+
649
+ Returns
650
+ -------
651
+ fold_frame : FoldFrame
652
+ created fold frame
653
+ """
654
+ self.parameters['features'].append(
655
+ {'feature_type': 'folded_fold_frame', 'feature_name': fold_frame_data, 'fold_frame': fold_frame, **kwargs})
656
+ if fold_frame is None:
657
+ logger.info("Using last feature as fold frame")
658
+ fold_frame = self.features[-1]
659
+ assert type(fold_frame) == FoldFrame, "Please specify a FoldFrame"
660
+ fold = FoldEvent(fold_frame,name='Fold_{}'.format(fold_frame_data))
661
+ fold_interpolator = self.get_interpolator("DFI", fold=fold, **kwargs)
662
+ frame_interpolator = self.get_interpolator(**kwargs)
663
+ interpolators = [fold_interpolator, frame_interpolator,
664
+ frame_interpolator.copy()]
665
+ fold_frame_builder = StructuralFrameBuilder(
666
+ interpolators=interpolators, name=fold_frame_data, **kwargs)
667
+ fold_frame_builder.add_data_from_data_frame(
668
+ self.data[self.data['feature_name'] == fold_frame_data])
669
+
670
+ ## add the data to the interpolator for the main foliation
671
+ fold_frame_builder[0].add_data_to_interpolator(True)
672
+ if "fold_axis" in kwargs:
673
+ logger.info("Using cylindrical fold axis")
674
+ fold.fold_axis = kwargs['fold_axis']
675
+ if "av_fold_axis" in kwargs:
676
+ logger.info("Using average intersection lineation for \n"
677
+ "fold axis")
678
+ _calculate_average_intersection(fold_frame_builder[0], fold_frame,
679
+ fold)
680
+
681
+ if fold.fold_axis is None:
682
+ logger.info("Fitting fold axis rotation angle")
683
+ far, fad = fold_frame.calculate_fold_axis_rotation(
684
+ fold_frame_builder[0])
685
+ fold_axis_rotation = FoldRotationAngle(far, fad)
686
+ a_wl = kwargs.get("axis_wl", None)
687
+ if 'axis_function' in kwargs:
688
+ # allow predefined function to be used
689
+ fold_axis_rotation.set_function(kwargs['axis_function'])
690
+ else:
691
+ fold_axis_rotation.fit_fourier_series(wl=a_wl)
692
+ fold.fold_axis_rotation = fold_axis_rotation
693
+ # give option of passing own fold limb rotation function
694
+ flr, fld = fold_frame.calculate_fold_limb_rotation(
695
+ fold_frame_builder[0])
696
+ fold_limb_rotation = FoldRotationAngle(flr, fld)
697
+ l_wl = kwargs.get("limb_wl", None)
698
+ if 'limb_function' in kwargs:
699
+ # allow for predefined functions to be used
700
+ fold_limb_rotation.set_function(kwargs['limb_function'])
701
+ else:
702
+ fold_limb_rotation.fit_fourier_series(wl=l_wl)
703
+ fold.fold_limb_rotation = fold_limb_rotation
704
+ # fold_limb_fitter = kwargs.get("fold_limb_function",
705
+ # _interpolate_fold_limb_rotation_angle)
706
+ # fold_limb_fitter(series_builder, fold_frame, fold, result, **kwargs)
707
+ kwargs['fold_weights'] = kwargs.get('fold_weights', None)
708
+
709
+ for i in range(3):
710
+ self._add_faults(fold_frame_builder[i])
711
+ # build feature
712
+ kwargs['cgw'] = 0.
713
+ kwargs['fold'] = fold
714
+ self._add_faults(fold_frame_builder[0])
715
+ self._add_faults(fold_frame_builder[1])
716
+ self._add_faults(fold_frame_builder[2])
717
+ fold_frame = fold_frame_builder.build(**kwargs, frame=FoldFrame)
718
+ fold_frame.type = 'structuralframe'
719
+ # see if any unconformities are above this feature if so add region
720
+ # for i in range(3):
721
+ # self._add_unconformity_above(fold_frame[i])
722
+
723
+ self._add_feature(fold_frame)
724
+
725
+
726
+ return fold_frame
727
+
728
+ def _add_faults(self, feature_builder, features=None):
729
+ """Adds all existing faults to a geological feature builder
730
+
731
+ Parameters
732
+ ----------
733
+ feature_builder : GeologicalFeatureInterpolator/StructuralFrameBuilder
734
+ The feature buider to add the faults to
735
+ features : list, optional
736
+ A specific list of features rather than all features in the model
737
+ Returns
738
+ -------
739
+
740
+ """
741
+ if features is None:
742
+ features = self.features
743
+ for f in reversed(features):
744
+ if f.type == 'fault':
745
+ feature_builder.add_fault(f)
746
+ # if f.type == 'unconformity':
747
+ # break
748
+ def _add_domain_fault_above(self, feature):
749
+ """
750
+ Looks through the feature list and adds any domain faults to the feature. The domain fault masks everything
751
+ where the fault scalar field is < 0 as being active when added to feature.
752
+
753
+ Parameters
754
+ ----------
755
+ feature : GeologicalFeatureInterpolator
756
+ the feature being added to the model where domain faults should be added
757
+
758
+ Returns
759
+ -------
760
+
761
+ """
762
+ for f in reversed(self.features):
763
+ if f.name == feature.name:
764
+ continue
765
+ if f.type == 'domain_fault':
766
+ feature.add_region(lambda pos: f.evaluate_value(pos) < 0)
767
+ break
768
+
769
+ def _add_domain_fault_below(self, domain_fault):
770
+ """
771
+ Looks through the feature list and adds any the domain_fault to the features that already exist in the stack
772
+ until an unconformity is reached. domain faults to the feature. The domain fault masks everything
773
+ where the fault scalar field is < 0 as being active when added to feature.
774
+
775
+ Parameters
776
+ ----------
777
+ feature : GeologicalFeatureInterpolator
778
+ the feature being added to the model where domain faults should be added
779
+
780
+ Returns
781
+ -------
782
+
783
+ """
784
+ for f in reversed(self.features):
785
+ if f.name == domain_fault.name:
786
+ continue
787
+ f.add_region(lambda pos: domain_fault.evaluate_value(pos) > 0)
788
+ if f.type == 'unconformity':
789
+ break
790
+
791
+ def _add_unconformity_above(self, feature):
792
+ """
793
+
794
+ Adds a region to the feature to prevent the value from being
795
+ interpolated where the unconformities exists above e.g.
796
+ if there is another feature above and the unconformity is at 0
797
+ then the features added below (after) will only be visible where the
798
+ uncomformity is <0
799
+
800
+ Parameters
801
+ ----------
802
+ feature - GeologicalFeature
803
+
804
+ Returns
805
+ -------
806
+
807
+ """
808
+ for f in reversed(self.features):
809
+ if f.type == 'unconformity':
810
+ feature.add_region(lambda pos: f.evaluate(pos))
811
+ break
812
+
813
+ def _add_unconformity_below(self, feature):
814
+ """
815
+ Adds a region to the features that represents the
816
+ unconformity so it is not evaluated below the unconformity
817
+
818
+ Parameters
819
+ ----------
820
+ feature
821
+
822
+ Returns
823
+ -------
824
+
825
+ """
826
+ for f in self.features:
827
+ if f.type == 'series' and feature.feature.name != f.name:
828
+ f.add_region(lambda pos: ~feature.evaluate(pos))
829
+ # for f in reversed(self.features):
830
+ # if f.type == 'unconformity':
831
+ # feature.add_region(lambda pos: f.evaluate(pos))
832
+ # break
833
+ # feature.add_region(lambda pos: ~uc.evaluate(pos))
834
+
835
+ def create_and_add_unconformity(self, unconformity_surface_data, **kwargs):
836
+ """
837
+ Parameters
838
+ ----------
839
+ unconformity_surface_data : string
840
+ name of the unconformity data in the data frame
841
+
842
+ Returns
843
+ -------
844
+ """
845
+ # self.parameters['features'].append({'feature_type':'unconformity','feature_name':unconformity_surface_data,**kwargs})
846
+ interpolator = self.get_interpolator(**kwargs)
847
+ unconformity_feature_builder = GeologicalFeatureInterpolator(
848
+ interpolator, name=unconformity_surface_data)
849
+ # add data
850
+ unconformity_data = self.data[
851
+ self.data['feature_name'] == unconformity_surface_data]
852
+
853
+ unconformity_feature_builder.add_data_from_data_frame(
854
+ unconformity_data)
855
+ # look through existing features if there is a fault before an
856
+ # unconformity
857
+ # then add to the feature, once we get to an unconformity stop
858
+ self._add_faults(unconformity_feature_builder)
859
+
860
+ # build feature
861
+ uc_feature_base = unconformity_feature_builder.build(**kwargs)
862
+ uc_feature_base.type = 'unconformity_base'
863
+ # uc_feature = UnconformityFeature(uc_feature_base,0)
864
+ # iterate over existing features and add the unconformity as a
865
+ # region so the feature is only
866
+ # evaluated where the unconformity is positive
867
+ return self.add_unconformity(uc_feature_base, 0)
868
+
869
+ def add_unconformity(self, feature, value):
870
+ """
871
+ Use an existing feature to add an unconformity to the model.
872
+
873
+ Parameters
874
+ ----------
875
+ feature : GeologicalFeature
876
+ existing geological feature
877
+ value : float
878
+ scalar value of isosurface that represents
879
+
880
+ Returns
881
+ -------
882
+ unconformity : GeologicalFeature
883
+ unconformity feature
884
+
885
+ """
886
+ self.parameters['features'].append({'feature_type': 'unconformity', 'feature': feature, 'value': value})
887
+ uc_feature = UnconformityFeature(feature, value)
888
+
889
+ # for f in self.features:
890
+ # f.add_region(lambda pos: uc_feature.evaluate(pos))
891
+
892
+ # see if any unconformities are above this feature if so add region
893
+ # self._add_unconformity_above(uc_feature)
894
+ # self._add_unconformity_below(feature)#, uc_feature)
895
+ self._add_feature(uc_feature)
896
+
897
+
898
+ return uc_feature
899
+
900
+ def add_onlap_unconformity(self, feature, value):
901
+ """
902
+ Use an existing feature to add an unconformity to the model.
903
+
904
+ Parameters
905
+ ----------
906
+ feature : GeologicalFeature
907
+ existing geological feature
908
+ value : float
909
+ scalar value of isosurface that represents
910
+
911
+ Returns
912
+ -------
913
+ unconformity_feature : GeologicalFeature
914
+ the created unconformity
915
+
916
+ """
917
+ self.parameters['features'].append({'feature_type': 'onlap', 'feature': feature, 'value': value})
918
+
919
+ uc_feature = UnconformityFeature(feature, value)
920
+
921
+ # for f in self.features:
922
+ # f.add_region(lambda pos: uc_feature.evaluate(pos))
923
+
924
+ # see if any unconformities are above this feature if so add region
925
+ # self._add_unconformity_above(uc_feature)
926
+ self._add_unconformity_below(uc_feature) # , uc_feature)
927
+ self._add_feature(uc_feature)
928
+
929
+
930
+ return uc_feature
931
+
932
+ def create_and_add_domain_fault(self, fault_surface_data, **kwargs):
933
+ """
934
+ Parameters
935
+ ----------
936
+ fault_surface_data : string
937
+ name of the domain fault data in the data frame
938
+
939
+ Returns
940
+ -------
941
+ domain_Fault : GeologicalFeature
942
+ the created domain fault
943
+
944
+ """
945
+ # self.parameters['features'].append({'feature_type':'unconformity','feature_name':unconformity_surface_data,**kwargs})
946
+ interpolator = self.get_interpolator(**kwargs)
947
+ domain_fault_feature_builder = GeologicalFeatureInterpolator(
948
+ interpolator, name=fault_surface_data)
949
+ # add data
950
+ unconformity_data = self.data[
951
+ self.data['feature_name'] == fault_surface_data]
952
+
953
+ domain_fault_feature_builder.add_data_from_data_frame(
954
+ unconformity_data)
955
+ # look through existing features if there is a fault before an
956
+ # unconformity
957
+ # then add to the feature, once we get to an unconformity stop
958
+ self._add_faults(domain_fault_feature_builder)
959
+
960
+ # build feature
961
+ domain_fault = domain_fault_feature_builder.build(**kwargs)
962
+ domain_fault.type = 'domain_fault'
963
+ self._add_feature(domain_fault)
964
+ self._add_domain_fault_below(domain_fault)
965
+
966
+ domain_fault_uc = UnconformityFeature(domain_fault,0)
967
+ # iterate over existing features and add the unconformity as a
968
+ # region so the feature is only
969
+ # evaluated where the unconformity is positive
970
+ return domain_fault_uc
971
+
972
+ def create_and_add_fault(self, fault_surface_data, displacement, renormalise=True, **kwargs):
973
+ """
974
+ Parameters
975
+ ----------
976
+ fault_surface_data : string
977
+ name of the fault surface data in the dataframe
978
+ displacement : displacement magnitude
979
+ kwargs : additional kwargs for Fault and interpolators
980
+
981
+ Returns
982
+ -------
983
+ fault : FaultSegment
984
+ created fault
985
+ """
986
+ self.parameters['features'].append(
987
+ {'feature_type': 'fault', 'feature_name': fault_surface_data, 'displacement': displacement, **kwargs})
988
+
989
+ displacement_scaled = displacement / self.scale_factor
990
+ # create fault frame
991
+ interpolator = self.get_interpolator(**kwargs)
992
+ fault_frame_builder = StructuralFrameBuilder(interpolator,
993
+ name=fault_surface_data,
994
+ **kwargs)
995
+ # add data
996
+ fault_frame_data = self.data[
997
+ self.data['feature_name'] == fault_surface_data].copy()
998
+ if 'coord' not in fault_frame_data:
999
+ fault_frame_data['coord'] = 0
1000
+ vals = fault_frame_data['val']
1001
+ if len(np.unique(vals[~np.isnan(vals)])) == 1 and renormalise:
1002
+ logger.info("Setting fault ellipsoid to 1/3 of fault length")
1003
+ xyz = fault_frame_data[['X', 'Y', 'Z']].to_numpy()
1004
+ p1 = xyz[0, :] # fault_frame_data.loc[0 ,['X','Y']]
1005
+ p2 = xyz[-1, :] # fault_frame_data.loc[-1 ,['X','Y']]
1006
+ # get a vector that goes from p1-p2 and normalise
1007
+ vector = p1 - p2
1008
+ length = np.linalg.norm(vector)
1009
+ vector /= length
1010
+ # now create the orthogonal vector
1011
+ # newvector = np.zeros(3)
1012
+ length /= 3
1013
+ # length/=2
1014
+ # print(fault_frame_data)
1015
+ mask = ~np.isnan(fault_frame_data['gx'])
1016
+ vectors = fault_frame_data[mask][['gx', 'gy', 'gz']].to_numpy()
1017
+ lengths = np.linalg.norm(vectors, axis=1)
1018
+ vectors /= lengths[:, None]
1019
+ # added 20/08 rescale fault ellipsoid for m2l
1020
+ # vectors*=length
1021
+ fault_frame_data.loc[mask, ['gx', 'gy', 'gz']] = vectors
1022
+ if 'strike' in fault_frame_data.columns and 'dip' in \
1023
+ fault_frame_data.columns:
1024
+ fault_frame_data = fault_frame_data.drop(['dip', 'strike'],
1025
+ axis=1)
1026
+ # print(fault_frame_data)
1027
+ # if there is no slip direction data assume vertical
1028
+ if fault_frame_data[fault_frame_data['coord'] == 1].shape[0] == 0:
1029
+ logger.info("Adding fault frame slip")
1030
+ loc = np.mean(fault_frame_data[['X', 'Y', 'Z']], axis=0)
1031
+ coord1 = pd.DataFrame([[loc[0], loc[1], loc[2], 0, 0, -1]],
1032
+ columns=normal_vector_headers())
1033
+ coord1['coord'] = 1
1034
+ fault_frame_data = pd.concat([fault_frame_data, coord1],
1035
+ sort=False)
1036
+
1037
+ if fault_frame_data[fault_frame_data['coord'] == 2].shape[0] == 0:
1038
+ logger.info("Adding fault extent data as first and last point")
1039
+ ## first and last point of the line
1040
+ value_data = fault_frame_data[fault_frame_data['val'] == 0]
1041
+ if not value_data.empty:
1042
+ coord2 = value_data.iloc[[0, len(value_data) - 1]]
1043
+ coord2 = coord2.reset_index(drop=True)
1044
+ c2_scale = kwargs.get('length_scale',1.)
1045
+ coord2.loc[0, 'val'] = -1/c2_scale
1046
+ coord2.loc[1, 'val'] = 1/c2_scale
1047
+ coord2['coord'] = 2
1048
+ fault_frame_data = pd.concat([fault_frame_data, coord2],
1049
+ sort=False)
1050
+ fault_frame_builder.add_data_from_data_frame(fault_frame_data)
1051
+ # if there is no fault slip data then we could find the strike of
1052
+ # the fault and build
1053
+ # the second coordinate
1054
+ # if we add a region to the fault then the fault operator doesn't
1055
+ # work but for visualisation
1056
+ # we want to add a region!
1057
+
1058
+ if 'splayregion' in kwargs and 'splay' in kwargs:
1059
+ # result['splayregionfeature'] = RegionFeature(kwargs['splayregion'])
1060
+ # apply splay to all parts of fault frame
1061
+ for i in range(3):
1062
+ # work out the values of the nodes where we want hard
1063
+ # constraints
1064
+ idc = np.arange(0, interpolator.support.n_nodes)[
1065
+ kwargs['splayregion'](interpolator.support.nodes)]
1066
+ val = kwargs['splay'][i].evaluate_value(
1067
+ interpolator.support.nodes[
1068
+ kwargs['splayregion'](interpolator.support.nodes), :])
1069
+ mask = ~np.isnan(val)
1070
+ fault_frame_builder[i].interpolator.add_equality_constraints(
1071
+ idc[mask], val[mask])
1072
+ # check if this fault overprint any existing faults exist in the stack
1073
+ overprinted = kwargs.get('overprinted', [])
1074
+ overprinted_faults = []
1075
+ for o in overprinted:
1076
+ overprinted_faults.append(self.features[self.feature_name_index[o]])
1077
+ self._add_faults(fault_frame_builder[0],overprinted_faults)
1078
+ self._add_faults(fault_frame_builder[1],overprinted_faults)
1079
+ self._add_faults(fault_frame_builder[2],overprinted_faults)
1080
+
1081
+ fault_frame = fault_frame_builder.build(**kwargs)
1082
+ if 'abut' in kwargs:
1083
+ fault_frame[0].add_region(lambda pos: kwargs['abut'].evaluate(pos))
1084
+
1085
+ fault = FaultSegment(fault_frame, displacement=displacement_scaled,
1086
+ **kwargs)
1087
+ for f in reversed(self.features):
1088
+ if f.type == 'unconformity':
1089
+ fault.add_region(lambda pos: f.evaluate_value(pos) <= 0)
1090
+ break
1091
+ if displacement == 0:
1092
+ fault.type = 'fault_inactive'
1093
+ self._add_feature(fault)
1094
+
1095
+
1096
+ return fault
1097
+
1098
+ def rescale(self, points, inplace=True):
1099
+ """
1100
+ Convert from model scale to real world scale - in the future this
1101
+ should also do transformations?
1102
+
1103
+ Parameters
1104
+ ----------
1105
+ points : np.array((N,3),dtype=double)
1106
+ inplace : boolean
1107
+ whether to return a modified copy or modify the original array
1108
+
1109
+ Returns
1110
+ -------
1111
+ points : np.array((N,3),dtype=double)
1112
+
1113
+ """
1114
+ if inplace == False:
1115
+ points = points.copy()
1116
+ points *= self.scale_factor
1117
+ points += self.origin
1118
+ return points
1119
+
1120
+ def scale(self, points, inplace=True):
1121
+ """ Take points in UTM coordinates and reproject
1122
+ into scaled model space
1123
+
1124
+ Parameters
1125
+ ----------
1126
+ points : np.array((N,3),dtype=float)
1127
+ points to
1128
+ inplace : bool, optional default = True
1129
+ whether to copy the points array or update the passed array
1130
+ Returns
1131
+ -------
1132
+ points : np.array((N,3),dtype=double)
1133
+
1134
+ """
1135
+ if inplace==False:
1136
+ points = points.copy()
1137
+
1138
+ points[:, :] -= self.origin
1139
+ points /= self.scale_factor
1140
+ return points
1141
+
1142
+
1143
+ def regular_grid(self, nsteps=(50, 50, 25), shuffle = True, rescale=False):
1144
+ """
1145
+ Return a regular grid within the model bounding box
1146
+
1147
+ Parameters
1148
+ ----------
1149
+ nsteps : tuple
1150
+ number of cells in x,y,z
1151
+
1152
+ Returns
1153
+ -------
1154
+ xyz : np.array((N,3),dtype=float)
1155
+ locations of points in regular grid
1156
+ """
1157
+ x = np.linspace(self.bounding_box[0, 0], self.bounding_box[1, 0],
1158
+ nsteps[0])
1159
+ y = np.linspace(self.bounding_box[0, 1], self.bounding_box[1, 1],
1160
+ nsteps[1])
1161
+ z = np.linspace(self.bounding_box[1, 2], self.bounding_box[0, 2],
1162
+ nsteps[2])
1163
+ xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
1164
+ locs = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
1165
+ if shuffle:
1166
+ logger.info("Shuffling points")
1167
+ np.random.shuffle(locs)
1168
+ if rescale:
1169
+ locs = self.rescale(locs)
1170
+ return locs
1171
+
1172
+ def evaluate_model(self, xyz, scale=True):
1173
+ """Evaluate the stratigraphic id at each location
1174
+
1175
+
1176
+ Parameters
1177
+ ----------
1178
+ xyz : np.array((N,3),dtype=float)
1179
+ locations
1180
+ scale : bool
1181
+ whether to rescale the xyz before evaluating model
1182
+
1183
+ Returns
1184
+ -------
1185
+ stratigraphic_id : np.array(N,dtype=int)
1186
+ the stratigraphic index for locations
1187
+
1188
+ Examples
1189
+ --------
1190
+ Evaluate on a voxet
1191
+
1192
+ >>> x = np.linspace(model.bounding_box[0, 0], model.bounding_box[1, 0],
1193
+ nsteps[0])
1194
+ >>> y = np.linspace(model.bounding_box[0, 1], model.bounding_box[1, 1],
1195
+ nsteps[1])
1196
+ >>> z = np.linspace(model.bounding_box[1, 2], model.bounding_box[0, 2],
1197
+ nsteps[2])
1198
+ >>> xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
1199
+ >>> xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
1200
+ >>> model.evaluate_model(xyz)
1201
+
1202
+ Evaluate on points defined by regular grid function
1203
+
1204
+ >>> model.evaluate_model(model.regular_grid())
1205
+
1206
+
1207
+ Evaluate on a map
1208
+
1209
+ >>> x = np.linspace(self.bounding_box[0, 0], self.bounding_box[1, 0],
1210
+ nsteps[0])
1211
+ >>> y = np.linspace(self.bounding_box[0, 1], self.bounding_box[1, 1],
1212
+ nsteps[1])
1213
+ >>> xx, yy = np.meshgrid(x, y, indexing='ij')
1214
+ >>> zz = np.zeros_like(yy)
1215
+ >>> xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
1216
+ >>> model.evaluate_model(xyz)
1217
+
1218
+ """
1219
+ if scale:
1220
+ xyz = self.scale(xyz,inplace=False)
1221
+ strat_id = np.zeros(xyz.shape[0],dtype=int)
1222
+ for group in self.stratigraphic_column.keys():
1223
+ feature_id = self.feature_name_index.get(group, -1)
1224
+ if feature_id >= 0:
1225
+ feature = self.features[feature_id]
1226
+ vals = feature.evaluate_value(xyz)
1227
+ for series in self.stratigraphic_column[group].values():
1228
+ strat_id[np.logical_and(vals < series.get('max',feature.max()), vals > series.get('min',feature.min()))] = series['id']
1229
+ if feature_id == -1:
1230
+ logger.error('Model does not contain {}'.format(group))
1231
+ return strat_id
1232
+
1233
+ def evaluate_fault_displacements(self,points,scale=True):
1234
+ """Evaluate the fault displacement magnitude at each location
1235
+
1236
+
1237
+ Parameters
1238
+ ----------
1239
+ xyz : np.array((N,3),dtype=float)
1240
+ locations
1241
+ scale : bool
1242
+ whether to rescale the xyz before evaluating model
1243
+
1244
+ Returns
1245
+ -------
1246
+ fault_displacement : np.array(N,dtype=float)
1247
+ the fault displacement magnitude
1248
+ """
1249
+ if scale:
1250
+ points = self.scale(points,inplace=False)
1251
+ vals = np.zeros(points.shape[0])
1252
+ for f in self.features:
1253
+ if f.type == 'fault':
1254
+ disp = f.displacementfeature.evaluate_value(points)
1255
+ vals[~np.isnan(disp)] += disp[~np.isnan(disp)]
1256
+ return vals*-self.scale_factor # convert from restoration magnutude to displacement
1257
+
1258
+ def get_feature_by_name(self, feature_name):
1259
+ """Returns a feature from the mode given a name
1260
+
1261
+
1262
+ Parameters
1263
+ ----------
1264
+ feature_name : string
1265
+ the name of the feature
1266
+
1267
+ Returns
1268
+ -------
1269
+ feature : GeologicalFeature
1270
+ the geological feature with the specified name, or none if no feature
1271
+
1272
+
1273
+
1274
+ """
1275
+ feature_index = self.feature_name_index.get(feature_name,-1)
1276
+ if feature_index > -1:
1277
+ return self.features[feature_index]
1278
+ else:
1279
+ return None
1280
+
1281
+ def evaluate_feature_value(self, feature_name, xyz, scale=True):
1282
+ """Evaluate the scalar value of the geological feature given the name at locations
1283
+ xyz
1284
+
1285
+ Parameters
1286
+ ----------
1287
+ feature_name : string
1288
+ name of the feature
1289
+ xyz : np.array((N,3))
1290
+ locations to evaluate
1291
+ scale : bool, optional
1292
+ whether to scale real world points into model scale, by default True
1293
+
1294
+ Returns
1295
+ -------
1296
+ np.array((N))
1297
+ vector of scalar values
1298
+
1299
+ Examples
1300
+ --------
1301
+ Evaluate on a voxet using model boundaries
1302
+
1303
+ >>> x = np.linspace(model.bounding_box[0, 0], model.bounding_box[1, 0],
1304
+ nsteps[0])
1305
+ >>> y = np.linspace(model.bounding_box[0, 1], model.bounding_box[1, 1],
1306
+ nsteps[1])
1307
+ >>> z = np.linspace(model.bounding_box[1, 2], model.bounding_box[0, 2],
1308
+ nsteps[2])
1309
+ >>> xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
1310
+ >>> xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
1311
+ >>> model.evaluate_feature_vaue('feature',xyz,scale=False)
1312
+
1313
+ Evaluate on points in UTM coordinates
1314
+
1315
+ >>> model.evaluate_feature_vaue('feature',utm_xyz)
1316
+
1317
+ """
1318
+ feature = self.get_feature_by_name(feature_name)
1319
+ if feature:
1320
+ scaled_xyz = xyz
1321
+ if scale:
1322
+ scaled_xyz = self.scale(xyz,inplace=False)
1323
+ return feature.evaluate_value(scaled_xyz)
1324
+ else:
1325
+ return np.zeros(xyz.shape[0])
1326
+
1327
+ def evaluate_feature_gradient(self, feature_name, xyz, scale=True):
1328
+ """Evaluate the gradient of the geological feature at a location
1329
+
1330
+ Parameters
1331
+ ----------
1332
+ feature_name : string
1333
+ name of the geological feature
1334
+ xyz : np.array((N,3))
1335
+ locations to evaluate
1336
+ scale : bool, optional
1337
+ whether to scale real world points into model scale, by default True
1338
+
1339
+ Returns
1340
+ -------
1341
+ results : np.array((N,3))
1342
+ gradient of the scalar field at the locations specified
1343
+ """
1344
+ feature = self.get_feature_by_name(feature_name)
1345
+ if feature:
1346
+ scaled_xyz = xyz
1347
+ if scale:
1348
+ scaled_xyz = self.scale(xyz, inplace = False)
1349
+ return feature.evaluate_gradient(scaled_xyz)
1350
+ else:
1351
+ return np.zeros(xyz.shape[0])