LoopStructural 1.0.3__zip → 1.0.71.dev0__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.
Files changed (119) hide show
  1. Miniconda/envs/loop/Lib/site-packages/LoopStructural/__init__.py +12 -7
  2. Miniconda/envs/loop/Lib/site-packages/LoopStructural/__pycache__/__init__.cpython-36.pyc +0 -0
  3. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/__pycache__/__init__.cpython-36.pyc +0 -0
  4. Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/__pycache__/_base.cpython-36.pyc +0 -0
  5. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__init__.py +3 -0
  6. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/__init__.cpython-36.pyc +0 -0
  7. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/base_structured_3d_support.cpython-36.pyc +0 -0
  8. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/discrete_fold_interpolator.cpython-36.pyc +0 -0
  9. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/discrete_interpolator.cpython-36.pyc +0 -0
  10. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/finite_difference_interpolator.cpython-36.pyc +0 -0
  11. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/geological_interpolator.cpython-36.pyc +0 -0
  12. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/operator.cpython-36.pyc +0 -0
  13. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/piecewiselinear_interpolator.cpython-36.pyc +0 -0
  14. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/structured_grid.cpython-36.pyc +0 -0
  15. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/structured_tetra.cpython-36.pyc +0 -0
  16. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/surfe_wrapper.cpython-36.pyc +0 -0
  17. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/base_structured_3d_support.py +101 -0
  18. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/__pycache__/__init__.cpython-36.pyc +0 -0
  19. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/dsi_helper.c +4137 -2716
  20. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/dsi_helper.cp36-win_amd64.pyd +0 -0
  21. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/discrete_fold_interpolator.py +56 -22
  22. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/discrete_interpolator.py +61 -28
  23. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/finite_difference_interpolator.py +71 -11
  24. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/geological_interpolator.py +22 -3
  25. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/operator.py +16 -1
  26. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/piecewiselinear_interpolator.py +150 -11
  27. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/structured_grid.py +31 -69
  28. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/structured_tetra.py +89 -45
  29. Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/surfe_wrapper.py +7 -8
  30. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/__pycache__/__init__.cpython-36.pyc +0 -0
  31. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__pycache__/__init__.cpython-36.pyc +0 -0
  32. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__pycache__/geological_model.cpython-36.pyc +0 -0
  33. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__pycache__/geological_model_graph.cpython-36.pyc +0 -0
  34. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__pycache__/stratigraphic_column.cpython-36.pyc +0 -0
  35. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/geological_model.py +515 -197
  36. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/geological_model_graph.py +881 -0
  37. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/stratigraphic_column.py +5 -0
  38. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__init__.py +1 -0
  39. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/__init__.cpython-36.pyc +0 -0
  40. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_builder.cpython-36.pyc +0 -0
  41. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_function.cpython-36.pyc +0 -0
  42. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_function_feature.cpython-36.pyc +0 -0
  43. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_segment.cpython-36.pyc +0 -0
  44. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_builder.py +127 -0
  45. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_function.py +2 -1
  46. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_function_feature.py +2 -1
  47. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_segment.py +30 -3
  48. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__init__.py +1 -0
  49. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/__init__.cpython-36.pyc +0 -0
  50. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/cross_product_geological_feature.cpython-36.pyc +0 -0
  51. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/geological_feature.cpython-36.pyc +0 -0
  52. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/geological_feature_builder.cpython-36.pyc +0 -0
  53. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/lambda_geological_feature.cpython-36.pyc +0 -0
  54. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/region_feature.cpython-36.pyc +0 -0
  55. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/structural_frame.cpython-36.pyc +0 -0
  56. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/structural_frame_builder.cpython-36.pyc +0 -0
  57. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/unconformity_feature.cpython-36.pyc +0 -0
  58. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/cross_product_geological_feature.py +18 -5
  59. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/geological_feature.py +22 -49
  60. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/geological_feature_builder.py +171 -47
  61. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/lambda_geological_feature.py +31 -0
  62. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/region_feature.py +3 -0
  63. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/structural_frame.py +28 -11
  64. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/structural_frame_builder.py +32 -22
  65. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/unconformity_feature.py +6 -1
  66. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/__init__.cpython-36.pyc +0 -0
  67. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/fold.cpython-36.pyc +0 -0
  68. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/fold_rotation_angle.cpython-36.pyc +0 -0
  69. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/fold_rotation_angle_feature.cpython-36.pyc +0 -0
  70. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/foldframe.cpython-36.pyc +0 -0
  71. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/svariogram.cpython-36.pyc +0 -0
  72. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/fold.py +13 -5
  73. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/fold_rotation_angle.py +5 -4
  74. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/fold_rotation_angle_feature.py +2 -1
  75. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/foldframe.py +7 -5
  76. Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/svariogram.py +2 -1
  77. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__init__.py +5 -1
  78. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/__init__.cpython-36.pyc +0 -0
  79. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/bounding_box.cpython-36.pyc +0 -0
  80. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/exceptions.cpython-36.pyc +0 -0
  81. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/helper.cpython-36.pyc +0 -0
  82. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/logging.cpython-36.pyc +0 -0
  83. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/map2loop.cpython-36.pyc +0 -0
  84. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/regions.cpython-36.pyc +0 -0
  85. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/utils.cpython-36.pyc +0 -0
  86. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/bounding_box.py +21 -0
  87. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/exceptions.py +2 -1
  88. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/helper.py +10 -2
  89. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/logging.py +60 -0
  90. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/map2loop.py +128 -37
  91. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/regions.py +11 -0
  92. Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/utils.py +40 -47
  93. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/__init__.cpython-36.pyc +0 -0
  94. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/map_viewer.cpython-36.pyc +0 -0
  95. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/model_plotter.cpython-36.pyc +0 -0
  96. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/model_visualisation.cpython-36.pyc +0 -0
  97. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/rotation_angle_plotter.cpython-36.pyc +0 -0
  98. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/sphinx_scraper.cpython-36.pyc +0 -0
  99. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/stratigraphic_column.cpython-36.pyc +0 -0
  100. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/map_viewer.py +236 -36
  101. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/model_plotter.py +2 -1
  102. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/model_visualisation.py +427 -79
  103. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/rotation_angle_plotter.py +29 -12
  104. Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/stratigraphic_column.py +60 -0
  105. Miniconda/envs/loop/Lib/site-packages/{LoopStructural-1.0.3-py3.6.egg-info → LoopStructural-1.0.71.dev0-py3.6.egg-info}/PKG-INFO +1 -1
  106. Miniconda/envs/loop/Lib/site-packages/{LoopStructural-1.0.3-py3.6.egg-info → LoopStructural-1.0.71.dev0-py3.6.egg-info}/SOURCES.txt +10 -5
  107. Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.71.dev0-py3.6.egg-info/requires.txt +8 -0
  108. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/__init__.cpython-36.pyc +0 -0
  109. Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.3-py3.6.egg-info/requires.txt +0 -3
  110. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_faults.cpython-36.pyc +0 -0
  111. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_fold.cpython-36.pyc +0 -0
  112. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_interpolator.cpython-36.pyc +0 -0
  113. Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_refolded.cpython-36.pyc +0 -0
  114. Miniconda/envs/loop/Lib/site-packages/tests/test_faults.py +0 -17
  115. Miniconda/envs/loop/Lib/site-packages/tests/test_fold.py +0 -57
  116. Miniconda/envs/loop/Lib/site-packages/tests/test_interpolator.py +0 -88
  117. Miniconda/envs/loop/Lib/site-packages/tests/test_refolded.py +0 -22
  118. /Miniconda/envs/loop/Lib/site-packages/{LoopStructural-1.0.3-py3.6.egg-info → LoopStructural-1.0.71.dev0-py3.6.egg-info}/dependency_links.txt +0 -0
  119. /Miniconda/envs/loop/Lib/site-packages/{LoopStructural-1.0.3-py3.6.egg-info → LoopStructural-1.0.71.dev0-py3.6.egg-info}/top_level.txt +0 -0
@@ -1,8 +1,10 @@
1
+ """
2
+ Main entry point for creating a geological model
3
+ """
1
4
  import logging
2
5
 
3
6
  import numpy as np
4
7
  import pandas as pd
5
-
6
8
  from LoopStructural.datasets import normal_vector_headers
7
9
  from LoopStructural.interpolators.discrete_fold_interpolator import \
8
10
  DiscreteFoldInterpolator as DFI
@@ -11,34 +13,25 @@ from LoopStructural.interpolators.finite_difference_interpolator import \
11
13
  from LoopStructural.interpolators.piecewiselinear_interpolator import \
12
14
  PiecewiseLinearInterpolator as PLI
13
15
 
14
- try:
15
- from LoopStructural.interpolators.surfe_wrapper import \
16
- SurfeRBFInterpolator as Surfe
17
-
18
- surfe = True
19
-
20
- except ImportError:
21
- surfe = False
22
16
 
23
- from LoopStructural.utils.helper import all_heading, gradient_vec_names, \
24
- strike_dip_vector
17
+ from LoopStructural.interpolators.structured_grid import StructuredGrid
18
+ from LoopStructural.interpolators.structured_tetra import TetMesh
25
19
  from LoopStructural.modelling.fault.fault_segment import FaultSegment
26
- from LoopStructural.modelling.features import \
27
- GeologicalFeatureInterpolator
28
- from LoopStructural.modelling.features import RegionFeature
29
- from LoopStructural.modelling.features import \
30
- StructuralFrameBuilder
31
- from LoopStructural.modelling.features import UnconformityFeature
32
- from LoopStructural.modelling.fold.fold import FoldEvent
20
+ from LoopStructural.modelling.fault import FaultBuilder
21
+ from LoopStructural.modelling.features import (GeologicalFeatureInterpolator,
22
+ RegionFeature,
23
+ StructuralFrameBuilder,
24
+ UnconformityFeature)
33
25
  from LoopStructural.modelling.fold import FoldRotationAngle
26
+ from LoopStructural.modelling.fold.fold import FoldEvent
34
27
  from LoopStructural.modelling.fold.foldframe import FoldFrame
35
- from LoopStructural.interpolators.structured_grid import StructuredGrid
36
- from LoopStructural.interpolators.structured_tetra import TetMesh
37
28
  from LoopStructural.utils.exceptions import LoopBaseException
29
+ from LoopStructural.utils.helper import (all_heading, gradient_vec_names,
30
+ strike_dip_vector)
31
+
32
+ from LoopStructural.utils import getLogger, log_to_file
33
+ logger = getLogger(__name__)
38
34
 
39
- logger = logging.getLogger(__name__)
40
- if not surfe:
41
- logger.warning("Cannot import Surfe")
42
35
 
43
36
 
44
37
  def _calculate_average_intersection(series_builder, fold_frame, fold,
@@ -62,12 +55,30 @@ def _calculate_average_intersection(series_builder, fold_frame, fold,
62
55
 
63
56
  class GeologicalModel:
64
57
  """
65
- A geological model is the recipe for building a 3D model and includes
66
- the rescaling
67
- of the model between 0 and 1.
58
+ A geological model is the recipe for building a 3D model and can include
59
+ the rescaling of the model between 0 and 1.
60
+
61
+ Attributes
62
+ ----------
63
+ features : list
64
+ Contains all features youngest to oldest
65
+ feature_name_index : dict
66
+ maps feature name to the list index of the features
67
+ data : pandas dataframe
68
+ the dataframe used for building the geological model
69
+ nsteps : tuple/np.array(3,dtype=int)
70
+ the number of steps x,y,z to evaluate the model
71
+ origin : tuple/np.array(3,dtype=doubles)
72
+ the origin of the model box
73
+ parameters : dict
74
+ a dictionary tracking the parameters used to build the model
75
+ scale_factor : double
76
+ the scale factor used to rescale the model
77
+
78
+
68
79
  """
69
80
  def __init__(self, origin, maximum, rescale=True, nsteps=(40, 40, 40),
70
- reuse_supports=False):
81
+ reuse_supports=False, logfile=None, loglevel='info'):
71
82
  """
72
83
  Parameters
73
84
  ----------
@@ -78,41 +89,91 @@ class GeologicalModel:
78
89
  rescale : bool
79
90
  whether to rescale the model to between 0/1
80
91
 
92
+ Examples
93
+ --------
94
+ Demo data
95
+
96
+ >>> from LoopStructural.datasets import load_claudius
97
+ >>> from LoopStructural import GeologicalModel
98
+
99
+ >>> data, bb = load_claudius()
100
+
101
+ >>> model = GeologicalModel(bb[:,0],bb[:,1]
102
+ >>> model.set_model_data(data)
103
+ >>> model.create_and_add_foliation('strati')
104
+
105
+ >>> y = np.linspace(model.bounding_box[0, 1], model.bounding_box[1, 1],
106
+ nsteps[1])
107
+ >>> z = np.linspace(model.bounding_box[1, 2], model.bounding_box[0, 2],
108
+ nsteps[2])
109
+ >>> xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
110
+ >>> xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
111
+ >>> model.evaluate_feature_value('strati',xyz,scale=False)
112
+
113
+
81
114
  """
115
+ if logfile:
116
+ self.logfile = logfile
117
+ log_to_file(logfile,loglevel)
118
+
119
+ logger.info('Initialising geological model')
82
120
  self.features = []
83
121
  self.feature_name_index = {}
84
122
  self.data = None
85
123
  self.nsteps = nsteps
86
-
124
+ self._str = 'Instance of LoopStructural.GeologicalModel \n'
125
+ self._str += '------------------------------------------ \n'
87
126
  # we want to rescale the model area so that the maximum length is
88
127
  # 1
89
128
  self.origin = np.array(origin).astype(float)
90
-
129
+ originstr = 'Model origin: {} {} {}'.format(self.origin[0],self.origin[1],self.origin[2])
130
+ logger.info(originstr)
131
+ self._str+=originstr+'\n'
91
132
  self.maximum = np.array(maximum).astype(float)
133
+ maximumstr = 'Model maximum: {} {} {}'.format(self.maximum[0],self.maximum[1],self.maximum[2])
134
+ logger.info(maximumstr)
135
+ self._str+=maximumstr+'\n'
136
+
92
137
  lengths = self.maximum - self.origin
93
138
  self.scale_factor = 1.
94
139
  self.bounding_box = np.zeros((2, 3))
95
140
  self.bounding_box[1, :] = self.maximum - self.origin
96
141
  self.bounding_box[1, :] = self.maximum - self.origin
97
142
  if rescale:
98
- self.scale_factor = np.max(lengths)
99
-
143
+ self.scale_factor = float(np.max(lengths))
144
+ logger.info('Rescaling model using scale factor {}'.format(self.scale_factor))
145
+ self._str+='Model rescale factor: {} \n'.format(self.scale_factor)
146
+ self._str+='The model contains {} GeologicalFeatures \n'.format(len(self.features))
147
+ self._str+=''
148
+ self._str += '------------------------------------------ \n'
149
+ self._str += ''
100
150
  self.bounding_box /= self.scale_factor
101
151
  self.support = {}
102
152
  self.reuse_supports = reuse_supports
153
+ if self.reuse_supports:
154
+ logger.warning("Supports are shared between geological features \n"
155
+ "this may cause unexpected behaviour and should only\n"
156
+ "be use by advanced users")
157
+ logger.info('Reusing interpolation supports: {}'.format(self.reuse_supports))
103
158
  self.stratigraphic_column = None
104
159
  self.parameters = {'features': [], 'model': {'bounding_box': self.origin.tolist() + self.maximum.tolist(),
105
160
  'rescale': rescale,
106
161
  'nsteps': nsteps,
107
162
  'reuse_supports': reuse_supports}}
163
+
164
+ def __str__(self):
165
+ return self._str
108
166
 
167
+ def _ipython_key_completions_(self):
168
+ return self.feature_name_index.keys()
169
+
109
170
  @classmethod
110
171
  def from_map2loop_directory(cls, m2l_directory,**kwargs):
111
172
  """Alternate constructor for a geological model using m2l output
112
173
 
113
174
  Uses the information saved in the map2loop files to build a geological model.
114
175
  You can specify kwargs for building foliation using foliation_params and for
115
- faults using fault_params. skip_faults is a flag that allows for the faults to be skipped.
176
+ faults using fault_params. faults is a flag that allows for the faults to be skipped.
116
177
 
117
178
  Parameters
118
179
  ----------
@@ -124,12 +185,26 @@ class GeologicalModel:
124
185
  (GeologicalModel, dict)
125
186
  the created geological model and a dictionary of the map2loop data
126
187
  """
127
- from LoopStructural.utils import process_map2loop, build_model
128
- m2l_data = process_map2loop(m2l_directory)
188
+ from LoopStructural.utils import build_model, process_map2loop
189
+ logger.info('LoopStructural model initialised from m2l directory: {}'.format(m2l_directory))
190
+ m2lflags = kwargs.pop('m2lflags',{})
191
+ m2l_data = process_map2loop(m2l_directory,m2lflags)
129
192
  return build_model(m2l_data,**kwargs), m2l_data
130
193
 
131
194
  @classmethod
132
195
  def from_file(cls, file):
196
+ """Load a geological model from file
197
+
198
+ Parameters
199
+ ----------
200
+ file : string
201
+ path to the file
202
+
203
+ Returns
204
+ -------
205
+ GeologicalModel
206
+ the geological model object
207
+ """
133
208
  try:
134
209
  import dill as pickle
135
210
  except ImportError:
@@ -137,18 +212,49 @@ class GeologicalModel:
137
212
  return None
138
213
  model = pickle.load(open(file,'rb'))
139
214
  if type(model) == GeologicalModel:
215
+ logger.info('GeologicalModel initialised from file')
140
216
  return model
141
217
  else:
142
218
  logger.error('{} does not contain a geological model'.format(file))
143
219
  return None
220
+
221
+ def __getitem__(self, feature_name):
222
+ """Accessor for feature in features using feature_name_index
144
223
 
224
+ Parameters
225
+ ----------
226
+ feature_name : string
227
+ name of the feature to return
228
+ """
229
+ return self.get_feature_by_name(feature_name)
230
+
231
+ def feature_names(self):
232
+ return self.feature_name_index.keys()
233
+
234
+ def fault_names(self):
235
+ pass
236
+
237
+ def check_inialisation(self):
238
+ if self.data is None:
239
+ logger.error("Data not associated with GeologicalModel. Run set_data")
240
+ return False
241
+
145
242
  def to_file(self, file):
243
+ """Save a model to a pickle file requires dill
244
+
245
+ Parameters
246
+ ----------
247
+ file : string
248
+ path to file location
249
+ """
146
250
  try:
147
251
  import dill as pickle
148
252
  except ImportError:
149
- logger.error("Cannot write to file, dill not installed")
253
+ logger.error("Cannot write to file, dill not installed \n"
254
+ "pip install dill")
150
255
  return
151
256
  try:
257
+ logger.info('Writing GeologicalModel to: {}'.format(file))
152
258
  pickle.dump(self,open(file,'wb'))
153
259
  except pickle.PicklingError:
154
260
  logger.error('Error saving file')
@@ -169,6 +275,7 @@ class GeologicalModel:
169
275
  (feature.name, self.feature_name_index[feature.name]))
170
276
  self.features[self.feature_name_index[feature.name]] = feature
171
277
  else:
278
+ self._str += 'GeologicalFeature: {} of type - {} \n'.format(feature.name,feature.type)
172
279
  self.features.append(feature)
173
280
  self.feature_name_index[feature.name] = len(self.features) - 1
174
281
  logger.info("Adding %s to model at location %i" % (
@@ -176,7 +283,10 @@ class GeologicalModel:
176
283
  self._add_domain_fault_above(feature)
177
284
  self._add_unconformity_above(feature)
178
285
  feature.set_model(self)
179
-
286
+
287
+ def data_for_feature(self,feature_name):
288
+ return self.data.loc[self.data['feature_name'] == feature_name,:]
289
+
180
290
  def set_model_data(self, data):
181
291
  """
182
292
  Set the data array for the model
@@ -204,7 +314,7 @@ class GeologicalModel:
204
314
  data = pd.read_csv(data)
205
315
  except:
206
316
  logger.error("Could not load pandas data frame from data")
207
-
317
+ logger.info('Adding data to GeologicalModel with {} data points'.format(len(data)))
208
318
  self.data = data.copy()
209
319
  self.data['X'] -= self.origin[0]
210
320
  self.data['Y'] -= self.origin[1]
@@ -213,7 +323,7 @@ class GeologicalModel:
213
323
  self.data['Y'] /= self.scale_factor
214
324
  self.data['Z'] /= self.scale_factor
215
325
  if 'type' in self.data:
216
- logger.warning("'type' is being replaced with 'feature_name' \n")
326
+ logger.warning("'type' is depreciated replace with 'feature_name' \n")
217
327
  self.data.rename(columns={'type':'feature_name'},inplace=True)
218
328
  for h in all_heading():
219
329
  if h not in self.data:
@@ -224,6 +334,7 @@ class GeologicalModel:
224
334
  self.data[h] = 0
225
335
 
226
336
  if 'strike' in self.data and 'dip' in self.data:
337
+ logger.info('Converting strike and dip to vectors')
227
338
  mask = np.all(~np.isnan(self.data.loc[:, ['strike', 'dip']]),
228
339
  axis=1)
229
340
  self.data.loc[mask, gradient_vec_names()] = strike_dip_vector(
@@ -255,14 +366,14 @@ class GeologicalModel:
255
366
  data_temp['Z'] /= self.scale_factor
256
367
  self.data.concat([self.data, data_temp], sort=True)
257
368
 
258
- def set_stratigraphic_column(self, stratigraphic_column):
369
+ def set_stratigraphic_column(self, stratigraphic_column,cmap='tab20'):
259
370
  """
260
371
  Adds a stratigraphic column to the model
261
372
 
262
373
  Parameters
263
374
  ----------
264
375
  stratigraphic_column : dictionary
265
-
376
+ cmap : matplotlib.cmap
266
377
  Returns
267
378
  -------
268
379
 
@@ -271,14 +382,46 @@ class GeologicalModel:
271
382
  stratigraphic_column is a nested dictionary with the format
272
383
  {'group':
273
384
  {'series1':
274
- {'min':0., 'max':10.,'id':0}
385
+ {'min':0., 'max':10.,'id':0,'colour':}
275
386
  }
276
387
  }
277
388
 
278
389
  """
390
+ # if the colour for a unit hasn't been specified we can just sample from
391
+ # a colour map e.g. tab20
392
+ logger.info('Adding stratigraphic column to model')
393
+ random_colour = True
394
+ n_units=0
395
+ for g in stratigraphic_column.keys():
396
+ for u in stratigraphic_column[g].keys():
397
+ if 'colour' in stratigraphic_column[g][u]:
398
+ random_colour = False
399
+ break
400
+ n_units+=1
401
+ if random_colour:
402
+ import matplotlib.cm as cm
403
+ cmap = cm.get_cmap(cmap,n_units)
404
+ cmap_colours = cmap.colors
405
+ ci = 0
406
+ for g in stratigraphic_column.keys():
407
+ for u in stratigraphic_column[g].keys():
408
+ stratigraphic_column[g][u]['colour'] = cmap_colours[ci,:]
409
+
279
410
  self.stratigraphic_column = stratigraphic_column
280
411
 
281
412
  def create_from_feature_list(self, features):
413
+ """Initialises a model from a dictionary containing the features
414
+
415
+ Parameters
416
+ ----------
417
+ features : [type]
418
+ [description]
419
+
420
+ Raises
421
+ ------
422
+ LoopBaseException
423
+ [description]
424
+ """
282
425
  for f in features:
283
426
  featuretype = f.pop('featuretype', None)
284
427
  if featuretype is None:
@@ -291,7 +434,7 @@ class GeologicalModel:
291
434
  self.create_and_add_folded_foliation(f)
292
435
 
293
436
  def get_interpolator(self, interpolatortype='PLI', nelements=1e5,
294
- buffer=0.2, **kwargs):
437
+ buffer=0.2, element_volume = None, **kwargs):
295
438
  """
296
439
  Returns an interpolator given the arguments, also constructs a
297
440
  support for a discrete interpolator
@@ -323,51 +466,64 @@ class GeologicalModel:
323
466
  bb[1, :] += buffer # *(bb[1,:]-bb[0,:])
324
467
  box_vol = (bb[1, 0]-bb[0, 0]) * (bb[1, 1]-bb[0, 1]) * (bb[1, 2]-bb[0, 2])
325
468
  if interpolatortype == "PLI":
326
- nelements /= 5
327
- ele_vol = box_vol / nelements
469
+ if element_volume is None:
470
+ nelements /= 5
471
+ element_volume = box_vol / nelements
328
472
  # calculate the step vector of a regular cube
329
473
  step_vector = np.zeros(3)
330
- step_vector[:] = ele_vol ** (1. / 3.)
474
+ step_vector[:] = element_volume ** (1. / 3.)
331
475
  # step_vector /= np.array([1,1,2])
332
476
  # number of steps is the length of the box / step vector
333
477
  nsteps = np.ceil((bb[1, :] - bb[0, :]) / step_vector).astype(int)
334
478
  # create a structured grid using the origin and number of steps
335
- mesh_id = 'mesh_{}'.format(nelements)
336
- mesh = self.support.get(mesh_id,
337
- TetMesh(origin=bb[0, :], nsteps=nsteps,
338
- step_vector=step_vector))
339
- if mesh_id not in self.support:
340
- self.support[mesh_id] = mesh
479
+ if self.reuse_supports:
480
+ mesh_id = 'mesh_{}'.format(nelements)
481
+ mesh = self.support.get(mesh_id,
482
+ TetMesh(origin=bb[0, :], nsteps=nsteps,
483
+ step_vector=step_vector))
484
+ if mesh_id not in self.support:
485
+ self.support[mesh_id] = mesh
486
+ else:
487
+ mesh = TetMesh(origin=bb[0, :], nsteps=nsteps, step_vector=step_vector)
341
488
  logger.info("Creating regular tetrahedron mesh with %i elements \n"
342
489
  "for modelling using PLI" % (mesh.ntetra))
343
490
 
344
491
  return PLI(mesh)
345
492
 
346
493
  if interpolatortype == 'FDI':
494
+
347
495
  # find the volume of one element
348
- ele_vol = box_vol / nelements
496
+ if element_volume is None:
497
+ element_volume = box_vol / nelements
349
498
  # calculate the step vector of a regular cube
350
499
  step_vector = np.zeros(3)
351
- step_vector[:] = ele_vol ** (1. / 3.)
500
+ step_vector[:] = element_volume ** (1. / 3.)
352
501
  # number of steps is the length of the box / step vector
353
502
  nsteps = np.ceil((bb[1, :] - bb[0, :]) / step_vector).astype(int)
503
+ if np.any(np.less(nsteps, 3)):
504
+ logger.error("Cannot create interpolator: number of steps is too small")
505
+ return None
354
506
  # create a structured grid using the origin and number of steps
355
- grid_id = 'grid_{}'.format(nelements)
356
- grid = self.support.get(grid_id, StructuredGrid(origin=bb[0, :],
507
+ if self.reuse_supports:
508
+ grid_id = 'grid_{}'.format(nelements)
509
+ grid = self.support.get(grid_id, StructuredGrid(origin=bb[0, :],
357
510
  nsteps=nsteps,
358
511
  step_vector=step_vector))
359
- if grid_id not in self.support:
360
- self.support[grid_id] = grid
512
+ if grid_id not in self.support:
513
+ self.support[grid_id] = grid
514
+ else:
515
+ grid = StructuredGrid(origin=bb[0, :], nsteps=nsteps,step_vector=step_vector)
361
516
  logger.info("Creating regular grid with %i elements \n"
362
517
  "for modelling using FDI" % grid.n_elements)
363
518
  return FDI(grid)
364
519
 
365
520
  if interpolatortype == "DFI": # "fold" in kwargs:
366
- nelements /= 5
367
- ele_vol = box_vol / nelements
521
+ if element_volume is None:
522
+ nelements /= 5
523
+ element_volume = box_vol / nelements
368
524
  # calculate the step vector of a regular cube
369
525
  step_vector = np.zeros(3)
370
- step_vector[:] = ele_vol ** (1. / 3.)
526
+ step_vector[:] = element_volume ** (1. / 3.)
371
527
  # number of steps is the length of the box / step vector
372
528
  nsteps = np.ceil((bb[1, :] - bb[0, :]) / step_vector).astype(int)
373
529
  # create a structured grid using the origin and number of steps
@@ -376,8 +532,19 @@ class GeologicalModel:
376
532
  logger.info("Creating regular tetrahedron mesh with %i elements \n"
377
533
  "for modelling using DFI" % mesh.ntetra)
378
534
  return DFI(mesh, kwargs['fold'])
379
- if interpolatortype == 'Surfe' or interpolatortype == 'surfe' and \
380
- surfe:
535
+ if interpolatortype == 'Surfe' or interpolatortype == 'surfe':
536
+ # move import of surfe to where we actually try and use it
537
+ try:
538
+ from LoopStructural.interpolators.surfe_wrapper import \
539
+ SurfeRBFInterpolator as Surfe
540
+
541
+ surfe = True
542
+
543
+ except ImportError:
544
+ surfe = False
545
+ if not surfe:
546
+ logger.warning("Cannot import Surfe, try another interpolator")
547
+ raise ImportError
381
548
  method = kwargs.get('method', 'single_surface')
382
549
  logger.info("Using surfe interpolator")
383
550
  return Surfe(method)
@@ -397,6 +564,8 @@ class GeologicalModel:
397
564
  feature : GeologicalFeature
398
565
  the created geological feature
399
566
  """
567
+ if self.check_inialisation() == False:
568
+ return False
400
569
  self.parameters['features'].append({'feature_type': 'foliation', 'feature_name': series_surface_data, **kwargs})
401
570
  interpolator = self.get_interpolator(**kwargs)
402
571
  series_builder = GeologicalFeatureInterpolator(interpolator,
@@ -411,13 +580,53 @@ class GeologicalModel:
411
580
  self._add_faults(series_builder)
412
581
 
413
582
  # build feature
414
- series_feature = series_builder.build(**kwargs)
583
+ # series_feature = series_builder.build(**kwargs)
584
+ series_feature = series_builder.feature
585
+ series_builder.build_arguments = kwargs
415
586
  series_feature.type = 'series'
416
587
  # see if any unconformities are above this feature if so add region
417
588
  # self._add_unconformity_above(series_feature)self._add_feature(series_feature)
418
589
  self._add_feature(series_feature)
419
590
  return series_feature
420
591
 
592
+ def create_and_add_dtm(self, series_surface_data, **kwargs):
593
+ """
594
+ Parameters
595
+ ----------
596
+ series_surface_data : string
597
+ corresponding to the feature_name in the data
598
+ kwargs
599
+
600
+ Returns
601
+ -------
602
+ feature : GeologicalFeature
603
+ the created geological feature
604
+ """
605
+ if self.check_inialisation() == False:
606
+ return False
607
+ self.parameters['features'].append({'feature_type': 'foliation', 'feature_name': series_surface_data, **kwargs})
608
+ interpolator = self.get_interpolator(**kwargs)
609
+ series_builder = GeologicalFeatureInterpolator(interpolator,
610
+ name=series_surface_data,
611
+ **kwargs)
612
+ # add data
613
+ series_data = self.data[self.data['feature_name'] == series_surface_data]
614
+ if series_data.shape[0] == 0:
615
+ logger.warning("No data for %s, skipping" % series_surface_data)
616
+ return
617
+ series_builder.add_data_from_data_frame(series_data)
618
+ # self._add_faults(series_builder)
619
+
620
+ # build feature
621
+ # series_feature = series_builder.build(**kwargs)
622
+ series_feature = series_builder.feature
623
+ series_builder.build_arguments = kwargs
624
+ series_feature.type = 'dtm'
625
+ # see if any unconformities are above this feature if so add region
626
+ # self._add_unconformity_above(series_feature)self._add_feature(series_feature)
627
+ self._add_feature(series_feature)
628
+ return series_feature
629
+
421
630
  def create_and_add_fold_frame(self, foldframe_data, **kwargs):
422
631
  """
423
632
  Parameters
@@ -432,8 +641,9 @@ class GeologicalModel:
432
641
  fold_frame : FoldFrame
433
642
  the created fold frame
434
643
  """
644
+ if self.check_inialisation() == False:
645
+ return False
435
646
  self.parameters['features'].append({'feature_type': 'fold_frame', 'feature_name': foldframe_data, **kwargs})
436
- result = {}
437
647
  # create fault frame
438
648
  interpolator = self.get_interpolator(**kwargs)
439
649
  #
@@ -455,16 +665,18 @@ class GeologicalModel:
455
665
 
456
666
  return fold_frame
457
667
 
458
- def create_and_add_folded_foliation(self, foliation_data, fold_frame=None,
668
+ def create_and_add_folded_foliation(self, foliation_data, fold_frame=None, svario=True,
459
669
  **kwargs):
460
670
  """
461
671
  Create a folded foliation field from data and a fold frame
462
672
 
463
673
  Parameters
464
674
  ----------
465
- foliation_data : string
675
+ foliation_data : str
466
676
  unique string in type column of data frame
467
677
  fold_frame : FoldFrame
678
+ svario : Boolean
679
+ whether to calculate svariograms, saves time if avoided
468
680
  kwargs
469
681
  additional kwargs to be passed through to other functions
470
682
 
@@ -473,13 +685,15 @@ class GeologicalModel:
473
685
  feature : GeologicalFeature
474
686
  created geological feature
475
687
  """
688
+ if self.check_inialisation() == False:
689
+ return False
476
690
  self.parameters['features'].append(
477
691
  {'feature_type': 'fold_foliation', 'feature_name': foliation_data, 'fold_frame': fold_frame, **kwargs})
478
692
  if fold_frame is None:
479
693
  logger.info("Using last feature as fold frame")
480
694
  fold_frame = self.features[-1]
481
695
  assert type(fold_frame) == FoldFrame, "Please specify a FoldFrame"
482
- fold = FoldEvent(fold_frame)
696
+ fold = FoldEvent(fold_frame,name='Fold_{}'.format(foliation_data))
483
697
  fold_interpolator = self.get_interpolator("DFI", fold=fold, **kwargs)
484
698
  series_builder = GeologicalFeatureInterpolator(
485
699
  interpolator=fold_interpolator,
@@ -488,16 +702,19 @@ class GeologicalModel:
488
702
  series_builder.add_data_from_data_frame(
489
703
  self.data[self.data['feature_name'] == foliation_data])
490
704
  self._add_faults(series_builder)
491
-
492
705
  series_builder.add_data_to_interpolator(True)
493
- if "fold_axis" in kwargs:
494
- fold.fold_axis = kwargs['fold_axis']
706
+ fold_axis = kwargs.get('fold_axis',None)
707
+ if fold_axis is not None:
708
+ fold_axis = np.array(fold_axis)
709
+ if len(fold_axis.shape) == 1:
710
+ fold.fold_axis = fold_axis
711
+
495
712
  if "av_fold_axis" in kwargs:
496
713
  _calculate_average_intersection(series_builder, fold_frame, fold)
497
714
  if fold.fold_axis is None:
498
715
  far, fad = fold_frame.calculate_fold_axis_rotation(
499
716
  series_builder)
500
- fold_axis_rotation = FoldRotationAngle(far, fad)
717
+ fold_axis_rotation = FoldRotationAngle(far, fad,svario=svario)
501
718
  a_wl = kwargs.get("axis_wl", None)
502
719
  if 'axis_function' in kwargs:
503
720
  # allow predefined function to be used
@@ -508,24 +725,26 @@ class GeologicalModel:
508
725
  # give option of passing own fold limb rotation function
509
726
  flr, fld = fold_frame.calculate_fold_limb_rotation(
510
727
  series_builder)
511
- fold_limb_rotation = FoldRotationAngle(flr, fld)
728
+ fold_limb_rotation = FoldRotationAngle(flr, fld,svario=svario)
512
729
  l_wl = kwargs.get("limb_wl", None)
513
730
  if 'limb_function' in kwargs:
514
731
  # allow for predefined functions to be used
515
732
  fold_limb_rotation.set_function(kwargs['limb_function'])
516
733
  else:
517
- fold_limb_rotation.fit_fourier_series(wl=l_wl)
734
+ fold_limb_rotation.fit_fourier_series(wl=l_wl,**kwargs)
518
735
  fold.fold_limb_rotation = fold_limb_rotation
519
736
  # fold_limb_fitter = kwargs.get("fold_limb_function",
520
737
  # _interpolate_fold_limb_rotation_angle)
521
738
  # fold_limb_fitter(series_builder, fold_frame, fold, result, **kwargs)
522
- kwargs['fold_weights'] = kwargs.get('fold_weights', None)
739
+ kwargs['fold_weights'] = kwargs.get('fold_weights', {})
523
740
 
524
741
  self._add_faults(series_builder)
525
742
  # build feature
526
743
  kwargs['cgw'] = 0.
527
744
  kwargs['fold'] = fold
528
- series_feature = series_builder.build(**kwargs)
745
+ # series_feature = series_builder.build(**kwargs)
746
+ series_feature = series_builder.feature
747
+ series_builder.build_arguments = kwargs
529
748
  series_feature.type = 'series'
530
749
  # see if any unconformities are above this feature if so add region
531
750
  # self._add_unconformity_above(series_feature)self._add_feature(series_feature)
@@ -541,8 +760,10 @@ class GeologicalModel:
541
760
  Parameters
542
761
  ----------
543
762
  fold_frame_data : string
763
+ name of the feature to be added
544
764
 
545
- fold_frame : StructuralFrame
765
+ fold_frame : StructuralFrame, optional
766
+ the fold frame for the fold if not specified uses last feature added
546
767
 
547
768
  kwargs
548
769
 
@@ -551,16 +772,20 @@ class GeologicalModel:
551
772
  fold_frame : FoldFrame
552
773
  created fold frame
553
774
  """
775
+ if self.check_inialisation() == False:
776
+ return False
554
777
  self.parameters['features'].append(
555
778
  {'feature_type': 'folded_fold_frame', 'feature_name': fold_frame_data, 'fold_frame': fold_frame, **kwargs})
556
779
  if fold_frame is None:
557
780
  logger.info("Using last feature as fold frame")
558
781
  fold_frame = self.features[-1]
559
782
  assert type(fold_frame) == FoldFrame, "Please specify a FoldFrame"
560
- fold = FoldEvent(fold_frame)
783
+ fold = FoldEvent(fold_frame,name='Fold_{}'.format(fold_frame_data))
561
784
  fold_interpolator = self.get_interpolator("DFI", fold=fold, **kwargs)
785
+ gy_fold_interpolator = self.get_interpolator("DFI", fold=fold, **kwargs)
786
+
562
787
  frame_interpolator = self.get_interpolator(**kwargs)
563
- interpolators = [fold_interpolator, frame_interpolator,
788
+ interpolators = [fold_interpolator, gy_fold_interpolator,
564
789
  frame_interpolator.copy()]
565
790
  fold_frame_builder = StructuralFrameBuilder(
566
791
  interpolators=interpolators, name=fold_frame_data, **kwargs)
@@ -569,13 +794,18 @@ class GeologicalModel:
569
794
 
570
795
  ## add the data to the interpolator for the main foliation
571
796
  fold_frame_builder[0].add_data_to_interpolator(True)
797
+
572
798
  if "fold_axis" in kwargs:
799
+ logger.info("Using cylindrical fold axis")
573
800
  fold.fold_axis = kwargs['fold_axis']
574
801
  if "av_fold_axis" in kwargs:
802
+ logger.info("Using average intersection lineation for \n"
803
+ "fold axis")
575
804
  _calculate_average_intersection(fold_frame_builder[0], fold_frame,
576
805
  fold)
577
806
 
578
807
  if fold.fold_axis is None:
808
+ logger.info("Fitting fold axis rotation angle")
579
809
  far, fad = fold_frame.calculate_fold_axis_rotation(
580
810
  fold_frame_builder[0])
581
811
  fold_axis_rotation = FoldRotationAngle(far, fad)
@@ -600,12 +830,11 @@ class GeologicalModel:
600
830
  # fold_limb_fitter = kwargs.get("fold_limb_function",
601
831
  # _interpolate_fold_limb_rotation_angle)
602
832
  # fold_limb_fitter(series_builder, fold_frame, fold, result, **kwargs)
603
- kwargs['fold_weights'] = kwargs.get('fold_weights', None)
833
+ kwargs['fold_weights'] = kwargs.get('fold_weights', {})
604
834
 
605
835
  for i in range(3):
606
836
  self._add_faults(fold_frame_builder[i])
607
837
  # build feature
608
- kwargs['cgw'] = 0.
609
838
  kwargs['fold'] = fold
610
839
  self._add_faults(fold_frame_builder[0])
611
840
  self._add_faults(fold_frame_builder[1])
@@ -622,12 +851,14 @@ class GeologicalModel:
622
851
  return fold_frame
623
852
 
624
853
  def _add_faults(self, feature_builder, features=None):
625
- """
626
-
854
+ """Adds all existing faults to a geological feature builder
855
+
627
856
  Parameters
628
857
  ----------
629
- feature_builder
630
-
858
+ feature_builder : GeologicalFeatureInterpolator/StructuralFrameBuilder
859
+ The feature buider to add the faults to
860
+ features : list, optional
861
+ A specific list of features rather than all features in the model
631
862
  Returns
632
863
  -------
633
864
 
@@ -736,6 +967,8 @@ class GeologicalModel:
736
967
  Returns
737
968
  -------
738
969
  """
970
+ if not self.check_initialisation():
971
+ return False
739
972
  # self.parameters['features'].append({'feature_type':'unconformity','feature_name':unconformity_surface_data,**kwargs})
740
973
  interpolator = self.get_interpolator(**kwargs)
741
974
  unconformity_feature_builder = GeologicalFeatureInterpolator(
@@ -752,7 +985,9 @@ class GeologicalModel:
752
985
  self._add_faults(unconformity_feature_builder)
753
986
 
754
987
  # build feature
755
- uc_feature_base = unconformity_feature_builder.build(**kwargs)
988
+ # uc_feature_base = unconformity_feature_builder.build(**kwargs)
989
+ uc_feature_base = unconformity_feature_builder.feature
990
+ unconformity_feature_builder.build_arguments = kwargs
756
991
  uc_feature_base.type = 'unconformity_base'
757
992
  # uc_feature = UnconformityFeature(uc_feature_base,0)
758
993
  # iterate over existing features and add the unconformity as a
@@ -852,24 +1087,41 @@ class GeologicalModel:
852
1087
  self._add_faults(domain_fault_feature_builder)
853
1088
 
854
1089
  # build feature
855
- domain_fault = domain_fault_feature_builder.build(**kwargs)
1090
+ # domain_fault = domain_fault_feature_builder.build(**kwargs)
1091
+ domain_fault = domain_fault_feature_builder.feature
1092
+ domain_fault_feature_builder.build_arguments = kwargs
856
1093
  domain_fault.type = 'domain_fault'
857
1094
  self._add_feature(domain_fault)
858
1095
  self._add_domain_fault_below(domain_fault)
859
1096
 
860
- # uc_feature = UnconformityFeature(uc_feature_base,0)
1097
+ domain_fault_uc = UnconformityFeature(domain_fault,0)
861
1098
  # iterate over existing features and add the unconformity as a
862
1099
  # region so the feature is only
863
1100
  # evaluated where the unconformity is positive
864
- return domain_fault
865
-
866
- def create_and_add_fault(self, fault_surface_data, displacement, **kwargs):
1101
+ return domain_fault_uc
1102
+
1103
+ def create_and_add_fault(self,
1104
+ fault_surface_data,
1105
+ displacement,
1106
+ fault_slip_vector=None,
1107
+ fault_center = None,
1108
+ fault_extent = None,
1109
+ fault_influence = None,
1110
+ fault_vectical_radius = None,
1111
+ faultfunction=None,
1112
+ **kwargs):
867
1113
  """
868
1114
  Parameters
869
1115
  ----------
870
1116
  fault_surface_data : string
871
1117
  name of the fault surface data in the dataframe
872
1118
  displacement : displacement magnitude
1119
+ fault_extent : [type], optional
1120
+ [description], by default None
1121
+ fault_influence : [type], optional
1122
+ [description], by default None
1123
+ fault_vectical_radius : [type], optional
1124
+ [description], by default None
873
1125
  kwargs : additional kwargs for Fault and interpolators
874
1126
 
875
1127
  Returns
@@ -879,98 +1131,65 @@ class GeologicalModel:
879
1131
  """
880
1132
  self.parameters['features'].append(
881
1133
  {'feature_type': 'fault', 'feature_name': fault_surface_data, 'displacement': displacement, **kwargs})
882
-
1134
+ if 'data_region' in kwargs:
1135
+ kwargs.pop('data_region')
1136
+ logger.error("kwarg data_region currently not supported, disabling")
883
1137
  displacement_scaled = displacement / self.scale_factor
884
1138
  # create fault frame
885
1139
  interpolator = self.get_interpolator(**kwargs)
886
- fault_frame_builder = StructuralFrameBuilder(interpolator,
1140
+ fault_frame_builder = FaultBuilder(interpolator,
887
1141
  name=fault_surface_data,
888
1142
  **kwargs)
889
1143
  # add data
890
- fault_frame_data = self.data[
891
- self.data['feature_name'] == fault_surface_data].copy()
892
- if 'coord' not in fault_frame_data:
893
- fault_frame_data['coord'] = 0
894
- vals = fault_frame_data['val']
895
- if len(np.unique(vals[~np.isnan(vals)])) == 1:
896
- xyz = fault_frame_data[['X', 'Y', 'Z']].to_numpy()
897
- p1 = xyz[0, :] # fault_frame_data.loc[0 ,['X','Y']]
898
- p2 = xyz[-1, :] # fault_frame_data.loc[-1 ,['X','Y']]
899
- # get a vector that goes from p1-p2 and normalise
900
- vector = p1 - p2
901
- length = np.linalg.norm(vector)
902
- vector /= length
903
- # now create the orthogonal vector
904
- # newvector = np.zeros(3)
905
- length /= 3
906
- # length/=2
907
- # print(fault_frame_data)
908
- mask = ~np.isnan(fault_frame_data['nx'])
909
- vectors = fault_frame_data[mask][['nx', 'ny', 'nz']].to_numpy()
910
- lengths = np.linalg.norm(vectors, axis=1)
911
- vectors /= lengths[:, None]
912
- fault_frame_data.loc[mask, ['nx', 'ny', 'nz']] = vectors
913
- if 'strike' in fault_frame_data.columns and 'dip' in \
914
- fault_frame_data.columns:
915
- fault_frame_data = fault_frame_data.drop(['dip', 'strike'],
916
- axis=1)
917
- # print(fault_frame_data)
918
- # if there is no slip direction data assume vertical
919
- if fault_frame_data[fault_frame_data['coord'] == 1].shape[0] == 0:
920
- logger.info("Adding fault frame slip")
921
- loc = np.mean(fault_frame_data[['X', 'Y', 'Z']], axis=0)
922
- coord1 = pd.DataFrame([[loc[0], loc[1], loc[2], 0, 0, -1]],
923
- columns=normal_vector_headers())
924
- coord1['coord'] = 1
925
- fault_frame_data = pd.concat([fault_frame_data, coord1],
926
- sort=False)
927
-
928
- if fault_frame_data[fault_frame_data['coord'] == 2].shape[0] == 0:
929
- logger.info("Adding fault extent data as first and last point")
930
- ## first and last point of the line
931
- value_data = fault_frame_data[fault_frame_data['val'] == 0]
932
- coord2 = value_data.iloc[[0, len(value_data) - 1]]
933
- coord2 = coord2.reset_index(drop=True)
934
- c2_scale = kwargs.get('length_scale',1.)
935
- coord2.loc[0, 'val'] = -1/c2_scale
936
- coord2.loc[1, 'val'] = 1/c2_scale
937
- coord2['coord'] = 2
938
- fault_frame_data = pd.concat([fault_frame_data, coord2],
939
- sort=False)
940
- fault_frame_builder.add_data_from_data_frame(fault_frame_data)
941
- # if there is no fault slip data then we could find the strike of
942
- # the fault and build
943
- # the second coordinate
944
- # if we add a region to the fault then the fault operator doesn't
945
- # work but for visualisation
946
- # we want to add a region!
947
-
948
- if 'splayregion' in kwargs and 'splay' in kwargs:
949
- # result['splayregionfeature'] = RegionFeature(kwargs['splayregion'])
950
- # apply splay to all parts of fault frame
951
- for i in range(3):
952
- # work out the values of the nodes where we want hard
953
- # constraints
954
- idc = np.arange(0, interpolator.support.n_nodes)[
955
- kwargs['splayregion'](interpolator.support.nodes)]
956
- val = kwargs['splay'][i].evaluate_value(
957
- interpolator.support.nodes[
958
- kwargs['splayregion'](interpolator.support.nodes), :])
959
- mask = ~np.isnan(val)
960
- fault_frame_builder[i].interpolator.add_equality_constraints(
961
- idc[mask], val[mask])
1144
+ fault_frame_data = self.data[ self.data['feature_name'] == fault_surface_data].copy()
1145
+ mask = np.logical_and(fault_frame_data['coord']==0,~np.isnan(fault_frame_data['gz']))
1146
+ fault_normal_vector = fault_frame_data.loc[mask,['gx','gy','gz']].mean(axis=0).to_numpy()
1147
+ mask = np.logical_and(fault_frame_data['coord']==1,~np.isnan(fault_frame_data['gz']))
1148
+ if fault_slip_vector is None:
1149
+ fault_slip_vector = fault_frame_data.loc[mask,['gx','gy','gz']].mean(axis=0).to_numpy()
1150
+ if fault_center is not None:
1151
+ fault_center = self.scale(fault_center,inplace=False)
1152
+ if fault_center is None:
1153
+ # if we haven't defined a fault centre take the center of mass for lines assocaited with
1154
+ # the fault trace
1155
+ mask = np.logical_and(fault_frame_data['coord']==0,fault_frame_data['val']==0)
1156
+ fault_center = fault_frame_data.loc[mask,['X','Y','Z']].mean(axis=0).to_numpy()
1157
+ if fault_influence:
1158
+ fault_influence=fault_influence/self.scale_factor
1159
+ if fault_extent:
1160
+ fault_extent=fault_extent/self.scale_factor
1161
+ if fault_vectical_radius:
1162
+ fault_vectical_radius=fault_vectical_radius/self.scale_factor
1163
+ fault_frame_builder.create_data_from_geometry(fault_frame_data,
1164
+ fault_center,
1165
+ fault_normal_vector,
1166
+ fault_slip_vector,
1167
+ influence_distance=fault_influence,
1168
+ horizontal_radius=fault_extent,
1169
+ vertical_radius=fault_vectical_radius
1170
+ )
1171
+ if fault_influence == None or fault_extent == None or fault_vectical_radius == None:
1172
+ fault_frame_builder.origin = self.origin
1173
+ fault_frame_builder.maximum = self.maximum
1174
+ fault_frame_builder.set_mesh_geometry(kwargs.get('fault_buffer',0.1))
1175
+ # fault_frame_builder.add_data_from_data_frame(fault_frame_data)
962
1176
  # check if this fault overprint any existing faults exist in the stack
963
1177
  overprinted = kwargs.get('overprinted', [])
964
- self._add_faults(fault_frame_builder[0],overprinted)
965
- self._add_faults(fault_frame_builder[1],overprinted)
966
- self._add_faults(fault_frame_builder[2],overprinted)
1178
+ overprinted_faults = []
1179
+ for o in overprinted:
1180
+ overprinted_faults.append(self.features[self.feature_name_index[o]])
1181
+ self._add_faults(fault_frame_builder[0],overprinted_faults)
1182
+ self._add_faults(fault_frame_builder[1],overprinted_faults)
1183
+ self._add_faults(fault_frame_builder[2],overprinted_faults)
967
1184
 
968
1185
  fault_frame = fault_frame_builder.build(**kwargs)
969
1186
  if 'abut' in kwargs:
970
1187
  fault_frame[0].add_region(lambda pos: kwargs['abut'].evaluate(pos))
971
1188
 
972
1189
  fault = FaultSegment(fault_frame, displacement=displacement_scaled,
1190
+ faultfunction=faultfunction,
973
1191
  **kwargs)
1192
+ fault.builder=fault_frame_builder
974
1193
  for f in reversed(self.features):
975
1194
  if f.type == 'unconformity':
976
1195
  fault.add_region(lambda pos: f.evaluate_value(pos) <= 0)
@@ -982,50 +1201,54 @@ class GeologicalModel:
982
1201
 
983
1202
  return fault
984
1203
 
985
- def rescale(self, points):
1204
+ def rescale(self, points, inplace=True):
986
1205
  """
987
1206
  Convert from model scale to real world scale - in the future this
988
1207
  should also do transformations?
989
1208
 
990
1209
  Parameters
991
1210
  ----------
992
- points
1211
+ points : np.array((N,3),dtype=double)
1212
+ inplace : boolean
1213
+ whether to return a modified copy or modify the original array
993
1214
 
994
1215
  Returns
995
1216
  -------
1217
+ points : np.array((N,3),dtype=double)
1218
+
996
1219
  """
1220
+ if inplace == False:
1221
+ points = points.copy()
997
1222
  points *= self.scale_factor
998
1223
  points += self.origin
999
1224
  return points
1000
1225
 
1001
- def scale(self, points):
1002
- """
1226
+ def scale(self, points, inplace=True):
1227
+ """ Take points in UTM coordinates and reproject
1228
+ into scaled model space
1229
+
1003
1230
  Parameters
1004
1231
  ----------
1005
1232
  points : np.array((N,3),dtype=float)
1006
1233
  points to
1007
-
1234
+ inplace : bool, optional default = True
1235
+ whether to copy the points array or update the passed array
1008
1236
  Returns
1009
1237
  -------
1238
+ points : np.array((N,3),dtype=double)
1239
+
1010
1240
  """
1011
- points = points.copy()
1012
- points[:, :] -= self.origin
1241
+ points = np.array(points).astype(float)
1242
+ if inplace==False:
1243
+ points = points.copy()
1244
+ # if len(points.shape) == 1:
1245
+ # points = points[None,:]
1246
+ # if len(points.shape) != 2:
1247
+ # logger.error("cannot scale array of dimensions".format(len(points.shape)))
1248
+ points -= self.origin
1013
1249
  points /= self.scale_factor
1014
1250
  return points
1015
1251
 
1016
- def voxet(self, nsteps=(50, 50, 25)):
1017
- """
1018
- Returns a voxet dict with the nsteps specified
1019
-
1020
- Parameters
1021
- ----------
1022
- nsteps : tuple
1023
- number of cells in
1024
-
1025
- Returns
1026
- -------
1027
- """
1028
- return {'bounding_box': self.bounding_box, 'nsteps': nsteps}
1029
1252
 
1030
1253
  def regular_grid(self, nsteps=(50, 50, 25), shuffle = True, rescale=False):
1031
1254
  """
@@ -1050,12 +1273,13 @@ class GeologicalModel:
1050
1273
  xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
1051
1274
  locs = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
1052
1275
  if shuffle:
1276
+ logger.info("Shuffling points")
1053
1277
  np.random.shuffle(locs)
1054
1278
  if rescale:
1055
1279
  locs = self.rescale(locs)
1056
1280
  return locs
1057
1281
 
1058
- def evaluate_model(self, xyz, rescale=True):
1282
+ def evaluate_model(self, xyz, scale=True):
1059
1283
  """Evaluate the stratigraphic id at each location
1060
1284
 
1061
1285
 
@@ -1063,8 +1287,8 @@ class GeologicalModel:
1063
1287
  ----------
1064
1288
  xyz : np.array((N,3),dtype=float)
1065
1289
  locations
1066
- rescale : bool
1067
- whether to rescale the model
1290
+ scale : bool
1291
+ whether to rescale the xyz before evaluating model
1068
1292
 
1069
1293
  Returns
1070
1294
  -------
@@ -1075,11 +1299,11 @@ class GeologicalModel:
1075
1299
  --------
1076
1300
  Evaluate on a voxet
1077
1301
 
1078
- >>> x = np.linspace(self.bounding_box[0, 0], self.bounding_box[1, 0],
1302
+ >>> x = np.linspace(model.bounding_box[0, 0], model.bounding_box[1, 0],
1079
1303
  nsteps[0])
1080
- >>> y = np.linspace(self.bounding_box[0, 1], self.bounding_box[1, 1],
1304
+ >>> y = np.linspace(model.bounding_box[0, 1], model.bounding_box[1, 1],
1081
1305
  nsteps[1])
1082
- >>> z = np.linspace(self.bounding_box[1, 2], self.bounding_box[0, 2],
1306
+ >>> z = np.linspace(model.bounding_box[1, 2], model.bounding_box[0, 2],
1083
1307
  nsteps[2])
1084
1308
  >>> xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
1085
1309
  >>> xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
@@ -1102,8 +1326,13 @@ class GeologicalModel:
1102
1326
  >>> model.evaluate_model(xyz)
1103
1327
 
1104
1328
  """
1329
+ xyz = np.array(xyz)
1330
+ if scale:
1331
+ xyz = self.scale(xyz,inplace=False)
1105
1332
  strat_id = np.zeros(xyz.shape[0],dtype=int)
1106
1333
  for group in self.stratigraphic_column.keys():
1334
+ if group == 'faults':
1335
+ continue
1107
1336
  feature_id = self.feature_name_index.get(group, -1)
1108
1337
  if feature_id >= 0:
1109
1338
  feature = self.features[feature_id]
@@ -1114,6 +1343,31 @@ class GeologicalModel:
1114
1343
  logger.error('Model does not contain {}'.format(group))
1115
1344
  return strat_id
1116
1345
 
1346
+ def evaluate_fault_displacements(self,points,scale=True):
1347
+ """Evaluate the fault displacement magnitude at each location
1348
+
1349
+
1350
+ Parameters
1351
+ ----------
1352
+ xyz : np.array((N,3),dtype=float)
1353
+ locations
1354
+ scale : bool
1355
+ whether to rescale the xyz before evaluating model
1356
+
1357
+ Returns
1358
+ -------
1359
+ fault_displacement : np.array(N,dtype=float)
1360
+ the fault displacement magnitude
1361
+ """
1362
+ if scale:
1363
+ points = self.scale(points,inplace=False)
1364
+ vals = np.zeros(points.shape[0])
1365
+ for f in self.features:
1366
+ if f.type == 'fault':
1367
+ disp = f.displacementfeature.evaluate_value(points)
1368
+ vals[~np.isnan(disp)] += disp[~np.isnan(disp)]
1369
+ return vals*-self.scale_factor # convert from restoration magnutude to displacement
1370
+
1117
1371
  def get_feature_by_name(self, feature_name):
1118
1372
  """Returns a feature from the mode given a name
1119
1373
 
@@ -1127,11 +1381,15 @@ class GeologicalModel:
1127
1381
  -------
1128
1382
  feature : GeologicalFeature
1129
1383
  the geological feature with the specified name, or none if no feature
1384
+
1385
+
1386
+
1130
1387
  """
1131
1388
  feature_index = self.feature_name_index.get(feature_name,-1)
1132
- if feature_index >0:
1389
+ if feature_index > -1:
1133
1390
  return self.features[feature_index]
1134
1391
  else:
1392
+ logger.error("{} does not exist!".format(feature_name))
1135
1393
  return None
1136
1394
 
1137
1395
  def evaluate_feature_value(self, feature_name, xyz, scale=True):
@@ -1151,11 +1409,31 @@ class GeologicalModel:
1151
1409
  -------
1152
1410
  np.array((N))
1153
1411
  vector of scalar values
1412
+
1413
+ Examples
1414
+ --------
1415
+ Evaluate on a voxet using model boundaries
1416
+
1417
+ >>> x = np.linspace(model.bounding_box[0, 0], model.bounding_box[1, 0],
1418
+ nsteps[0])
1419
+ >>> y = np.linspace(model.bounding_box[0, 1], model.bounding_box[1, 1],
1420
+ nsteps[1])
1421
+ >>> z = np.linspace(model.bounding_box[1, 2], model.bounding_box[0, 2],
1422
+ nsteps[2])
1423
+ >>> xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
1424
+ >>> xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
1425
+ >>> model.evaluate_feature_vaue('feature',xyz,scale=False)
1426
+
1427
+ Evaluate on points in UTM coordinates
1428
+
1429
+ >>> model.evaluate_feature_vaue('feature',utm_xyz)
1430
+
1154
1431
  """
1155
1432
  feature = self.get_feature_by_name(feature_name)
1156
1433
  if feature:
1434
+ scaled_xyz = xyz
1157
1435
  if scale:
1158
- scaled_xyz = self.scale(xyz)
1436
+ scaled_xyz = self.scale(xyz,inplace=False)
1159
1437
  return feature.evaluate_value(scaled_xyz)
1160
1438
  else:
1161
1439
  return np.zeros(xyz.shape[0])
@@ -1179,8 +1457,48 @@ class GeologicalModel:
1179
1457
  """
1180
1458
  feature = self.get_feature_by_name(feature_name)
1181
1459
  if feature:
1460
+ scaled_xyz = xyz
1182
1461
  if scale:
1183
- scaled_xyz = self.scale(xyz)
1462
+ scaled_xyz = self.scale(xyz, inplace = False)
1184
1463
  return feature.evaluate_gradient(scaled_xyz)
1185
1464
  else:
1186
- return np.zeros(xyz.shape[0])
1465
+ return np.zeros(xyz.shape[0])
1466
+
1467
+ def update(self,verbose=False,progressbar=True):
1468
+ total_dof = 0
1469
+ nfeatures = 0
1470
+ for f in self.features:
1471
+ if f.type=='fault':
1472
+ nfeatures+=3
1473
+ total_dof+=f[0].interpolator.nx*3
1474
+ if f.type == 'series':
1475
+ nfeatures+=1
1476
+ total_dof+=f.interpolator.nx
1477
+ if verbose==True:
1478
+ print('Updating geological model. There are: \n'
1479
+ '{} geological features that need to be interpolated\n'.format(nfeatures)
1480
+ )
1481
+
1482
+ from tqdm.auto import tqdm
1483
+ import time
1484
+ start = time.time()
1485
+ sizecounter = 0
1486
+
1487
+ # Load tqdm with size counter instead of file counter
1488
+ with tqdm(total=nfeatures) as pbar:
1489
+ buf=0
1490
+ for f in self.features:
1491
+ pbar.set_description('Interpolating {}'.format(f.name))
1492
+ if f.type == 'fault':
1493
+ for i in range(3):
1494
+ buf+=1
1495
+ f[i].builder.update()
1496
+ pbar.update()
1497
+ if f.type == 'series':
1498
+ f.builder.update()
1499
+ pbar.update()
1500
+
1501
+
1502
+ if verbose:
1503
+ print("Model update took: {} seconds".format(time.time()-start))
1504
+