LoopStructural 1.6.1__py3-none-any.whl
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.
- LoopStructural/__init__.py +52 -0
- LoopStructural/datasets/__init__.py +23 -0
- LoopStructural/datasets/_base.py +301 -0
- LoopStructural/datasets/_example_models.py +10 -0
- LoopStructural/datasets/data/claudius.csv +21049 -0
- LoopStructural/datasets/data/claudiusbb.txt +2 -0
- LoopStructural/datasets/data/duplex.csv +126 -0
- LoopStructural/datasets/data/duplexbb.txt +2 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
- LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
- LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
- LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
- LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
- LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
- LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
- LoopStructural/datasets/data/intrusion.csv +1017 -0
- LoopStructural/datasets/data/intrusionbb.txt +2 -0
- LoopStructural/datasets/data/onefoldbb.txt +2 -0
- LoopStructural/datasets/data/onefolddata.csv +2226 -0
- LoopStructural/datasets/data/refolded_bb.txt +2 -0
- LoopStructural/datasets/data/refolded_fold.csv +205 -0
- LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
- LoopStructural/datatypes/__init__.py +4 -0
- LoopStructural/datatypes/_bounding_box.py +422 -0
- LoopStructural/datatypes/_point.py +166 -0
- LoopStructural/datatypes/_structured_grid.py +94 -0
- LoopStructural/datatypes/_surface.py +184 -0
- LoopStructural/export/exporters.py +554 -0
- LoopStructural/export/file_formats.py +15 -0
- LoopStructural/export/geoh5.py +100 -0
- LoopStructural/export/gocad.py +126 -0
- LoopStructural/export/omf_wrapper.py +88 -0
- LoopStructural/interpolators/__init__.py +105 -0
- LoopStructural/interpolators/_api.py +143 -0
- LoopStructural/interpolators/_builders.py +149 -0
- LoopStructural/interpolators/_cython/__init__.py +0 -0
- LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
- LoopStructural/interpolators/_discrete_interpolator.py +692 -0
- LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
- LoopStructural/interpolators/_geological_interpolator.py +380 -0
- LoopStructural/interpolators/_interpolator_factory.py +89 -0
- LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
- LoopStructural/interpolators/_operator.py +38 -0
- LoopStructural/interpolators/_p1interpolator.py +228 -0
- LoopStructural/interpolators/_p2interpolator.py +277 -0
- LoopStructural/interpolators/_surfe_wrapper.py +174 -0
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
- LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
- LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
- LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
- LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
- LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
- LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
- LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
- LoopStructural/interpolators/supports/__init__.py +55 -0
- LoopStructural/interpolators/supports/_aabb.py +77 -0
- LoopStructural/interpolators/supports/_base_support.py +114 -0
- LoopStructural/interpolators/supports/_face_table.py +70 -0
- LoopStructural/interpolators/supports/_support_factory.py +32 -0
- LoopStructural/modelling/__init__.py +29 -0
- LoopStructural/modelling/core/__init__.py +0 -0
- LoopStructural/modelling/core/geological_model.py +1867 -0
- LoopStructural/modelling/features/__init__.py +32 -0
- LoopStructural/modelling/features/_analytical_feature.py +79 -0
- LoopStructural/modelling/features/_base_geological_feature.py +364 -0
- LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
- LoopStructural/modelling/features/_geological_feature.py +288 -0
- LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
- LoopStructural/modelling/features/_region.py +18 -0
- LoopStructural/modelling/features/_structural_frame.py +186 -0
- LoopStructural/modelling/features/_unconformity_feature.py +83 -0
- LoopStructural/modelling/features/builders/__init__.py +5 -0
- LoopStructural/modelling/features/builders/_base_builder.py +111 -0
- LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
- LoopStructural/modelling/features/fault/__init__.py +3 -0
- LoopStructural/modelling/features/fault/_fault_function.py +444 -0
- LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
- LoopStructural/modelling/features/fold/__init__.py +9 -0
- LoopStructural/modelling/features/fold/_fold.py +167 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
- LoopStructural/modelling/features/fold/_foldframe.py +194 -0
- LoopStructural/modelling/features/fold/_svariogram.py +188 -0
- LoopStructural/modelling/input/__init__.py +2 -0
- LoopStructural/modelling/input/fault_network.py +80 -0
- LoopStructural/modelling/input/map2loop_processor.py +165 -0
- LoopStructural/modelling/input/process_data.py +650 -0
- LoopStructural/modelling/input/project_file.py +84 -0
- LoopStructural/modelling/intrusions/__init__.py +25 -0
- LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
- LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
- LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
- LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
- LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
- LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
- LoopStructural/utils/__init__.py +38 -0
- LoopStructural/utils/_surface.py +143 -0
- LoopStructural/utils/_transformation.py +76 -0
- LoopStructural/utils/config.py +18 -0
- LoopStructural/utils/dtm_creator.py +17 -0
- LoopStructural/utils/exceptions.py +31 -0
- LoopStructural/utils/helper.py +292 -0
- LoopStructural/utils/json_encoder.py +18 -0
- LoopStructural/utils/linalg.py +8 -0
- LoopStructural/utils/logging.py +79 -0
- LoopStructural/utils/maths.py +245 -0
- LoopStructural/utils/regions.py +103 -0
- LoopStructural/utils/typing.py +7 -0
- LoopStructural/utils/utils.py +68 -0
- LoopStructural/version.py +1 -0
- LoopStructural/visualisation/__init__.py +11 -0
- LoopStructural-1.6.1.dist-info/LICENSE +21 -0
- LoopStructural-1.6.1.dist-info/METADATA +81 -0
- LoopStructural-1.6.1.dist-info/RECORD +129 -0
- LoopStructural-1.6.1.dist-info/WHEEL +5 -0
- LoopStructural-1.6.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from ....modelling.features.builders import GeologicalFeatureBuilder
|
|
2
|
+
from ....modelling.features.fold import FoldRotationAngle
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from ....utils import getLogger, InterpolatorError
|
|
6
|
+
from ....datatypes import BoundingBox
|
|
7
|
+
|
|
8
|
+
logger = getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
interpolatortype: str,
|
|
15
|
+
bounding_box: BoundingBox,
|
|
16
|
+
fold,
|
|
17
|
+
nelements: int = 1000,
|
|
18
|
+
fold_weights={},
|
|
19
|
+
name="Feature",
|
|
20
|
+
region=None,
|
|
21
|
+
svario=True,
|
|
22
|
+
**kwargs,
|
|
23
|
+
):
|
|
24
|
+
"""Builder for creating a geological feature using fold constraints
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
interpolator : GeologicalInterpolator
|
|
29
|
+
the interpolator to add the fold constraints to
|
|
30
|
+
fold : FoldEvent
|
|
31
|
+
a fold event object that contains the geometry of the fold
|
|
32
|
+
fold_weights : dict, optional
|
|
33
|
+
interpolation weights for the fold, by default {}
|
|
34
|
+
name : str, optional
|
|
35
|
+
name of the geological feature, by default "Feature"
|
|
36
|
+
region : _type_, optional
|
|
37
|
+
_description_, by default None
|
|
38
|
+
"""
|
|
39
|
+
GeologicalFeatureBuilder.__init__(
|
|
40
|
+
self,
|
|
41
|
+
interpolatortype=interpolatortype,
|
|
42
|
+
bounding_box=bounding_box,
|
|
43
|
+
nelements=nelements,
|
|
44
|
+
name=name,
|
|
45
|
+
region=region,
|
|
46
|
+
**kwargs,
|
|
47
|
+
)
|
|
48
|
+
self.interpolator.fold = fold
|
|
49
|
+
self.fold = fold
|
|
50
|
+
self.fold_weights = fold_weights
|
|
51
|
+
self.kwargs = kwargs
|
|
52
|
+
self.svario = svario
|
|
53
|
+
|
|
54
|
+
def set_fold_axis(self):
|
|
55
|
+
"""calculates the fold axis/ fold axis rotation and adds this to the fold"""
|
|
56
|
+
kwargs = self.kwargs
|
|
57
|
+
fold_axis = kwargs.get("fold_axis", None)
|
|
58
|
+
if fold_axis is not None:
|
|
59
|
+
fold_axis = np.array(fold_axis)
|
|
60
|
+
if len(fold_axis.shape) == 1:
|
|
61
|
+
self.fold.fold_axis = fold_axis
|
|
62
|
+
|
|
63
|
+
if "av_fold_axis" in kwargs:
|
|
64
|
+
l2 = self.fold.foldframe.calculate_intersection_lineation(self)
|
|
65
|
+
self.fold.fold_axis = np.mean(l2, axis=0)
|
|
66
|
+
if self.fold.fold_axis is None:
|
|
67
|
+
if not self.fold.foldframe[1].is_valid():
|
|
68
|
+
raise InterpolatorError("Fold frame direction coordinate is not valid")
|
|
69
|
+
far, fad = self.fold.foldframe.calculate_fold_axis_rotation(self)
|
|
70
|
+
fold_axis_rotation = FoldRotationAngle(far, fad, svario=self.svario)
|
|
71
|
+
a_wl = kwargs.get("axis_wl", None)
|
|
72
|
+
if "axis_function" in kwargs:
|
|
73
|
+
# allow predefined function to be used
|
|
74
|
+
fold_axis_rotation.set_function(kwargs["axis_function"])
|
|
75
|
+
else:
|
|
76
|
+
fold_axis_rotation.fit_fourier_series(wl=a_wl)
|
|
77
|
+
self.fold.fold_axis_rotation = fold_axis_rotation
|
|
78
|
+
|
|
79
|
+
def set_fold_limb_rotation(self):
|
|
80
|
+
"""Calculates the limb rotation of the fold and adds it to the fold object"""
|
|
81
|
+
kwargs = self.kwargs
|
|
82
|
+
# give option of passing own fold limb rotation function
|
|
83
|
+
flr, fld = self.fold.foldframe.calculate_fold_limb_rotation(
|
|
84
|
+
self, self.fold.get_fold_axis_orientation
|
|
85
|
+
)
|
|
86
|
+
fold_limb_rotation = FoldRotationAngle(flr, fld, svario=self.svario)
|
|
87
|
+
l_wl = kwargs.get("limb_wl", None)
|
|
88
|
+
if "limb_function" in kwargs:
|
|
89
|
+
# allow for predefined functions to be used
|
|
90
|
+
fold_limb_rotation.set_function(kwargs["limb_function"])
|
|
91
|
+
else:
|
|
92
|
+
fold_limb_rotation.fit_fourier_series(wl=l_wl, **kwargs)
|
|
93
|
+
self.fold.fold_limb_rotation = fold_limb_rotation
|
|
94
|
+
|
|
95
|
+
def build(self, data_region=None, constrained=None, **kwargs):
|
|
96
|
+
"""the main function to run the interpolation and set up the parameters
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
data_region : [type], optional
|
|
101
|
+
[description], by default None
|
|
102
|
+
"""
|
|
103
|
+
# add the data to the interpolator and force constraints to be
|
|
104
|
+
# gradient not norm, to prevent issues with fold norm constraint
|
|
105
|
+
# TODO folding norm constraint should be minimising the difference in norm
|
|
106
|
+
# not setting the norm
|
|
107
|
+
|
|
108
|
+
# Use norm constraints if the fold normalisation weight is 0.
|
|
109
|
+
if constrained is None:
|
|
110
|
+
if "fold_normalisation" in kwargs:
|
|
111
|
+
if kwargs["fold_normalisation"] == 0.0:
|
|
112
|
+
constrained = False
|
|
113
|
+
else:
|
|
114
|
+
constrained = True
|
|
115
|
+
self.add_data_to_interpolator(constrained=constrained)
|
|
116
|
+
if not self.fold.foldframe[0].is_valid():
|
|
117
|
+
raise InterpolatorError("Fold frame main coordinate is not valid")
|
|
118
|
+
self.set_fold_axis()
|
|
119
|
+
self.set_fold_limb_rotation()
|
|
120
|
+
logger.info("Adding fold to {}".format(self.name))
|
|
121
|
+
self.interpolator.fold = self.fold
|
|
122
|
+
# if we have fold weights use those, otherwise just use default
|
|
123
|
+
# self.interpolator.add_fold_constraints(**self.fold_weights)
|
|
124
|
+
kwargs["fold_weights"] = self.fold_weights
|
|
125
|
+
if "cgw" not in kwargs:
|
|
126
|
+
# try adding very small cg
|
|
127
|
+
kwargs["cgw"] = 0.0
|
|
128
|
+
# now the fold is set up run the standard interpolation
|
|
129
|
+
super().build(self, data_region=data_region, **kwargs)
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Feature builder
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from ....utils import getLogger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from ....interpolators import GeologicalInterpolator
|
|
12
|
+
from ....utils.helper import (
|
|
13
|
+
xyz_names,
|
|
14
|
+
val_name,
|
|
15
|
+
normal_vec_names,
|
|
16
|
+
weight_name,
|
|
17
|
+
gradient_vec_names,
|
|
18
|
+
tangent_vec_names,
|
|
19
|
+
interface_name,
|
|
20
|
+
inequality_name,
|
|
21
|
+
pairs_name,
|
|
22
|
+
)
|
|
23
|
+
from ....modelling.features import GeologicalFeature
|
|
24
|
+
from ....modelling.features.builders import BaseBuilder
|
|
25
|
+
from ....utils.helper import (
|
|
26
|
+
get_data_bounding_box_map as get_data_bounding_box,
|
|
27
|
+
)
|
|
28
|
+
from ....utils import RegionEverywhere
|
|
29
|
+
from ....interpolators import DiscreteInterpolator
|
|
30
|
+
from ....interpolators import InterpolatorFactory
|
|
31
|
+
|
|
32
|
+
logger = getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class GeologicalFeatureBuilder(BaseBuilder):
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
interpolatortype: str,
|
|
39
|
+
bounding_box,
|
|
40
|
+
nelements: int = 1000,
|
|
41
|
+
name="Feature",
|
|
42
|
+
interpolation_region=None,
|
|
43
|
+
model=None,
|
|
44
|
+
**kwargs,
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Constructor for a GeologicalFeatureBuilder
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
interpolator : GeologicalInterpolator
|
|
52
|
+
An empty GeologicalInterpolator
|
|
53
|
+
region : lambda function
|
|
54
|
+
defining whether the location (xyz) should be included in the
|
|
55
|
+
kwargs - name of the feature, region to interpolate the feature
|
|
56
|
+
"""
|
|
57
|
+
BaseBuilder.__init__(self, model, name)
|
|
58
|
+
interpolator = InterpolatorFactory.create_interpolator(
|
|
59
|
+
interpolatortype=interpolatortype,
|
|
60
|
+
boundingbox=bounding_box,
|
|
61
|
+
nelements=nelements,
|
|
62
|
+
buffer=kwargs.get("buffer", 0.2),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not issubclass(type(interpolator), GeologicalInterpolator):
|
|
66
|
+
raise TypeError(
|
|
67
|
+
"interpolator is {} and must be a GeologicalInterpolator".format(type(interpolator))
|
|
68
|
+
)
|
|
69
|
+
self._interpolator = interpolator
|
|
70
|
+
|
|
71
|
+
header = (
|
|
72
|
+
xyz_names()
|
|
73
|
+
+ val_name()
|
|
74
|
+
+ gradient_vec_names()
|
|
75
|
+
+ normal_vec_names()
|
|
76
|
+
+ tangent_vec_names()
|
|
77
|
+
+ weight_name()
|
|
78
|
+
)
|
|
79
|
+
self.data = pd.DataFrame(columns=header)
|
|
80
|
+
self.data_added = False
|
|
81
|
+
self._interpolation_region = None
|
|
82
|
+
self.interpolation_region = interpolation_region
|
|
83
|
+
if self.interpolation_region is not None:
|
|
84
|
+
self._interpolator.set_region(region=self.interpolation_region)
|
|
85
|
+
|
|
86
|
+
self._feature = GeologicalFeature(
|
|
87
|
+
self._name,
|
|
88
|
+
self._interpolator,
|
|
89
|
+
builder=self,
|
|
90
|
+
regions=[],
|
|
91
|
+
faults=self.faults,
|
|
92
|
+
)
|
|
93
|
+
self._orthogonal_features = {}
|
|
94
|
+
self._equality_constraints = {}
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def interpolator(self):
|
|
98
|
+
return self._interpolator
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def interpolation_region(self):
|
|
102
|
+
return self._interpolation_region
|
|
103
|
+
|
|
104
|
+
@interpolation_region.setter
|
|
105
|
+
def interpolation_region(self, interpolation_region):
|
|
106
|
+
if interpolation_region is not None:
|
|
107
|
+
self._interpolation_region = interpolation_region
|
|
108
|
+
self._interpolator.set_region(region=self._interpolation_region)
|
|
109
|
+
else:
|
|
110
|
+
self._interpolation_region = RegionEverywhere()
|
|
111
|
+
self._interpolator.set_region(region=self._interpolation_region)
|
|
112
|
+
self._up_to_date = False
|
|
113
|
+
|
|
114
|
+
def add_data_from_data_frame(self, data_frame, overwrite=False):
|
|
115
|
+
"""
|
|
116
|
+
Extract data from a pandas dataframe with columns for
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
data_frame : pd.DataFrame
|
|
121
|
+
a dataframe containing the data to be added
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
self.data = data_frame.copy()
|
|
128
|
+
|
|
129
|
+
def add_orthogonal_feature(self, feature, w=1.0, region=None, step=1, B=0):
|
|
130
|
+
"""
|
|
131
|
+
Add a constraint to the interpolator so that the gradient of an exisitng
|
|
132
|
+
feature is orthogonal to the feature being built. E.g. dot product
|
|
133
|
+
between gradients should be = 0
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
feature : GeologicalFeature
|
|
138
|
+
feature which we want to be orthogonal to
|
|
139
|
+
w : double
|
|
140
|
+
how much to weight in least squares sense
|
|
141
|
+
region : unused
|
|
142
|
+
step : int
|
|
143
|
+
numpy slicing step size to see how many tetras to add
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
|
|
148
|
+
Notes
|
|
149
|
+
-----
|
|
150
|
+
The constraint can be applied to a random subset of the tetrahedral
|
|
151
|
+
elements in the mesh
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
step = int(step) # cast as int in case it was a float
|
|
155
|
+
except ValueError:
|
|
156
|
+
logger.error("Cannot cast {} as integer, setting step to 1".format(step))
|
|
157
|
+
step = 1
|
|
158
|
+
self._orthogonal_features[feature.name] = [feature, w, region, step, B]
|
|
159
|
+
self._up_to_date = False
|
|
160
|
+
|
|
161
|
+
def add_data_to_interpolator(self, constrained=False, force_constrained=False, **kwargs):
|
|
162
|
+
"""
|
|
163
|
+
Iterates through the list of data and applies any faults active on the
|
|
164
|
+
data in the order they are added
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
-----------
|
|
168
|
+
constrained : boolean
|
|
169
|
+
force_constrained : boolean
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
if self.data_added:
|
|
176
|
+
return
|
|
177
|
+
# first move the data for the fault
|
|
178
|
+
logger.info(f"Adding {len(self.faults)} faults to {self.name}")
|
|
179
|
+
data = self.data.copy()
|
|
180
|
+
|
|
181
|
+
# convert data locations to numpy array and then update
|
|
182
|
+
# for gradient data we need to calculate the rotation of the vector field
|
|
183
|
+
# create a tetrahedron at each point, and assign the corner values as the values that would
|
|
184
|
+
# fit the vector for the gradient. Then apply the fault to these points and then recalculate
|
|
185
|
+
# the gradient using the tetrahedron.
|
|
186
|
+
|
|
187
|
+
mask_gradient = np.all(~np.isnan(data.loc[:, gradient_vec_names()].to_numpy(float)), axis=1)
|
|
188
|
+
mask_normal = np.all(~np.isnan(data.loc[:, normal_vec_names()].to_numpy(float)), axis=1)
|
|
189
|
+
mask_tangent = np.all(~np.isnan(data.loc[:, tangent_vec_names()].to_numpy(float)), axis=1)
|
|
190
|
+
mask_vector = mask_gradient | mask_normal | mask_tangent
|
|
191
|
+
if np.sum(mask_vector) > 0:
|
|
192
|
+
for f in self.faults:
|
|
193
|
+
if np.sum(mask_normal) > 0:
|
|
194
|
+
data.loc[mask_normal, normal_vec_names()] = f.apply_to_vectors(
|
|
195
|
+
data.loc[mask_normal, xyz_names() + normal_vec_names()].to_numpy(float)
|
|
196
|
+
)
|
|
197
|
+
if np.sum(mask_gradient) > 0:
|
|
198
|
+
data.loc[mask_gradient, gradient_vec_names()] = f.apply_to_vectors(
|
|
199
|
+
data.loc[mask_gradient, xyz_names() + gradient_vec_names()].to_numpy(float)
|
|
200
|
+
)
|
|
201
|
+
if np.sum(mask_tangent) > 0:
|
|
202
|
+
data.loc[mask_tangent, tangent_vec_names()] = f.apply_to_vectors(
|
|
203
|
+
data.loc[mask_tangent, xyz_names() + tangent_vec_names()].to_numpy(float)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
for f in self.faults:
|
|
207
|
+
data.loc[:, xyz_names()] = f.apply_to_points(data.loc[:, xyz_names()])
|
|
208
|
+
|
|
209
|
+
# self.check_interpolation_geometry(data.loc[:,xyz_names()].to_numpy())
|
|
210
|
+
# Now check whether there are enough constraints for the
|
|
211
|
+
# interpolator to be able to solve
|
|
212
|
+
# we need at least 2 different value points or a single norm
|
|
213
|
+
# constraint. If there are not enough
|
|
214
|
+
# try converting grad to norms, if still not enough send user an error
|
|
215
|
+
if constrained:
|
|
216
|
+
# Change normals to gradients
|
|
217
|
+
mask = np.all(~np.isnan(data.loc[:, normal_vec_names()]), axis=1)
|
|
218
|
+
if mask.sum() > 0:
|
|
219
|
+
data.loc[mask, gradient_vec_names()] = data.loc[mask, normal_vec_names()].to_numpy(
|
|
220
|
+
float
|
|
221
|
+
)
|
|
222
|
+
data.loc[mask, normal_vec_names()] = np.nan
|
|
223
|
+
if self.get_norm_constraints().shape[0] > 0:
|
|
224
|
+
constrained = True
|
|
225
|
+
|
|
226
|
+
if np.unique(self.get_value_constraints()[:, 3]).shape[0] > 1:
|
|
227
|
+
constrained = True
|
|
228
|
+
|
|
229
|
+
if not constrained or force_constrained:
|
|
230
|
+
# change gradient constraints to normal vector constraints
|
|
231
|
+
mask = np.all(~np.isnan(data.loc[:, gradient_vec_names()]), axis=1)
|
|
232
|
+
if mask.sum() > 0:
|
|
233
|
+
data.loc[mask, normal_vec_names()] = data.loc[mask, gradient_vec_names()].to_numpy(
|
|
234
|
+
float
|
|
235
|
+
)
|
|
236
|
+
data.loc[mask, gradient_vec_names()] = np.nan
|
|
237
|
+
logger.info("Setting gradient points to norm constraints")
|
|
238
|
+
constrained = True
|
|
239
|
+
mask = np.all(~np.isnan(data.loc[:, normal_vec_names()].to_numpy(float)), axis=1)
|
|
240
|
+
|
|
241
|
+
if not constrained:
|
|
242
|
+
logger.error("Not enough constraints for scalar field add more")
|
|
243
|
+
# self.interpolator.reset()
|
|
244
|
+
mask = ~np.isnan(data.loc[:, val_name()].to_numpy(float))
|
|
245
|
+
# add value constraints
|
|
246
|
+
if mask.sum() > 0:
|
|
247
|
+
value_data = data.loc[mask[:, 0], xyz_names() + val_name() + weight_name()].to_numpy(
|
|
248
|
+
float
|
|
249
|
+
)
|
|
250
|
+
self.interpolator.set_value_constraints(value_data)
|
|
251
|
+
|
|
252
|
+
# add gradient constraints
|
|
253
|
+
mask = np.all(~np.isnan(data.loc[:, gradient_vec_names()].to_numpy(float)), axis=1)
|
|
254
|
+
if mask.sum() > 0:
|
|
255
|
+
gradient_data = data.loc[
|
|
256
|
+
mask, xyz_names() + gradient_vec_names() + weight_name()
|
|
257
|
+
].to_numpy(float)
|
|
258
|
+
self.interpolator.set_gradient_constraints(gradient_data)
|
|
259
|
+
|
|
260
|
+
# add normal vector data
|
|
261
|
+
mask = np.all(~np.isnan(data.loc[:, normal_vec_names()].to_numpy(float)), axis=1)
|
|
262
|
+
if mask.sum() > 0:
|
|
263
|
+
normal_data = data.loc[mask, xyz_names() + normal_vec_names() + weight_name()].to_numpy(
|
|
264
|
+
float
|
|
265
|
+
)
|
|
266
|
+
self.interpolator.set_normal_constraints(normal_data)
|
|
267
|
+
|
|
268
|
+
# add tangent data
|
|
269
|
+
mask = np.all(~np.isnan(data.loc[:, tangent_vec_names()].to_numpy(float)), axis=1)
|
|
270
|
+
if mask.sum() > 0:
|
|
271
|
+
tangent_data = data.loc[
|
|
272
|
+
mask, xyz_names() + tangent_vec_names() + weight_name()
|
|
273
|
+
].to_numpy(float)
|
|
274
|
+
self.interpolator.set_tangent_constraints(tangent_data)
|
|
275
|
+
|
|
276
|
+
# add interface constraints
|
|
277
|
+
mask = np.all(~np.isnan(data.loc[:, interface_name()].to_numpy(float)), axis=1)
|
|
278
|
+
if mask.sum() > 0:
|
|
279
|
+
interface_data = data.loc[
|
|
280
|
+
mask, xyz_names() + interface_name() + weight_name()
|
|
281
|
+
].to_numpy(float)
|
|
282
|
+
self.interpolator.set_interface_constraints(interface_data)
|
|
283
|
+
# add inequality constraints
|
|
284
|
+
mask = np.all(~np.isnan(data.loc[:, inequality_name()].to_numpy(float)), axis=1)
|
|
285
|
+
if mask.sum() > 0:
|
|
286
|
+
inequality_data = data.loc[mask, xyz_names() + inequality_name()].to_numpy(float)
|
|
287
|
+
self.interpolator.set_value_inequality_constraints(inequality_data)
|
|
288
|
+
mask = np.all(~np.isnan(data.loc[:, pairs_name()].to_numpy(float)), axis=1)
|
|
289
|
+
if mask.sum() > 0:
|
|
290
|
+
pairs_data = data.loc[mask, xyz_names() + pairs_name()].to_numpy(float)
|
|
291
|
+
self.interpolator.set_inequality_pairs_constraints(pairs_data)
|
|
292
|
+
self.data_added = True
|
|
293
|
+
self._up_to_date = False
|
|
294
|
+
|
|
295
|
+
def install_gradient_constraint(self):
|
|
296
|
+
if issubclass(type(self.interpolator), DiscreteInterpolator):
|
|
297
|
+
for g in self._orthogonal_features.values():
|
|
298
|
+
feature, w, region, step, B = g
|
|
299
|
+
if w == 0:
|
|
300
|
+
continue
|
|
301
|
+
logger.info(f"Adding gradient orthogonal constraint {feature.name} to {self.name}")
|
|
302
|
+
logger.info(f'Evaluating gradient {feature.name} for {self.name}')
|
|
303
|
+
# don't worry about masking for unconformities here
|
|
304
|
+
vector = feature.evaluate_gradient(
|
|
305
|
+
self.interpolator.support.barycentre, ignore_regions=True
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
norm = np.linalg.norm(vector, axis=1)
|
|
309
|
+
|
|
310
|
+
vector[norm > 0] /= norm[norm > 0, None]
|
|
311
|
+
element_idx = np.arange(self.interpolator.support.n_elements)
|
|
312
|
+
logger.info(f"Adding to least squares matrix: {self.name}")
|
|
313
|
+
|
|
314
|
+
self.interpolator.add_gradient_orthogonal_constraints(
|
|
315
|
+
self.interpolator.support.barycentre[element_idx[::step], :],
|
|
316
|
+
vector[element_idx[::step], :],
|
|
317
|
+
w=w,
|
|
318
|
+
B=B,
|
|
319
|
+
name=f'{feature.name}_orthogonal',
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def add_equality_constraints(self, feature, region, scalefactor=1.0):
|
|
323
|
+
self._equality_constraints[feature.name] = [feature, region, scalefactor]
|
|
324
|
+
self._up_to_date = False
|
|
325
|
+
|
|
326
|
+
def install_equality_constraints(self):
|
|
327
|
+
for e in self._equality_constraints.values():
|
|
328
|
+
try:
|
|
329
|
+
# assume all parts of structural frame have the same support
|
|
330
|
+
support = self.interpolator.support
|
|
331
|
+
|
|
332
|
+
# work out the values of the nodes where we want hard
|
|
333
|
+
# constraints
|
|
334
|
+
idc = np.arange(0, support.n_nodes)[e[1](support.nodes)]
|
|
335
|
+
val = e[0].evaluate_value(support.nodes[e[1](support.nodes), :])
|
|
336
|
+
mask = ~np.isnan(val)
|
|
337
|
+
self.interpolator.add_equality_constraints(idc[mask], val[mask] * e[2])
|
|
338
|
+
except BaseException as e:
|
|
339
|
+
logger.error(f"Could not add equality for {self.name}")
|
|
340
|
+
logger.error(f"Exception: {e}")
|
|
341
|
+
|
|
342
|
+
def get_value_constraints(self):
|
|
343
|
+
"""
|
|
344
|
+
Get the value constraints for this geological feature
|
|
345
|
+
|
|
346
|
+
Returns
|
|
347
|
+
-------
|
|
348
|
+
np.array((N,4),dtype=double)
|
|
349
|
+
"""
|
|
350
|
+
header = xyz_names() + val_name() + weight_name()
|
|
351
|
+
mask = ~np.isnan(self.data.loc[:, val_name()].to_numpy(float))
|
|
352
|
+
return self.data.loc[mask[:, 0], header].to_numpy(float)
|
|
353
|
+
|
|
354
|
+
def get_gradient_constraints(self):
|
|
355
|
+
"""
|
|
356
|
+
Get the gradient direction constraints
|
|
357
|
+
|
|
358
|
+
Returns
|
|
359
|
+
-------
|
|
360
|
+
numpy array
|
|
361
|
+
"""
|
|
362
|
+
mask = np.all(~np.isnan(self.data.loc[:, gradient_vec_names()].to_numpy(float)), axis=1)
|
|
363
|
+
if mask.shape[0] > 0:
|
|
364
|
+
return self.data.loc[mask, xyz_names() + gradient_vec_names() + weight_name()].to_numpy(
|
|
365
|
+
float
|
|
366
|
+
)
|
|
367
|
+
else:
|
|
368
|
+
return np.zeros((0, 7))
|
|
369
|
+
|
|
370
|
+
def get_tangent_constraints(self):
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
Returns
|
|
374
|
+
-------
|
|
375
|
+
numpy array
|
|
376
|
+
"""
|
|
377
|
+
mask = np.all(~np.isnan(self.data.loc[:, tangent_vec_names()].to_numpy(float)), axis=1)
|
|
378
|
+
if mask.shape[0] > 0:
|
|
379
|
+
return self.data.loc[mask, xyz_names() + tangent_vec_names() + weight_name()].to_numpy(
|
|
380
|
+
float
|
|
381
|
+
)
|
|
382
|
+
else:
|
|
383
|
+
return np.zeros((0, 7))
|
|
384
|
+
|
|
385
|
+
def get_norm_constraints(self):
|
|
386
|
+
"""
|
|
387
|
+
Get the gradient norm constraints
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
numpy array
|
|
392
|
+
"""
|
|
393
|
+
mask = np.all(~np.isnan(self.data.loc[:, normal_vec_names()].to_numpy(float)), axis=1)
|
|
394
|
+
if mask.shape[0] > 0:
|
|
395
|
+
return self.data.loc[mask, xyz_names() + normal_vec_names() + weight_name()].to_numpy(
|
|
396
|
+
float
|
|
397
|
+
)
|
|
398
|
+
else:
|
|
399
|
+
return np.zeros((0, 7))
|
|
400
|
+
|
|
401
|
+
def get_orientation_constraints(self):
|
|
402
|
+
"""
|
|
403
|
+
Get the orientation constraints
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
numpy array
|
|
408
|
+
"""
|
|
409
|
+
gradient_constraints = self.get_gradient_constraints()
|
|
410
|
+
normal_constraints = self.get_norm_constraints()
|
|
411
|
+
return np.vstack([gradient_constraints, normal_constraints])
|
|
412
|
+
|
|
413
|
+
def get_interface_constraints(self):
|
|
414
|
+
mask = np.all(~np.isnan(self.data.loc[:, interface_name()].to_numpy(float)), axis=1)
|
|
415
|
+
if mask.shape[0] > 0:
|
|
416
|
+
return self.data.loc[mask, xyz_names() + interface_name() + weight_name()].to_numpy(
|
|
417
|
+
float
|
|
418
|
+
)
|
|
419
|
+
else:
|
|
420
|
+
return np.zeros((0, 5))
|
|
421
|
+
|
|
422
|
+
def get_data_locations(self):
|
|
423
|
+
"""
|
|
424
|
+
Get only the location for all data points
|
|
425
|
+
|
|
426
|
+
Returns
|
|
427
|
+
-------
|
|
428
|
+
|
|
429
|
+
"""
|
|
430
|
+
return self.data.loc[:, xyz_names()].to_numpy(float)
|
|
431
|
+
|
|
432
|
+
def set_interpolation_geometry(self, origin, maximum, rotation=None):
|
|
433
|
+
"""Update the interpolation support geometry to new bounding box
|
|
434
|
+
|
|
435
|
+
Parameters
|
|
436
|
+
----------
|
|
437
|
+
origin : np.array(3)
|
|
438
|
+
origin vector
|
|
439
|
+
maximum : np.array(3)
|
|
440
|
+
maximum vector
|
|
441
|
+
"""
|
|
442
|
+
logger.info(f"Setting mesh origin: {origin[0]} {origin[1]} {origin[2]} ")
|
|
443
|
+
logger.info(f"Setting mesh maximum: {maximum[0]} {maximum[1]} {maximum[2]}")
|
|
444
|
+
if np.any(np.isnan(origin)):
|
|
445
|
+
logger.warning("Origin is NaN, not updating")
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
if np.any(np.isnan(maximum)):
|
|
449
|
+
logger.warning("Maximum is NaN, not updating")
|
|
450
|
+
return
|
|
451
|
+
|
|
452
|
+
self.interpolator.support.origin = origin
|
|
453
|
+
self.interpolator.support.maximum = maximum
|
|
454
|
+
self.interpolator.support.rotation_xy = rotation
|
|
455
|
+
self._up_to_date = False
|
|
456
|
+
|
|
457
|
+
while self.interpolator.nx < 100:
|
|
458
|
+
self.interpolator.support.step_vector = self.interpolator.support.step_vector * 0.9
|
|
459
|
+
|
|
460
|
+
def check_interpolation_geometry(self, data, buffer=0.3):
|
|
461
|
+
"""Check the interpolation support geometry
|
|
462
|
+
to data to make sure everything fits
|
|
463
|
+
Apply the fault to the model grid to ensure that the support
|
|
464
|
+
is big enough to capture the faulted feature.
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
origin = self.interpolator.support.origin
|
|
468
|
+
maximum = self.interpolator.support.maximum
|
|
469
|
+
pts = self.model.bounding_box.with_buffer(buffer).regular_grid(local=True)
|
|
470
|
+
for f in self.faults:
|
|
471
|
+
pts = f.apply_to_points(pts)
|
|
472
|
+
|
|
473
|
+
origin[origin > np.min(pts, axis=0)] = np.min(pts, axis=0)[origin > np.min(pts, axis=0)]
|
|
474
|
+
maximum[maximum < np.max(pts, axis=0)] = np.max(pts, axis=0)[maximum < np.max(pts, axis=0)]
|
|
475
|
+
self.interpolator.support.origin = origin
|
|
476
|
+
self.interpolator.support.maximum = maximum
|
|
477
|
+
|
|
478
|
+
def build(self, fold=None, fold_weights={}, data_region=None, **kwargs):
|
|
479
|
+
"""
|
|
480
|
+
Runs the interpolation and builds the geological feature
|
|
481
|
+
|
|
482
|
+
Parameters
|
|
483
|
+
----------
|
|
484
|
+
fold : FoldEvent
|
|
485
|
+
fold_weights : dict
|
|
486
|
+
data_region : double <1
|
|
487
|
+
If not none adds a region around the data points to the interpolation
|
|
488
|
+
with data_region as a buffer
|
|
489
|
+
kwargs
|
|
490
|
+
|
|
491
|
+
Returns
|
|
492
|
+
-------
|
|
493
|
+
|
|
494
|
+
"""
|
|
495
|
+
logger.info(f'Building {self.name}')
|
|
496
|
+
# self.get_interpolator(**kwargs)
|
|
497
|
+
for f in self.faults:
|
|
498
|
+
f.builder.update()
|
|
499
|
+
domain = kwargs.get("domain", None)
|
|
500
|
+
if domain:
|
|
501
|
+
self.check_interpolation_geometry(None)
|
|
502
|
+
self.add_data_to_interpolator(**kwargs)
|
|
503
|
+
if data_region is not None:
|
|
504
|
+
xyz = self.interpolator.get_data_locations()
|
|
505
|
+
bb, region = get_data_bounding_box(xyz, data_region)
|
|
506
|
+
# if self.model.reuse_supports == False:
|
|
507
|
+
if np.any(np.min(bb[0, :]) < self.interpolator.support.origin):
|
|
508
|
+
neworigin = np.min([self.interpolator.support.origin, bb[0, :]], axis=0)
|
|
509
|
+
logger.info(
|
|
510
|
+
f"Changing origin of support for {self.name} from \
|
|
511
|
+
{self.interpolator.support.origin[0]} \
|
|
512
|
+
{self.interpolator.support.origin[1]} \
|
|
513
|
+
{self.interpolator.support.origin[2]} \
|
|
514
|
+
to {neworigin[0]} {neworigin[1]} {neworigin[2]}"
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
self.interpolator.support.origin = neworigin
|
|
518
|
+
if np.any(np.max(bb[0, :]) < self.interpolator.support.maximum):
|
|
519
|
+
newmax = np.max([self.interpolator.support.maximum, bb[0, :]], axis=0)
|
|
520
|
+
logger.info(
|
|
521
|
+
f"Changing origin of support for {self.name} from \
|
|
522
|
+
from {self.interpolator.support.maximum[0]} \
|
|
523
|
+
{self.interpolator.support.maximum[1]} \
|
|
524
|
+
{self.interpolator.support.maximum[2]} \
|
|
525
|
+
to {newmax[0]} {newmax[1]} {newmax[2]}"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
self.interpolator.support.maximum = newmax
|
|
529
|
+
self.interpolator.set_region(region=region)
|
|
530
|
+
logger.info(f'Setting up interpolator for {self.name}')
|
|
531
|
+
self.interpolator.setup_interpolator(**kwargs)
|
|
532
|
+
logger.info(f'installing gradient constraints {self.name}')
|
|
533
|
+
self.install_gradient_constraint()
|
|
534
|
+
logger.info(f'installing equality constraints {self.name}')
|
|
535
|
+
self.install_equality_constraints()
|
|
536
|
+
logger.info(f'running interpolation for {self.name}')
|
|
537
|
+
|
|
538
|
+
self.interpolator.solve_system(
|
|
539
|
+
solver=kwargs.get('solver', None), solver_kwargs=kwargs.get('solver_kwargs', {})
|
|
540
|
+
)
|
|
541
|
+
logger.info(f'Finished building {self.name}')
|
|
542
|
+
self._up_to_date = True
|
|
543
|
+
return self._feature
|