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.

Files changed (56) hide show
  1. LoopStructural/datatypes/_bounding_box.py +19 -4
  2. LoopStructural/datatypes/_point.py +31 -0
  3. LoopStructural/datatypes/_structured_grid.py +17 -0
  4. LoopStructural/datatypes/_surface.py +17 -0
  5. LoopStructural/export/omf_wrapper.py +49 -21
  6. LoopStructural/interpolators/__init__.py +13 -0
  7. LoopStructural/interpolators/_api.py +81 -13
  8. LoopStructural/interpolators/_discrete_fold_interpolator.py +11 -4
  9. LoopStructural/interpolators/_discrete_interpolator.py +100 -53
  10. LoopStructural/interpolators/_finite_difference_interpolator.py +68 -78
  11. LoopStructural/interpolators/_geological_interpolator.py +27 -10
  12. LoopStructural/interpolators/_p1interpolator.py +3 -3
  13. LoopStructural/interpolators/_surfe_wrapper.py +42 -12
  14. LoopStructural/interpolators/supports/_2d_base_unstructured.py +16 -0
  15. LoopStructural/interpolators/supports/_2d_structured_grid.py +44 -9
  16. LoopStructural/interpolators/supports/_3d_base_structured.py +24 -7
  17. LoopStructural/interpolators/supports/_3d_structured_grid.py +38 -9
  18. LoopStructural/interpolators/supports/_3d_structured_tetra.py +7 -3
  19. LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +8 -2
  20. LoopStructural/interpolators/supports/__init__.py +7 -0
  21. LoopStructural/interpolators/supports/_base_support.py +7 -0
  22. LoopStructural/modelling/__init__.py +1 -1
  23. LoopStructural/modelling/core/geological_model.py +0 -2
  24. LoopStructural/modelling/features/_analytical_feature.py +25 -16
  25. LoopStructural/modelling/features/_geological_feature.py +47 -11
  26. LoopStructural/modelling/features/builders/_base_builder.py +8 -0
  27. LoopStructural/modelling/features/builders/_folded_feature_builder.py +45 -14
  28. LoopStructural/modelling/features/builders/_geological_feature_builder.py +29 -13
  29. LoopStructural/modelling/features/builders/_structural_frame_builder.py +5 -0
  30. LoopStructural/modelling/features/fault/__init__.py +1 -1
  31. LoopStructural/modelling/features/fault/_fault_function.py +19 -1
  32. LoopStructural/modelling/features/fault/_fault_segment.py +15 -49
  33. LoopStructural/modelling/features/fold/__init__.py +1 -2
  34. LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +0 -23
  35. LoopStructural/modelling/features/fold/_foldframe.py +4 -4
  36. LoopStructural/modelling/features/fold/_svariogram.py +81 -46
  37. LoopStructural/modelling/features/fold/fold_function/__init__.py +27 -0
  38. LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +253 -0
  39. LoopStructural/modelling/features/fold/fold_function/_fourier_series_fold_rotation_angle.py +153 -0
  40. LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +46 -0
  41. LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +151 -0
  42. LoopStructural/modelling/input/process_data.py +6 -0
  43. LoopStructural/modelling/input/project_file.py +24 -3
  44. LoopStructural/utils/_surface.py +6 -3
  45. LoopStructural/utils/colours.py +26 -0
  46. LoopStructural/utils/features.py +5 -0
  47. LoopStructural/utils/maths.py +53 -1
  48. LoopStructural/version.py +1 -1
  49. LoopStructural-1.6.3.dist-info/METADATA +146 -0
  50. {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.dist-info}/RECORD +53 -48
  51. {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.dist-info}/WHEEL +1 -1
  52. LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
  53. LoopStructural/modelling/features/fold/_fold_rotation_angle.py +0 -149
  54. LoopStructural-1.6.1.dist-info/METADATA +0 -81
  55. {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.dist-info}/LICENSE +0 -0
  56. {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 FoldRotationAngle
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 = FoldRotationAngle(far, fad, svario=self.svario)
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.fit_fourier_series(wl=a_wl)
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.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)
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.fit_fourier_series(wl=l_wl, **kwargs)
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.set_fold_axis()
119
- self.set_fold_limb_rotation()
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(self, data_region=data_region, **kwargs)
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
- B=B,
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
- 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)
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
- 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
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, fold=None, fold_weights={}, data_region=None, **kwargs):
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', None), solver_kwargs=kwargs.get('solver_kwargs', {})
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,3 +1,3 @@
1
- from ._fault_function import Composite, CubicFunction, Ones, Zeros
1
+ from ._fault_function import Composite, CubicFunction, Ones, Zeros, FaultDisplacement
2
2
  from ._fault_function_feature import FaultDisplacementFeature
3
3
  from ._fault_segment import FaultSegment
@@ -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
- regular_tetrahedron = np.array(
393
- [
394
- [np.sqrt(8 / 9), 0, -1 / 3],
395
- [-np.sqrt(2 / 9), np.sqrt(2 / 3), -1 / 3],
396
- [-np.sqrt(2 / 9), -np.sqrt(2 / 3), -1 / 3],
397
- [0, 0, 1],
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, fourier_series
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.interpolator.get_gradient_constraints()[:, :6]
104
- npoints = feature_builder.interpolator.get_norm_constraints()[:, :6]
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.interpolator.get_gradient_constraints()[:, :6]
177
- npoints = feature_builder.interpolator.get_norm_constraints()[:, :6]
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
- return False
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.wavelength_guess = [None, None]
66
+ self.wavelength_guesses = []
62
67
 
63
- def calc_semivariogram(self, lag=None, nlag=None, lags=None):
68
+ def initialise_lags(self, step: Optional[float] = None, nsteps: Optional[int] = None):
64
69
  """
65
- Calculate a semi-variogram for the x and y data for this object.
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
- step: float
74
- lag distance for the s-variogram
75
- nstep: int
76
- number of lags for the s-variogram
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
- self.lags = np.arange(step / 2.0, nstep * step, step)
94
-
95
- if nlag is None and lag is not None:
96
- nstep = int(np.ceil((np.nanmax(self.xdata) - np.nanmin(self.xdata)) / step))
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
- self.lags = np.arange(step / 2.0, nstep * step, step)
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
- if lags is not None:
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
- nstep = int(np.ceil((np.nanmax(self.xdata) - np.nanmin(self.xdata)) / step))
113
- if nstep > 200:
114
- logger.warning(f"Variogram has too many steps: {nstep}, using 200")
115
- maximum = step * nstep
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, nstep * step, step)
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 {nstep} lags"
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(self, **kwargs):
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, npairs = self.calc_semivariogram(**kwargs)
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
- px2, py2 = find_peaks_and_troughs(averagex, averagey)
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 self.wavelength_guess
220
+ return [wl2]
186
221
  # wavelength is 2x the peak on the curve
187
- self.wavelength_guess = np.array([wl1 * 2.0, wl2 * 2.0])
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)