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,32 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FeatureType(IntEnum):
|
|
5
|
+
""" """
|
|
6
|
+
|
|
7
|
+
BASE = 0
|
|
8
|
+
INTERPOLATED = 1
|
|
9
|
+
STRUCTURALFRAME = 2
|
|
10
|
+
REGION = 3
|
|
11
|
+
FOLDED = 4
|
|
12
|
+
ANALYTICAL = 5
|
|
13
|
+
LAMBDA = 6
|
|
14
|
+
UNCONFORMITY = 7
|
|
15
|
+
INTRUSION = 8
|
|
16
|
+
FAULT = 9
|
|
17
|
+
DOMAINFAULT = 10
|
|
18
|
+
INACTIVEFAULT = 11
|
|
19
|
+
ONLAPUNCONFORMITY = 12
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# from .builders._geological_feature_builder import GeologicalFeatureBuilder
|
|
23
|
+
from ._base_geological_feature import BaseFeature
|
|
24
|
+
from ._geological_feature import GeologicalFeature
|
|
25
|
+
from ._lambda_geological_feature import LambdaGeologicalFeature
|
|
26
|
+
|
|
27
|
+
# from .builders._geological_feature_builder import GeologicalFeatureBuilder
|
|
28
|
+
from ._structural_frame import StructuralFrame
|
|
29
|
+
from ._cross_product_geological_feature import CrossProductGeologicalFeature
|
|
30
|
+
|
|
31
|
+
from ._unconformity_feature import UnconformityFeature
|
|
32
|
+
from ._analytical_feature import AnalyticalGeologicalFeature
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from ...modelling.features import BaseFeature
|
|
3
|
+
from ...utils import getLogger
|
|
4
|
+
from ...modelling.features import FeatureType
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
logger = getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AnalyticalGeologicalFeature(BaseFeature):
|
|
11
|
+
"""
|
|
12
|
+
Geological feature is class that is used to represent a geometrical element in a geological
|
|
13
|
+
model. For example foliations, fault planes, fold rotation angles etc.
|
|
14
|
+
|
|
15
|
+
Attributes
|
|
16
|
+
----------
|
|
17
|
+
name : string
|
|
18
|
+
should be a unique name for the geological feature
|
|
19
|
+
support : a ScalarField
|
|
20
|
+
holds the property values for the feature and links to the
|
|
21
|
+
support geometry
|
|
22
|
+
data : list
|
|
23
|
+
list containing geological data
|
|
24
|
+
region : list
|
|
25
|
+
list of boolean functions defining whether the feature is
|
|
26
|
+
active
|
|
27
|
+
faults : list
|
|
28
|
+
list of FaultSegments that affect this feature
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, name, vector, origin, regions=[], faults=[], model=None, builder=None):
|
|
32
|
+
BaseFeature.__init__(self, name, model, faults, regions, builder)
|
|
33
|
+
self.vector = np.array(vector, dtype=float)
|
|
34
|
+
self.origin = np.array(origin, dtype=float)
|
|
35
|
+
self.type = FeatureType.ANALYTICAL
|
|
36
|
+
|
|
37
|
+
def to_json(self):
|
|
38
|
+
"""
|
|
39
|
+
Returns a json representation of the geological feature
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
json : dict
|
|
44
|
+
json representation of the geological feature
|
|
45
|
+
"""
|
|
46
|
+
json = super().to_json()
|
|
47
|
+
json["vector"] = self.vector.tolist()
|
|
48
|
+
json["origin"] = self.origin.tolist()
|
|
49
|
+
return json
|
|
50
|
+
|
|
51
|
+
def evaluate_value(self, xyz, ignore_regions=False):
|
|
52
|
+
xyz = np.array(xyz)
|
|
53
|
+
if len(xyz.shape) == 1:
|
|
54
|
+
xyz = xyz[None, :]
|
|
55
|
+
if len(xyz.shape) != 2:
|
|
56
|
+
raise ValueError("xyz must be a 1D or 2D array")
|
|
57
|
+
xyz2 = np.zeros(xyz.shape)
|
|
58
|
+
xyz2[:] = xyz[:]
|
|
59
|
+
for f in self.faults:
|
|
60
|
+
xyz2[:] = f.apply_to_points(xyz)
|
|
61
|
+
if self.model is not None:
|
|
62
|
+
xyz2[:] = self.model.rescale(xyz2, inplace=False)
|
|
63
|
+
xyz2[:] = xyz2 - self.origin
|
|
64
|
+
normal = self.vector / np.linalg.norm(self.vector)
|
|
65
|
+
distance = normal[0] * xyz2[:, 0] + normal[1] * xyz2[:, 1] + normal[2] * xyz2[:, 2]
|
|
66
|
+
return distance / np.linalg.norm(self.vector)
|
|
67
|
+
|
|
68
|
+
def evaluate_gradient(self, xyz, ignore_regions=False):
|
|
69
|
+
xyz = np.array(xyz)
|
|
70
|
+
if len(xyz.shape) == 1:
|
|
71
|
+
xyz = xyz[None, :]
|
|
72
|
+
if len(xyz.shape) != 2:
|
|
73
|
+
raise ValueError("xyz must be a 1D or 2D array")
|
|
74
|
+
v = np.zeros(xyz.shape)
|
|
75
|
+
v[:, :] = self.vector[None, :]
|
|
76
|
+
return v
|
|
77
|
+
|
|
78
|
+
def get_data(self, value_map: Optional[dict] = None):
|
|
79
|
+
return
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABCMeta, abstractmethod
|
|
4
|
+
from typing import Union, List, Optional
|
|
5
|
+
from LoopStructural.modelling.features import FeatureType
|
|
6
|
+
from LoopStructural.utils import getLogger
|
|
7
|
+
from LoopStructural.utils.typing import NumericInput
|
|
8
|
+
from LoopStructural.utils import LoopIsosurfacer, surface_list
|
|
9
|
+
from LoopStructural.datatypes import VectorPoints
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
logger = getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseFeature(metaclass=ABCMeta):
|
|
17
|
+
"""
|
|
18
|
+
Base class for geological features.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, name: str, model=None, faults: list = [], regions: list = [], builder=None):
|
|
22
|
+
"""Base geological feature, this is a virtual class and should not be
|
|
23
|
+
used directly. Inheret from this to implement a new type of geological
|
|
24
|
+
feature or use one of the exisitng implementations
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
name :
|
|
29
|
+
Name of the geological feature to add
|
|
30
|
+
model : GeologicalModel, optional
|
|
31
|
+
the model the feature is associated with, by default None
|
|
32
|
+
faults : list, optional
|
|
33
|
+
any faults that fault this feature, by default []
|
|
34
|
+
regions : list, optional
|
|
35
|
+
any regions that affect this feature, by default []
|
|
36
|
+
builder : GeologicalFeatureBuilder, optional
|
|
37
|
+
the builder of the feature, by default None
|
|
38
|
+
"""
|
|
39
|
+
self.name = name
|
|
40
|
+
self.type = FeatureType.BASE
|
|
41
|
+
self.regions = regions
|
|
42
|
+
self._faults = []
|
|
43
|
+
if faults:
|
|
44
|
+
self.faults = faults
|
|
45
|
+
self._model = model
|
|
46
|
+
self.builder = builder
|
|
47
|
+
self.faults_enabled = True
|
|
48
|
+
self._min = None
|
|
49
|
+
self._max = None
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def faults(self):
|
|
53
|
+
return self._faults
|
|
54
|
+
|
|
55
|
+
@faults.setter
|
|
56
|
+
def faults(self, faults: list):
|
|
57
|
+
_faults = []
|
|
58
|
+
try:
|
|
59
|
+
for f in faults:
|
|
60
|
+
if not issubclass(type(f), BaseFeature):
|
|
61
|
+
raise TypeError("Faults must be a list of BaseFeature")
|
|
62
|
+
_faults.append(f)
|
|
63
|
+
except TypeError:
|
|
64
|
+
logger.error(
|
|
65
|
+
f'Faults must be a list of BaseFeature \n Trying to set using {type(faults)}'
|
|
66
|
+
)
|
|
67
|
+
raise TypeError("Faults must be a list of BaseFeature")
|
|
68
|
+
|
|
69
|
+
self._faults = _faults
|
|
70
|
+
|
|
71
|
+
def to_json(self):
|
|
72
|
+
"""
|
|
73
|
+
Returns a json representation of the geological feature
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
json : dict
|
|
78
|
+
json representation of the geological feature
|
|
79
|
+
"""
|
|
80
|
+
json = {}
|
|
81
|
+
json["name"] = self.name
|
|
82
|
+
json["regions"] = [r.to_json() for r in self.regions]
|
|
83
|
+
json["faults"] = [f.name for f in self.faults]
|
|
84
|
+
json["type"] = self.type
|
|
85
|
+
return json
|
|
86
|
+
|
|
87
|
+
def __str__(self):
|
|
88
|
+
_str = "-----------------------------------------------------\n"
|
|
89
|
+
_str += f"{self.name} {self.type} \n"
|
|
90
|
+
_str += "-----------------------------------------------------\n"
|
|
91
|
+
_str += f"\t{len(self.regions)} regions\n"
|
|
92
|
+
for r in self.regions:
|
|
93
|
+
_str += f"\t \t{r.__str__}\n"
|
|
94
|
+
_str += f"\t{len(self.faults)} faults.\n"
|
|
95
|
+
_str += f"\tFault enabled {self.faults_enabled}\n"
|
|
96
|
+
|
|
97
|
+
for f in self.faults:
|
|
98
|
+
_str += f"\t \t{f.__str__}\n"
|
|
99
|
+
return _str
|
|
100
|
+
|
|
101
|
+
def __repr__(self):
|
|
102
|
+
return self.__str__()
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def model(self):
|
|
106
|
+
return self._model
|
|
107
|
+
|
|
108
|
+
@model.setter
|
|
109
|
+
def model(self, model):
|
|
110
|
+
from LoopStructural import GeologicalModel
|
|
111
|
+
|
|
112
|
+
# causes circular import, could delay import?
|
|
113
|
+
if isinstance(model, GeologicalModel):
|
|
114
|
+
self._model = model
|
|
115
|
+
elif not model:
|
|
116
|
+
self._model = None
|
|
117
|
+
logger.error("Model not set")
|
|
118
|
+
else:
|
|
119
|
+
raise TypeError("Model must be a GeologicalModel")
|
|
120
|
+
|
|
121
|
+
def toggle_faults(self):
|
|
122
|
+
"""
|
|
123
|
+
Turn the fault off for a feature
|
|
124
|
+
This function is only really used for debugging or creating methods
|
|
125
|
+
explanation figures
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
logger.warning(f"Toggling faults for feature {self.name}")
|
|
132
|
+
self.faults_enabled = not self.faults_enabled
|
|
133
|
+
|
|
134
|
+
def add_region(self, region):
|
|
135
|
+
"""
|
|
136
|
+
Adds a region where the geological feature is active to the model.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
region : boolean function(x,y,z)
|
|
141
|
+
returns true if inside region, false if outside
|
|
142
|
+
can be passed as a lambda function e.g.
|
|
143
|
+
lambda pos : feature.evaluate_value(pos) > 0
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
self.regions.append(region)
|
|
150
|
+
|
|
151
|
+
def __call__(self, xyz):
|
|
152
|
+
"""Calls evaluate_value method
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
xyz : np.ndarray
|
|
157
|
+
location to evaluate feature
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
np.ndarray
|
|
162
|
+
the value of the feature at the locations
|
|
163
|
+
"""
|
|
164
|
+
return self.evaluate_value(xyz)
|
|
165
|
+
|
|
166
|
+
@abstractmethod
|
|
167
|
+
def evaluate_value(self, pos, ignore_regions=False):
|
|
168
|
+
"""
|
|
169
|
+
Evaluate the feature at a given position.
|
|
170
|
+
"""
|
|
171
|
+
raise NotImplementedError
|
|
172
|
+
|
|
173
|
+
def evaluate_normalised_value(self, pos: NumericInput):
|
|
174
|
+
"""Evaluate the feature value scaling between 0 and 1
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
pos : NumericInput
|
|
179
|
+
An array or arraylike object with locations
|
|
180
|
+
"""
|
|
181
|
+
value = self.evaluate_value(pos)
|
|
182
|
+
return (value - self.min()) / (self.max() - self.min())
|
|
183
|
+
|
|
184
|
+
def _calculate_mask(self, evaluation_points: np.ndarray, ignore_regions=False) -> np.ndarray:
|
|
185
|
+
"""Calculate the mask for which evaluation points need to be calculated
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
evaluation_points : np.ndarray
|
|
190
|
+
location to be evaluated, Nx3 array
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
np.ndarray
|
|
195
|
+
bool mask Nx1 ndarray
|
|
196
|
+
"""
|
|
197
|
+
mask = np.zeros(evaluation_points.shape[0]).astype(bool)
|
|
198
|
+
|
|
199
|
+
mask[:] = True
|
|
200
|
+
if not ignore_regions:
|
|
201
|
+
# check regions
|
|
202
|
+
for r in self.regions:
|
|
203
|
+
# try:
|
|
204
|
+
mask = np.logical_and(mask, r(evaluation_points))
|
|
205
|
+
return mask
|
|
206
|
+
|
|
207
|
+
def _apply_faults(self, evaluation_points: np.ndarray, reverse: bool = False) -> np.ndarray:
|
|
208
|
+
"""Calculate the restored location of the points given any faults if faults are enabled
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
evaluation_points : np.ndarray
|
|
213
|
+
location to be evaluated, Nx3 array
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
np.ndarray
|
|
218
|
+
faulted value Nx1 ndarray
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
if self.faults_enabled:
|
|
222
|
+
# check faults
|
|
223
|
+
for f in self.faults:
|
|
224
|
+
evaluation_points = f.apply_to_points(evaluation_points, reverse=reverse)
|
|
225
|
+
return evaluation_points
|
|
226
|
+
|
|
227
|
+
@abstractmethod
|
|
228
|
+
def evaluate_gradient(self, pos, ignore_regions=False):
|
|
229
|
+
"""
|
|
230
|
+
Evaluate the gradient of the feature at a given position.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
raise NotImplementedError
|
|
234
|
+
|
|
235
|
+
def min(self):
|
|
236
|
+
"""Calculate the min value of the geological feature
|
|
237
|
+
in the model
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
minimum, float
|
|
242
|
+
min value of the feature evaluated on a regular grid in the model domain
|
|
243
|
+
"""
|
|
244
|
+
if self.model is None:
|
|
245
|
+
return 0
|
|
246
|
+
|
|
247
|
+
return np.nanmin(self.evaluate_value(self.model.regular_grid((10, 10, 10))))
|
|
248
|
+
|
|
249
|
+
def max(self):
|
|
250
|
+
"""Calculate the maximum value of the geological feature
|
|
251
|
+
in the model
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
maximum, float
|
|
256
|
+
max value of the feature evaluated on a regular grid in the model domain
|
|
257
|
+
"""
|
|
258
|
+
if self.model is None:
|
|
259
|
+
return 0
|
|
260
|
+
return np.nanmax(self.evaluate_value(self.model.regular_grid((10, 10, 10))))
|
|
261
|
+
|
|
262
|
+
def __tojson__(self):
|
|
263
|
+
regions = [r.name for r in self.regions]
|
|
264
|
+
faults = [f.name for f in self.faults]
|
|
265
|
+
return {
|
|
266
|
+
"name": self.name,
|
|
267
|
+
"type": self.type,
|
|
268
|
+
"regions": regions,
|
|
269
|
+
"faults": faults,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
def surfaces(
|
|
273
|
+
self,
|
|
274
|
+
value: Union[float, int, List[Union[float, int]]],
|
|
275
|
+
bounding_box=None,
|
|
276
|
+
name: Optional[Union[List[str], str]] = None,
|
|
277
|
+
) -> surface_list:
|
|
278
|
+
"""Find the surfaces of the geological feature at a given value
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
value : Union[float, int, List[float, int]]
|
|
283
|
+
value or list of values to find the surface of the feature
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
list
|
|
288
|
+
list of surfaces
|
|
289
|
+
"""
|
|
290
|
+
if bounding_box is None:
|
|
291
|
+
if self.model is None:
|
|
292
|
+
raise ValueError("Must specify bounding box")
|
|
293
|
+
bounding_box = self.model.bounding_box
|
|
294
|
+
callable = lambda xyz: self.evaluate_value(self.model.scale(xyz))
|
|
295
|
+
isosurfacer = LoopIsosurfacer(bounding_box, callable=callable)
|
|
296
|
+
if name is None and self.name is not None:
|
|
297
|
+
name = self.name
|
|
298
|
+
return isosurfacer.fit(value, name)
|
|
299
|
+
|
|
300
|
+
def scalar_field(self, bounding_box=None):
|
|
301
|
+
"""Create a scalar field for the feature
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
bounding_box : Optional[BoundingBox], optional
|
|
306
|
+
bounding box to evaluate the scalar field in, by default None
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
-------
|
|
310
|
+
np.ndarray
|
|
311
|
+
scalar field
|
|
312
|
+
"""
|
|
313
|
+
if bounding_box is None:
|
|
314
|
+
if self.model is None:
|
|
315
|
+
raise ValueError("Must specify bounding box")
|
|
316
|
+
bounding_box = self.model.bounding_box
|
|
317
|
+
grid = bounding_box.structured_grid(name=self.name)
|
|
318
|
+
value = self.evaluate_value(
|
|
319
|
+
self.model.scale(bounding_box.regular_grid(local=False, order='F'))
|
|
320
|
+
)
|
|
321
|
+
grid.properties[self.name] = value
|
|
322
|
+
|
|
323
|
+
value = self.evaluate_value(bounding_box.cell_centers(order='F'))
|
|
324
|
+
grid.cell_properties[self.name] = value
|
|
325
|
+
return grid
|
|
326
|
+
|
|
327
|
+
def vector_field(self, bounding_box=None, tolerance=0.05, scale=1.0):
|
|
328
|
+
"""Create a vector field for the feature
|
|
329
|
+
|
|
330
|
+
Parameters
|
|
331
|
+
----------
|
|
332
|
+
bounding_box : Optional[BoundingBox], optional
|
|
333
|
+
bounding box to evaluate the vector field in, by default None
|
|
334
|
+
|
|
335
|
+
Returns
|
|
336
|
+
-------
|
|
337
|
+
np.ndarray
|
|
338
|
+
vector field
|
|
339
|
+
"""
|
|
340
|
+
if bounding_box is None:
|
|
341
|
+
if self.model is None:
|
|
342
|
+
raise ValueError("Must specify bounding box")
|
|
343
|
+
bounding_box = self.model.bounding_box
|
|
344
|
+
grid = bounding_box.vtk()
|
|
345
|
+
points = grid.points
|
|
346
|
+
value = self.evaluate_gradient(points)
|
|
347
|
+
|
|
348
|
+
return VectorPoints(points, value, self.name)
|
|
349
|
+
|
|
350
|
+
@abstractmethod
|
|
351
|
+
def get_data(self, value_map: Optional[dict] = None):
|
|
352
|
+
"""Get the data for the feature
|
|
353
|
+
|
|
354
|
+
Parameters
|
|
355
|
+
----------
|
|
356
|
+
value_map : Optional[dict], optional
|
|
357
|
+
map a scalar value to a string, by default None
|
|
358
|
+
|
|
359
|
+
Returns
|
|
360
|
+
-------
|
|
361
|
+
dict
|
|
362
|
+
dictionary of data
|
|
363
|
+
"""
|
|
364
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from ...modelling.features import BaseFeature
|
|
8
|
+
|
|
9
|
+
from ...utils import getLogger
|
|
10
|
+
|
|
11
|
+
logger = getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CrossProductGeologicalFeature(BaseFeature):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
name: str,
|
|
18
|
+
geological_feature_a: BaseFeature,
|
|
19
|
+
geological_feature_b: BaseFeature,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
Create a geological feature for a vector field using the cross
|
|
24
|
+
product between
|
|
25
|
+
two existing features
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
name: feature name
|
|
29
|
+
geological_feature_a: first feature
|
|
30
|
+
geological_feature_b: second feature
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
name : str
|
|
36
|
+
name of the feature
|
|
37
|
+
geological_feature_a : BaseFeature
|
|
38
|
+
Left hand side of cross product
|
|
39
|
+
geological_feature_b : BaseFeature
|
|
40
|
+
Right hand side of cross product
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(name)
|
|
43
|
+
self.geological_feature_a = geological_feature_a
|
|
44
|
+
self.geological_feature_b = geological_feature_b
|
|
45
|
+
self.value_feature = None
|
|
46
|
+
|
|
47
|
+
def evaluate_gradient(self, locations: np.ndarray, ignore_regions=False) -> np.ndarray:
|
|
48
|
+
"""
|
|
49
|
+
Calculate the gradient of the geological feature by using numpy to
|
|
50
|
+
calculate the cross
|
|
51
|
+
product between the two existing feature gradients.
|
|
52
|
+
This means both features have to be evaluated for the locations
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
locations
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
v1 = self.geological_feature_a.evaluate_gradient(locations, ignore_regions)
|
|
63
|
+
# v1 /= np.linalg.norm(v1,axis=1)[:,None]
|
|
64
|
+
v2 = self.geological_feature_b.evaluate_gradient(locations, ignore_regions)
|
|
65
|
+
# v2 /= np.linalg.norm(v2,axis=1)[:,None]
|
|
66
|
+
return np.cross(v1, v2, axisa=1, axisb=1)
|
|
67
|
+
|
|
68
|
+
def evaluate_value(self, evaluation_points: np.ndarray, ignore_regions=False) -> np.ndarray:
|
|
69
|
+
"""
|
|
70
|
+
Return 0 because there is no value for this feature
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
evaluation_points
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
values = np.zeros(evaluation_points.shape[0])
|
|
80
|
+
if self.value_feature is not None:
|
|
81
|
+
values[:] = self.value_feature.evaluate_value(evaluation_points, ignore_regions)
|
|
82
|
+
return values
|
|
83
|
+
|
|
84
|
+
def mean(self):
|
|
85
|
+
if self.value_feature:
|
|
86
|
+
return self.value_feature.mean()
|
|
87
|
+
return 0.0
|
|
88
|
+
|
|
89
|
+
def min(self):
|
|
90
|
+
if self.value_feature:
|
|
91
|
+
return self.value_feature.min()
|
|
92
|
+
return 0.0
|
|
93
|
+
|
|
94
|
+
def max(self):
|
|
95
|
+
if self.value_feature:
|
|
96
|
+
return self.value_feature.max()
|
|
97
|
+
return 0.0
|
|
98
|
+
|
|
99
|
+
def get_data(self, value_map: Optional[dict] = None):
|
|
100
|
+
return
|