LoopStructural 1.6.1__py3-none-any.whl → 1.6.3__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/datatypes/_bounding_box.py +19 -4
- LoopStructural/datatypes/_point.py +31 -0
- LoopStructural/datatypes/_structured_grid.py +17 -0
- LoopStructural/datatypes/_surface.py +17 -0
- LoopStructural/export/omf_wrapper.py +49 -21
- LoopStructural/interpolators/__init__.py +13 -0
- LoopStructural/interpolators/_api.py +81 -13
- LoopStructural/interpolators/_discrete_fold_interpolator.py +11 -4
- LoopStructural/interpolators/_discrete_interpolator.py +100 -53
- LoopStructural/interpolators/_finite_difference_interpolator.py +68 -78
- LoopStructural/interpolators/_geological_interpolator.py +27 -10
- LoopStructural/interpolators/_p1interpolator.py +3 -3
- LoopStructural/interpolators/_surfe_wrapper.py +42 -12
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +16 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +44 -9
- LoopStructural/interpolators/supports/_3d_base_structured.py +24 -7
- LoopStructural/interpolators/supports/_3d_structured_grid.py +38 -9
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +7 -3
- LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +8 -2
- LoopStructural/interpolators/supports/__init__.py +7 -0
- LoopStructural/interpolators/supports/_base_support.py +7 -0
- LoopStructural/modelling/__init__.py +1 -1
- LoopStructural/modelling/core/geological_model.py +0 -2
- LoopStructural/modelling/features/_analytical_feature.py +25 -16
- LoopStructural/modelling/features/_geological_feature.py +47 -11
- LoopStructural/modelling/features/builders/_base_builder.py +8 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +45 -14
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +29 -13
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +5 -0
- LoopStructural/modelling/features/fault/__init__.py +1 -1
- LoopStructural/modelling/features/fault/_fault_function.py +19 -1
- LoopStructural/modelling/features/fault/_fault_segment.py +15 -49
- LoopStructural/modelling/features/fold/__init__.py +1 -2
- LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +0 -23
- LoopStructural/modelling/features/fold/_foldframe.py +4 -4
- LoopStructural/modelling/features/fold/_svariogram.py +81 -46
- LoopStructural/modelling/features/fold/fold_function/__init__.py +27 -0
- LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +253 -0
- LoopStructural/modelling/features/fold/fold_function/_fourier_series_fold_rotation_angle.py +153 -0
- LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +46 -0
- LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +151 -0
- LoopStructural/modelling/input/process_data.py +6 -0
- LoopStructural/modelling/input/project_file.py +24 -3
- LoopStructural/utils/_surface.py +6 -3
- LoopStructural/utils/colours.py +26 -0
- LoopStructural/utils/features.py +5 -0
- LoopStructural/utils/maths.py +53 -1
- LoopStructural/version.py +1 -1
- LoopStructural-1.6.3.dist-info/METADATA +146 -0
- {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.dist-info}/RECORD +53 -48
- {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.dist-info}/WHEEL +1 -1
- LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle.py +0 -149
- LoopStructural-1.6.1.dist-info/METADATA +0 -81
- {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.dist-info}/LICENSE +0 -0
- {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from ....modelling.features.builders import GeologicalFeatureBuilder
|
|
2
|
-
from ....modelling.features.fold import
|
|
2
|
+
from ....modelling.features.fold.fold_function import FoldRotationType, get_fold_rotation_profile
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
5
|
from ....utils import getLogger, InterpolatorError
|
|
@@ -19,6 +19,8 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
19
19
|
name="Feature",
|
|
20
20
|
region=None,
|
|
21
21
|
svario=True,
|
|
22
|
+
axis_profile_type=FoldRotationType.FOURIER_SERIES,
|
|
23
|
+
limb_profile_type=FoldRotationType.FOURIER_SERIES,
|
|
22
24
|
**kwargs,
|
|
23
25
|
):
|
|
24
26
|
"""Builder for creating a geological feature using fold constraints
|
|
@@ -36,6 +38,7 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
36
38
|
region : _type_, optional
|
|
37
39
|
_description_, by default None
|
|
38
40
|
"""
|
|
41
|
+
# create the feature builder, this intialises the interpolator
|
|
39
42
|
GeologicalFeatureBuilder.__init__(
|
|
40
43
|
self,
|
|
41
44
|
interpolatortype=interpolatortype,
|
|
@@ -45,11 +48,27 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
45
48
|
region=region,
|
|
46
49
|
**kwargs,
|
|
47
50
|
)
|
|
51
|
+
# link the interpolator to the fold object
|
|
48
52
|
self.interpolator.fold = fold
|
|
49
53
|
self.fold = fold
|
|
50
54
|
self.fold_weights = fold_weights
|
|
51
55
|
self.kwargs = kwargs
|
|
52
56
|
self.svario = svario
|
|
57
|
+
self.axis_profile_type = axis_profile_type
|
|
58
|
+
self.limb_profile_type = limb_profile_type
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def fold_axis_rotation(self):
|
|
62
|
+
if self.fold.fold_axis_rotation is None:
|
|
63
|
+
self.set_fold_axis()
|
|
64
|
+
return self.fold.fold_axis_rotation
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def fold_limb_rotation(self):
|
|
68
|
+
_axis = self.fold.fold_axis # get axis to make sure its initialised
|
|
69
|
+
if self.fold.fold_limb_rotation is None:
|
|
70
|
+
self.set_fold_limb_rotation()
|
|
71
|
+
return self.fold.fold_limb_rotation
|
|
53
72
|
|
|
54
73
|
def set_fold_axis(self):
|
|
55
74
|
"""calculates the fold axis/ fold axis rotation and adds this to the fold"""
|
|
@@ -67,31 +86,41 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
67
86
|
if not self.fold.foldframe[1].is_valid():
|
|
68
87
|
raise InterpolatorError("Fold frame direction coordinate is not valid")
|
|
69
88
|
far, fad = self.fold.foldframe.calculate_fold_axis_rotation(self)
|
|
70
|
-
fold_axis_rotation =
|
|
71
|
-
a_wl = kwargs.get("axis_wl", None)
|
|
89
|
+
fold_axis_rotation = get_fold_rotation_profile(self.axis_profile_type, far, fad)
|
|
72
90
|
if "axis_function" in kwargs:
|
|
73
91
|
# allow predefined function to be used
|
|
74
92
|
fold_axis_rotation.set_function(kwargs["axis_function"])
|
|
75
93
|
else:
|
|
76
|
-
fold_axis_rotation.
|
|
94
|
+
fold_axis_rotation.fit(params={'wavelength': kwargs.get("axis_wl", None)})
|
|
77
95
|
self.fold.fold_axis_rotation = fold_axis_rotation
|
|
96
|
+
fold_axis_rotation.add_observer(self)
|
|
78
97
|
|
|
79
98
|
def set_fold_limb_rotation(self):
|
|
80
99
|
"""Calculates the limb rotation of the fold and adds it to the fold object"""
|
|
81
100
|
kwargs = self.kwargs
|
|
101
|
+
# need to calculate the fold axis before the fold limb rotation angle
|
|
102
|
+
if self.fold.fold_axis is None:
|
|
103
|
+
self.set_fold_axis()
|
|
82
104
|
# give option of passing own fold limb rotation function
|
|
83
|
-
flr, fld = self.
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
fold_limb_rotation = FoldRotationAngle(flr, fld, svario=self.svario)
|
|
87
|
-
l_wl = kwargs.get("limb_wl", None)
|
|
105
|
+
flr, fld = self.calculate_fold_limb_rotation_angle()
|
|
106
|
+
|
|
107
|
+
fold_limb_rotation = get_fold_rotation_profile(self.limb_profile_type, flr, fld)
|
|
88
108
|
if "limb_function" in kwargs:
|
|
89
109
|
# allow for predefined functions to be used
|
|
90
110
|
fold_limb_rotation.set_function(kwargs["limb_function"])
|
|
91
111
|
else:
|
|
92
|
-
fold_limb_rotation.
|
|
112
|
+
fold_limb_rotation.fit(params={'wavelength': kwargs.get("limb_wl", None)})
|
|
113
|
+
|
|
93
114
|
self.fold.fold_limb_rotation = fold_limb_rotation
|
|
115
|
+
fold_limb_rotation.add_observer(self)
|
|
116
|
+
|
|
117
|
+
def calculate_fold_limb_rotation_angle(self):
|
|
118
|
+
flr, fld = self.fold.foldframe.calculate_fold_limb_rotation(
|
|
119
|
+
self, self.fold.get_fold_axis_orientation
|
|
120
|
+
)
|
|
121
|
+
return flr, fld
|
|
94
122
|
|
|
123
|
+
# def
|
|
95
124
|
def build(self, data_region=None, constrained=None, **kwargs):
|
|
96
125
|
"""the main function to run the interpolation and set up the parameters
|
|
97
126
|
|
|
@@ -115,15 +144,17 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
115
144
|
self.add_data_to_interpolator(constrained=constrained)
|
|
116
145
|
if not self.fold.foldframe[0].is_valid():
|
|
117
146
|
raise InterpolatorError("Fold frame main coordinate is not valid")
|
|
118
|
-
self.
|
|
119
|
-
|
|
147
|
+
if self.fold.fold_axis is None:
|
|
148
|
+
self.set_fold_axis()
|
|
149
|
+
if self.fold.fold_limb_rotation is None:
|
|
150
|
+
self.set_fold_limb_rotation()
|
|
120
151
|
logger.info("Adding fold to {}".format(self.name))
|
|
121
152
|
self.interpolator.fold = self.fold
|
|
122
153
|
# if we have fold weights use those, otherwise just use default
|
|
123
154
|
# self.interpolator.add_fold_constraints(**self.fold_weights)
|
|
124
|
-
kwargs["fold_weights"] = self.fold_weights
|
|
155
|
+
# kwargs["fold_weights"] = self.fold_weights
|
|
125
156
|
if "cgw" not in kwargs:
|
|
126
157
|
# try adding very small cg
|
|
127
158
|
kwargs["cgw"] = 0.0
|
|
128
159
|
# now the fold is set up run the standard interpolation
|
|
129
|
-
super().build(
|
|
160
|
+
return super().build(data_region=data_region, **kwargs)
|
|
@@ -67,7 +67,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
67
67
|
"interpolator is {} and must be a GeologicalInterpolator".format(type(interpolator))
|
|
68
68
|
)
|
|
69
69
|
self._interpolator = interpolator
|
|
70
|
-
|
|
70
|
+
self._up_to_date = self._interpolator.up_to_date
|
|
71
71
|
header = (
|
|
72
72
|
xyz_names()
|
|
73
73
|
+ val_name()
|
|
@@ -93,6 +93,13 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
93
93
|
self._orthogonal_features = {}
|
|
94
94
|
self._equality_constraints = {}
|
|
95
95
|
|
|
96
|
+
def set_not_up_to_date(self, caller):
|
|
97
|
+
logger.info(
|
|
98
|
+
f"Setting {self.name} to not up to date from an instance of {caller.__class__.__name__}"
|
|
99
|
+
)
|
|
100
|
+
self._up_to_date = False
|
|
101
|
+
self._interpolator.up_to_date = False
|
|
102
|
+
|
|
96
103
|
@property
|
|
97
104
|
def interpolator(self):
|
|
98
105
|
return self._interpolator
|
|
@@ -109,6 +116,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
109
116
|
else:
|
|
110
117
|
self._interpolation_region = RegionEverywhere()
|
|
111
118
|
self._interpolator.set_region(region=self._interpolation_region)
|
|
119
|
+
logger.info(f'Setting interpolation region {self.name}')
|
|
112
120
|
self._up_to_date = False
|
|
113
121
|
|
|
114
122
|
def add_data_from_data_frame(self, data_frame, overwrite=False):
|
|
@@ -156,6 +164,8 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
156
164
|
logger.error("Cannot cast {} as integer, setting step to 1".format(step))
|
|
157
165
|
step = 1
|
|
158
166
|
self._orthogonal_features[feature.name] = [feature, w, region, step, B]
|
|
167
|
+
|
|
168
|
+
logger.info(f"Adding orthogonal constraint {feature.name} to {self.name}")
|
|
159
169
|
self._up_to_date = False
|
|
160
170
|
|
|
161
171
|
def add_data_to_interpolator(self, constrained=False, force_constrained=False, **kwargs):
|
|
@@ -315,12 +325,13 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
315
325
|
self.interpolator.support.barycentre[element_idx[::step], :],
|
|
316
326
|
vector[element_idx[::step], :],
|
|
317
327
|
w=w,
|
|
318
|
-
|
|
328
|
+
b=B,
|
|
319
329
|
name=f'{feature.name}_orthogonal',
|
|
320
330
|
)
|
|
321
331
|
|
|
322
332
|
def add_equality_constraints(self, feature, region, scalefactor=1.0):
|
|
323
333
|
self._equality_constraints[feature.name] = [feature, region, scalefactor]
|
|
334
|
+
logger.info(f'Adding equality constraints to {self.name}')
|
|
324
335
|
self._up_to_date = False
|
|
325
336
|
|
|
326
337
|
def install_equality_constraints(self):
|
|
@@ -463,19 +474,22 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
463
474
|
Apply the fault to the model grid to ensure that the support
|
|
464
475
|
is big enough to capture the faulted feature.
|
|
465
476
|
"""
|
|
477
|
+
if self.interpolator.support is not None:
|
|
466
478
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
479
|
+
origin = self.interpolator.support.origin
|
|
480
|
+
maximum = self.interpolator.support.maximum
|
|
481
|
+
pts = self.model.bounding_box.with_buffer(buffer).regular_grid(local=True)
|
|
482
|
+
for f in self.faults:
|
|
483
|
+
pts = f.apply_to_points(pts)
|
|
472
484
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
485
|
+
origin[origin > np.min(pts, axis=0)] = np.min(pts, axis=0)[origin > np.min(pts, axis=0)]
|
|
486
|
+
maximum[maximum < np.max(pts, axis=0)] = np.max(pts, axis=0)[
|
|
487
|
+
maximum < np.max(pts, axis=0)
|
|
488
|
+
]
|
|
489
|
+
self.interpolator.support.origin = origin
|
|
490
|
+
self.interpolator.support.maximum = maximum
|
|
477
491
|
|
|
478
|
-
def build(self,
|
|
492
|
+
def build(self, data_region=None, **kwargs):
|
|
479
493
|
"""
|
|
480
494
|
Runs the interpolation and builds the geological feature
|
|
481
495
|
|
|
@@ -536,7 +550,9 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
536
550
|
logger.info(f'running interpolation for {self.name}')
|
|
537
551
|
|
|
538
552
|
self.interpolator.solve_system(
|
|
539
|
-
solver=kwargs.get('solver',
|
|
553
|
+
solver=kwargs.get('solver', 'cg'),
|
|
554
|
+
tol=kwargs.get('tol', None),
|
|
555
|
+
solver_kwargs=kwargs.get('solver_kwargs', {}),
|
|
540
556
|
)
|
|
541
557
|
logger.info(f'Finished building {self.name}')
|
|
542
558
|
self._up_to_date = True
|
|
@@ -235,3 +235,8 @@ class StructuralFrameBuilder:
|
|
|
235
235
|
def up_to_date(self, callback=None):
|
|
236
236
|
for i in range(3):
|
|
237
237
|
self.builders[i].up_to_date(callback=callback)
|
|
238
|
+
|
|
239
|
+
def set_not_up_to_date(self, caller):
|
|
240
|
+
|
|
241
|
+
for i in range(3):
|
|
242
|
+
self.builders[i].set_not_up_to_date(caller)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import abstractmethod, ABCMeta
|
|
4
|
-
from typing import Optional
|
|
4
|
+
from typing import Optional, List
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
7
|
from ....utils import getLogger
|
|
@@ -367,6 +367,24 @@ class FaultDisplacement:
|
|
|
367
367
|
gz = CubicFunction.from_dict(data["gz"])
|
|
368
368
|
return cls(gx=gx, gy=gy, gz=gz)
|
|
369
369
|
|
|
370
|
+
def plot(self, range=(-1, 1), axs: Optional[List] = None):
|
|
371
|
+
try:
|
|
372
|
+
import matplotlib.pyplot as plt
|
|
373
|
+
|
|
374
|
+
if axs is None:
|
|
375
|
+
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
|
|
376
|
+
for i, (name, f) in enumerate(zip(["gx", "gy", "gz"], [self.gx, self.gy, self.gz])):
|
|
377
|
+
x = np.linspace(range[0], range[1], 100)
|
|
378
|
+
ax[i].plot(x, f(x), label=name)
|
|
379
|
+
ax[i].set_title(name)
|
|
380
|
+
ax[i].set_xlabel("Fault frame coordinate")
|
|
381
|
+
ax[i].set_ylabel("Displacement")
|
|
382
|
+
ax[i].legend()
|
|
383
|
+
|
|
384
|
+
except ImportError:
|
|
385
|
+
logger.warning("matplotlib not installed, not plotting")
|
|
386
|
+
return
|
|
387
|
+
|
|
370
388
|
|
|
371
389
|
class BaseFault(object):
|
|
372
390
|
""" """
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from LoopStructural.utils.maths import regular_tetraherdron_for_points, gradient_from_tetrahedron
|
|
1
2
|
from ....modelling.features.fault._fault_function_feature import (
|
|
2
3
|
FaultDisplacementFeature,
|
|
3
4
|
)
|
|
4
5
|
from ....modelling.features import FeatureType
|
|
5
|
-
from ....modelling.features.fault._fault_function import BaseFault, BaseFault3D
|
|
6
|
+
from ....modelling.features.fault._fault_function import BaseFault, BaseFault3D, FaultDisplacement
|
|
6
7
|
from ....utils import getLogger, NegativeRegion, PositiveRegion
|
|
7
8
|
from ....modelling.features import StructuralFrame
|
|
8
9
|
|
|
@@ -39,7 +40,7 @@ class FaultSegment(StructuralFrame):
|
|
|
39
40
|
StructuralFrame.__init__(self, features, name, fold, model)
|
|
40
41
|
self.type = FeatureType.FAULT
|
|
41
42
|
self.displacement = displacement
|
|
42
|
-
self._faultfunction = BaseFault.fault_displacement
|
|
43
|
+
self._faultfunction = BaseFault().fault_displacement
|
|
43
44
|
self.steps = steps
|
|
44
45
|
self.regions = []
|
|
45
46
|
self.faults_enabled = True
|
|
@@ -57,10 +58,13 @@ class FaultSegment(StructuralFrame):
|
|
|
57
58
|
def faultfunction(self, value):
|
|
58
59
|
if callable(value):
|
|
59
60
|
self._faultfunction = value
|
|
61
|
+
if issubclass(FaultDisplacement, type(value)):
|
|
62
|
+
self._faultfunction = value
|
|
63
|
+
|
|
60
64
|
elif isinstance(value, str) and value == "BaseFault":
|
|
61
|
-
self._faultfunction = BaseFault.fault_displacement
|
|
65
|
+
self._faultfunction = BaseFault().fault_displacement
|
|
62
66
|
elif isinstance(value, str) and value == "BaseFault3D":
|
|
63
|
-
self._faultfunction = BaseFault3D.fault_displacement
|
|
67
|
+
self._faultfunction = BaseFault3D().fault_displacement
|
|
64
68
|
else:
|
|
65
69
|
raise ValueError("Fault function must be a function or BaseFault")
|
|
66
70
|
|
|
@@ -282,6 +286,7 @@ class FaultSegment(StructuralFrame):
|
|
|
282
286
|
-------
|
|
283
287
|
|
|
284
288
|
"""
|
|
289
|
+
logger.info(f'Applying fault {self.name} to points {points.shape}')
|
|
285
290
|
steps = self.steps
|
|
286
291
|
newp = np.copy(points).astype(float)
|
|
287
292
|
# evaluate fault function for all points
|
|
@@ -389,51 +394,12 @@ class FaultSegment(StructuralFrame):
|
|
|
389
394
|
# the nodes of the tetrahedron are then restored by the fault and then gradient is
|
|
390
395
|
# recalculated using the updated node positions but the original corner values
|
|
391
396
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
]
|
|
399
|
-
)
|
|
400
|
-
regular_tetrahedron *= scale_parameter
|
|
401
|
-
xyz = vector[:, :3]
|
|
402
|
-
tetrahedron = np.zeros((xyz.shape[0], 4, 3))
|
|
403
|
-
tetrahedron[:] = xyz[:, None, :]
|
|
404
|
-
tetrahedron[:, :, :] += regular_tetrahedron[None, :, :]
|
|
405
|
-
|
|
406
|
-
vectors = vector[:, 3:]
|
|
407
|
-
corners = np.einsum('ikj,ij->ik', tetrahedron - xyz[:, None, :], vectors)
|
|
408
|
-
tetrahedron = tetrahedron.reshape(-1, 3)
|
|
409
|
-
tetrahedron = self.apply_to_points(tetrahedron)
|
|
410
|
-
tetrahedron = tetrahedron.reshape(-1, 4, 3)
|
|
411
|
-
m = np.array(
|
|
412
|
-
[
|
|
413
|
-
[
|
|
414
|
-
(tetrahedron[:, 1, 0] - tetrahedron[:, 0, 0]),
|
|
415
|
-
(tetrahedron[:, 1, 1] - tetrahedron[:, 0, 1]),
|
|
416
|
-
(tetrahedron[:, 1, 2] - tetrahedron[:, 0, 2]),
|
|
417
|
-
],
|
|
418
|
-
[
|
|
419
|
-
(tetrahedron[:, 2, 0] - tetrahedron[:, 0, 0]),
|
|
420
|
-
(tetrahedron[:, 2, 1] - tetrahedron[:, 0, 1]),
|
|
421
|
-
(tetrahedron[:, 2, 2] - tetrahedron[:, 0, 2]),
|
|
422
|
-
],
|
|
423
|
-
[
|
|
424
|
-
(tetrahedron[:, 3, 0] - tetrahedron[:, 0, 0]),
|
|
425
|
-
(tetrahedron[:, 3, 1] - tetrahedron[:, 0, 1]),
|
|
426
|
-
(tetrahedron[:, 3, 2] - tetrahedron[:, 0, 2]),
|
|
427
|
-
],
|
|
428
|
-
]
|
|
429
|
-
)
|
|
430
|
-
I = np.array([[-1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 1.0, 0.0], [-1.0, 0.0, 0.0, 1.0]])
|
|
431
|
-
m = np.swapaxes(m, 0, 2)
|
|
432
|
-
element_gradients = np.linalg.inv(m)
|
|
433
|
-
|
|
434
|
-
element_gradients = element_gradients.swapaxes(1, 2)
|
|
435
|
-
element_gradients = element_gradients @ I
|
|
436
|
-
v = np.sum(element_gradients * corners[:, None, :], axis=2)
|
|
397
|
+
tetrahedron = regular_tetraherdron_for_points(vector[:, :3], scale_parameter)
|
|
398
|
+
corners = np.einsum('ikj,ij->ik', tetrahedron - vector[:, None, :3], vector[:, 3:])
|
|
399
|
+
|
|
400
|
+
tetrahedron = self.apply_to_points(tetrahedron.reshape(-1, 3)).reshape(-1, 4, 3)
|
|
401
|
+
v = gradient_from_tetrahedron(tetrahedron, corners)
|
|
402
|
+
|
|
437
403
|
return v
|
|
438
404
|
|
|
439
405
|
def add_abutting_fault(self, abutting_fault_feature, positive=None):
|
|
@@ -4,6 +4,5 @@
|
|
|
4
4
|
|
|
5
5
|
from ._fold import FoldEvent
|
|
6
6
|
from ._svariogram import SVariogram
|
|
7
|
-
from ._fold_rotation_angle_feature import FoldRotationAngleFeature
|
|
7
|
+
from ._fold_rotation_angle_feature import FoldRotationAngleFeature
|
|
8
8
|
from ._foldframe import FoldFrame
|
|
9
|
-
from ._fold_rotation_angle import FoldRotationAngle
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import numpy as np
|
|
2
1
|
from ....modelling.features import BaseFeature
|
|
3
|
-
|
|
4
2
|
from ....utils import getLogger
|
|
5
3
|
|
|
6
4
|
logger = getLogger(__name__)
|
|
@@ -44,24 +42,3 @@ class FoldRotationAngleFeature(BaseFeature):
|
|
|
44
42
|
s1 = self.fold_frame.features[0].evaluate_value(location)
|
|
45
43
|
r = self.rotation(s1)
|
|
46
44
|
return r
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def fourier_series(x, c0, c1, c2, w):
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
Parameters
|
|
53
|
-
----------
|
|
54
|
-
x
|
|
55
|
-
c0
|
|
56
|
-
c1
|
|
57
|
-
c2
|
|
58
|
-
w
|
|
59
|
-
|
|
60
|
-
Returns
|
|
61
|
-
-------
|
|
62
|
-
|
|
63
|
-
"""
|
|
64
|
-
v = np.array(x.astype(float))
|
|
65
|
-
# v.fill(c0)
|
|
66
|
-
v = c0 + c1 * np.cos(2 * np.pi / w * x) + c2 * np.sin(2 * np.pi / w * x)
|
|
67
|
-
return np.rad2deg(np.arctan(v))
|
|
@@ -100,8 +100,8 @@ class FoldFrame(StructuralFrame):
|
|
|
100
100
|
# self.features[0].faults_enabled = False
|
|
101
101
|
# self.features[1].faults_enabled = False
|
|
102
102
|
|
|
103
|
-
gpoints = feature_builder.
|
|
104
|
-
npoints = feature_builder.
|
|
103
|
+
gpoints = feature_builder.get_gradient_constraints()[:, :6]
|
|
104
|
+
npoints = feature_builder.get_norm_constraints()[:, :6]
|
|
105
105
|
points = []
|
|
106
106
|
if gpoints.shape[0] > 0:
|
|
107
107
|
points.append(gpoints)
|
|
@@ -173,8 +173,8 @@ class FoldFrame(StructuralFrame):
|
|
|
173
173
|
|
|
174
174
|
"""
|
|
175
175
|
self.features[0].faults_enabled = False
|
|
176
|
-
gpoints = feature_builder.
|
|
177
|
-
npoints = feature_builder.
|
|
176
|
+
gpoints = feature_builder.get_gradient_constraints()[:, :6]
|
|
177
|
+
npoints = feature_builder.get_norm_constraints()[:, :6]
|
|
178
178
|
points = []
|
|
179
179
|
if gpoints.shape[0] > 0:
|
|
180
180
|
points.append(gpoints)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
|
|
2
|
+
from typing import List, Tuple, Optional
|
|
3
3
|
from ....utils import getLogger
|
|
4
4
|
|
|
5
5
|
logger = getLogger(__name__)
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def find_peaks_and_troughs(x, y):
|
|
8
|
+
def find_peaks_and_troughs(x: np.ndarray, y: np.ndarray) -> Tuple[List, List]:
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
Parameters
|
|
@@ -24,7 +24,7 @@ def find_peaks_and_troughs(x, y):
|
|
|
24
24
|
finding the change in derivative
|
|
25
25
|
"""
|
|
26
26
|
if len(x) != len(y):
|
|
27
|
-
|
|
27
|
+
raise ValueError("Cannot guess wavelength, x and y must be the same length")
|
|
28
28
|
pairsx = []
|
|
29
29
|
pairsy = []
|
|
30
30
|
# #TODO numpyize
|
|
@@ -51,55 +51,46 @@ class SVariogram:
|
|
|
51
51
|
The SVariogram is an experimental semi-variogram.
|
|
52
52
|
"""
|
|
53
53
|
|
|
54
|
-
def __init__(self, xdata, ydata):
|
|
55
|
-
self.xdata = xdata
|
|
56
|
-
self.ydata = ydata
|
|
54
|
+
def __init__(self, xdata: np.ndarray, ydata: np.ndarray):
|
|
55
|
+
self.xdata = np.asarray(xdata)
|
|
56
|
+
self.ydata = np.asarray(ydata)
|
|
57
|
+
mask = np.logical_or(np.isnan(self.xdata), np.isnan(self.ydata))
|
|
58
|
+
self.xdata = self.xdata[~mask]
|
|
59
|
+
self.ydata = self.ydata[~mask]
|
|
60
|
+
## maybe check that xdata is not too big here, if it is then this should be done
|
|
61
|
+
## using another library maybe gstools?
|
|
57
62
|
self.dist = np.abs(self.xdata[:, None] - self.xdata[None, :])
|
|
58
63
|
self.variance_matrix = (self.ydata[:, None] - self.ydata[None, :]) ** 2
|
|
59
64
|
self.lags = None
|
|
60
65
|
self.variogram = None
|
|
61
|
-
self.
|
|
66
|
+
self.wavelength_guesses = []
|
|
62
67
|
|
|
63
|
-
def
|
|
68
|
+
def initialise_lags(self, step: Optional[float] = None, nsteps: Optional[int] = None):
|
|
64
69
|
"""
|
|
65
|
-
|
|
66
|
-
You can specify the lags as an array or specify the step size and
|
|
67
|
-
number of steps.
|
|
68
|
-
If neither are specified then the lags are created to be the average
|
|
69
|
-
spacing of the data
|
|
70
|
+
Initialise the lags for the s-variogram
|
|
70
71
|
|
|
71
72
|
Parameters
|
|
72
73
|
----------
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
lags: array
|
|
78
|
-
num
|
|
74
|
+
lag: float
|
|
75
|
+
lag distance for the s-variogram
|
|
76
|
+
nlag: int
|
|
77
|
+
number of lags for the s-variogram
|
|
79
78
|
|
|
80
79
|
Returns
|
|
81
80
|
-------
|
|
82
81
|
|
|
83
82
|
"""
|
|
84
|
-
logger.info("Calculating S-Variogram")
|
|
85
|
-
if lag is not None:
|
|
86
|
-
step = lag
|
|
87
|
-
logger.info(f"Using lag: {step} kwarg for S-variogram")
|
|
88
|
-
|
|
89
|
-
if nlag is not None:
|
|
90
|
-
nstep = nlag
|
|
91
|
-
logger.info(f"Using nlag {nstep} kwarg for s-variogram")
|
|
92
83
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
logger.info(f"Using lag kwarg but calculating nlag as {nstep} for s-variogram")
|
|
84
|
+
if nsteps is not None and step is not None:
|
|
85
|
+
logger.info(f"Using nlag {nsteps} kwarg for s-variogram")
|
|
86
|
+
logger.info(f"Using lag: {step} kwarg for S-variogram")
|
|
87
|
+
self.lags = np.arange(step / 2.0, nsteps * step, step)
|
|
98
88
|
|
|
99
|
-
|
|
89
|
+
if nsteps is None and step is not None:
|
|
90
|
+
nsteps = int(np.ceil((np.nanmax(self.xdata) - np.nanmin(self.xdata)) / step))
|
|
91
|
+
logger.info(f"Using lag kwarg but calculating nlag as {nsteps} for s-variogram")
|
|
100
92
|
|
|
101
|
-
|
|
102
|
-
self.lags = lags
|
|
93
|
+
self.lags = np.arange(step / 2.0, nsteps * step, step)
|
|
103
94
|
|
|
104
95
|
if self.lags is None:
|
|
105
96
|
# time to guess the step size
|
|
@@ -109,15 +100,50 @@ class SVariogram:
|
|
|
109
100
|
|
|
110
101
|
step = np.nanmean(np.nanmin(d, axis=1)) * 4.0
|
|
111
102
|
# find number of steps to cover range in data
|
|
112
|
-
|
|
113
|
-
if
|
|
114
|
-
logger.warning(f"Variogram has too many steps: {
|
|
115
|
-
maximum = step *
|
|
103
|
+
nsteps = int(np.ceil((np.nanmax(self.xdata) - np.nanmin(self.xdata)) / step))
|
|
104
|
+
if nsteps > 200:
|
|
105
|
+
logger.warning(f"Variogram has too many steps: {nsteps}, using 200")
|
|
106
|
+
maximum = step * nsteps
|
|
116
107
|
nstep = 200
|
|
117
108
|
step = maximum / nstep
|
|
118
|
-
self.lags = np.arange(step / 2.0,
|
|
109
|
+
self.lags = np.arange(step / 2.0, nsteps * step, step)
|
|
119
110
|
logger.info(
|
|
120
|
-
f"Using average minimum nearest neighbour distance as lag distance size {step} and using {
|
|
111
|
+
f"Using average minimum nearest neighbour distance as lag distance size {step} and using {nsteps} lags"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def calc_semivariogram(
|
|
115
|
+
self,
|
|
116
|
+
step: Optional[float] = None,
|
|
117
|
+
nsteps: Optional[int] = None,
|
|
118
|
+
lags: Optional[np.ndarray] = None,
|
|
119
|
+
):
|
|
120
|
+
"""
|
|
121
|
+
Calculate a semi-variogram for the x and y data for this object.
|
|
122
|
+
You can specify the lags as an array or specify the step size and
|
|
123
|
+
number of steps.
|
|
124
|
+
If neither are specified then the lags are created to be the average
|
|
125
|
+
spacing of the data
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
step: float
|
|
130
|
+
lag distance for the s-variogram
|
|
131
|
+
nstep: int
|
|
132
|
+
number of lags for the s-variogram
|
|
133
|
+
lags: array
|
|
134
|
+
num
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
logger.info("Calculating S-Variogram")
|
|
141
|
+
if lags is not None:
|
|
142
|
+
self.lags = lags
|
|
143
|
+
self.initialise_lags(step, nsteps)
|
|
144
|
+
if self.lags is None:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"S-Variogram cannot calculate the variogram step size, please specify either step or nsteps"
|
|
121
147
|
)
|
|
122
148
|
tol = self.lags[1] - self.lags[0]
|
|
123
149
|
self.variogram = np.zeros(self.lags.shape)
|
|
@@ -133,7 +159,12 @@ class SVariogram:
|
|
|
133
159
|
self.variogram[i] = np.mean(self.variance_matrix[logic])
|
|
134
160
|
return self.lags, self.variogram, npairs
|
|
135
161
|
|
|
136
|
-
def find_wavelengths(
|
|
162
|
+
def find_wavelengths(
|
|
163
|
+
self,
|
|
164
|
+
step: Optional[float] = None,
|
|
165
|
+
nsteps: Optional[int] = None,
|
|
166
|
+
lags: Optional[np.ndarray] = None,
|
|
167
|
+
) -> List:
|
|
137
168
|
"""
|
|
138
169
|
Picks the wavelengths of the fold by finding the maximum and
|
|
139
170
|
minimums of the s-variogram
|
|
@@ -145,7 +176,7 @@ class SVariogram:
|
|
|
145
176
|
----------
|
|
146
177
|
kwargs : object
|
|
147
178
|
"""
|
|
148
|
-
h, var,
|
|
179
|
+
h, var, _npairs = self.calc_semivariogram(step=step, nsteps=nsteps, lags=lags)
|
|
149
180
|
|
|
150
181
|
px, py = find_peaks_and_troughs(h, var)
|
|
151
182
|
|
|
@@ -156,7 +187,8 @@ class SVariogram:
|
|
|
156
187
|
averagey.append((py[i] + py[i + 1]) / 2.0)
|
|
157
188
|
i += 1 # iterate twice
|
|
158
189
|
# find the extrema of the average curve
|
|
159
|
-
|
|
190
|
+
res = find_peaks_and_troughs(np.array(averagex), np.array(averagey))
|
|
191
|
+
px2, py2 = res
|
|
160
192
|
wl1 = 0.0
|
|
161
193
|
wl1py = 0.0
|
|
162
194
|
for i in range(len(px)):
|
|
@@ -178,11 +210,14 @@ class SVariogram:
|
|
|
178
210
|
if wl2 > 0.0 and wl2 > wl1 * 2 and wl1py < py2[i]:
|
|
179
211
|
break
|
|
180
212
|
if wl1 == 0.0 and wl2 == 0.0:
|
|
213
|
+
logger.warning(
|
|
214
|
+
'Could not automatically guess the wavelength, using 2x the range of the data'
|
|
215
|
+
)
|
|
181
216
|
self.wavelength_guess = [2 * (np.max(self.xdata) - np.min(self.xdata)), 0.0]
|
|
182
217
|
return self.wavelength_guess
|
|
183
218
|
if np.isclose(wl1, 0.0):
|
|
184
219
|
self.wavelength_guess = np.array([wl2 * 2.0, wl1 * 2.0])
|
|
185
|
-
return
|
|
220
|
+
return [wl2]
|
|
186
221
|
# wavelength is 2x the peak on the curve
|
|
187
|
-
self.wavelength_guess =
|
|
222
|
+
self.wavelength_guess = [wl1 * 2.0, wl2 * 2.0]
|
|
188
223
|
return self.wavelength_guess
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from ._trigo_fold_rotation_angle import TrigoFoldRotationAngleProfile
|
|
2
|
+
from ._fourier_series_fold_rotation_angle import FourierSeriesFoldRotationAngleProfile
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Optional
|
|
5
|
+
import numpy.typing as npt
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FoldRotationType(Enum):
|
|
10
|
+
TRIGONOMETRIC = TrigoFoldRotationAngleProfile
|
|
11
|
+
FOURIER_SERIES = FourierSeriesFoldRotationAngleProfile
|
|
12
|
+
# ADDITIONAL = AdditionalFoldRotationAngle
|
|
13
|
+
|
|
14
|
+
def __str__(self):
|
|
15
|
+
return self.name
|
|
16
|
+
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
return self.name
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_fold_rotation_profile(
|
|
22
|
+
fold_rotation_type,
|
|
23
|
+
rotation_angle: Optional[npt.NDArray[np.float64]] = None,
|
|
24
|
+
fold_frame_coordinate: Optional[npt.NDArray[np.float64]] = None,
|
|
25
|
+
**kwargs,
|
|
26
|
+
):
|
|
27
|
+
return fold_rotation_type.value(rotation_angle, fold_frame_coordinate, **kwargs)
|