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.
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/__init__.py +33 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/__init__.py +12 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/__pycache__/_base.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/_base.py +65 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/claudius.csv +21049 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/claudiusbb.txt +2 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/duplex.csv +126 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/duplexbb.txt +2 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/intrusion.csv +1017 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/intrusionbb.txt +2 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/onefoldbb.txt +2 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/onefolddata.csv +2226 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/refolded_bb.txt +2 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/datasets/data/refolded_fold.csv +2126 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__init__.py +31 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/discrete_fold_interpolator.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/discrete_interpolator.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/finite_difference_interpolator.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/geological_interpolator.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/operator.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/piecewiselinear_interpolator.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/structured_grid.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/structured_tetra.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/__pycache__/surfe_wrapper.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/__init__.py +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/dsi_helper.c +27782 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/cython/dsi_helper.cp37-win_amd64.pyd +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/discrete_fold_interpolator.py +171 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/discrete_interpolator.py +551 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/finite_difference_interpolator.py +342 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/geological_interpolator.py +190 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/operator.py +60 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/piecewiselinear_interpolator.py +348 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/structured_grid.py +466 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/structured_tetra.py +638 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/interpolators/surfe_wrapper.py +117 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/__init__.py +46 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__init__.py +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/__pycache__/geological_model.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/core/geological_model.py +1351 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__init__.py +3 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_function.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_function_feature.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/__pycache__/fault_segment.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_function.py +187 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_function_feature.py +75 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fault/fault_segment.py +270 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__init__.py +7 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/cross_product_geological_feature.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/geological_feature.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/geological_feature_builder.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/region_feature.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/structural_frame.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/structural_frame_builder.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/__pycache__/unconformity_feature.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/cross_product_geological_feature.py +77 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/geological_feature.py +286 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/geological_feature_builder.py +329 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/region_feature.py +34 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/structural_frame.py +116 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/structural_frame_builder.py +179 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/features/unconformity_feature.py +69 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__init__.py +8 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/fold.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/fold_rotation_angle.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/fold_rotation_angle_feature.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/foldframe.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/__pycache__/svariogram.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/fold.py +135 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/fold_rotation_angle.py +132 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/fold_rotation_angle_feature.py +57 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/foldframe.py +192 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/modelling/fold/svariogram.py +179 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__init__.py +14 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/exceptions.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/helper.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/map2loop.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/__pycache__/utils.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/exceptions.py +9 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/helper.py +378 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/map2loop.py +314 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/utils/utils.py +120 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__init__.py +19 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/map_viewer.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/model_plotter.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/model_visualisation.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/rotation_angle_plotter.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/__pycache__/sphinx_scraper.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/map_viewer.py +307 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/model_plotter.py +16 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/model_visualisation.py +1012 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/rotation_angle_plotter.py +82 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural/visualisation/sphinx_scraper.py +34 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/PKG-INFO +10 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/SOURCES.txt +60 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/dependency_links.txt +1 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/requires.txt +8 -0
- Miniconda/envs/loop/Lib/site-packages/LoopStructural-1.0.4-py3.7.egg-info/top_level.txt +2 -0
- Miniconda/envs/loop/Lib/site-packages/tests/__init__.py +0 -0
- Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/__init__.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_faults.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_fold.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_interpolator.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/tests/__pycache__/test_refolded.cpython-37.pyc +0 -0
- Miniconda/envs/loop/Lib/site-packages/tests/test_faults.py +17 -0
- Miniconda/envs/loop/Lib/site-packages/tests/test_fold.py +57 -0
- Miniconda/envs/loop/Lib/site-packages/tests/test_interpolator.py +88 -0
- 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])
|