LoopStructural 1.6.7__py3-none-any.whl → 1.6.9__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 (45) hide show
  1. LoopStructural/__init__.py +1 -0
  2. LoopStructural/datatypes/_bounding_box.py +69 -10
  3. LoopStructural/datatypes/_point.py +18 -11
  4. LoopStructural/datatypes/_structured_grid.py +37 -9
  5. LoopStructural/datatypes/_surface.py +3 -3
  6. LoopStructural/export/geoh5.py +4 -2
  7. LoopStructural/interpolators/__init__.py +1 -0
  8. LoopStructural/interpolators/_discrete_interpolator.py +18 -0
  9. LoopStructural/interpolators/_finite_difference_interpolator.py +64 -11
  10. LoopStructural/interpolators/_geological_interpolator.py +9 -0
  11. LoopStructural/interpolators/_interpolator_builder.py +98 -19
  12. LoopStructural/interpolators/_interpolator_factory.py +2 -3
  13. LoopStructural/interpolators/_surfe_wrapper.py +3 -0
  14. LoopStructural/interpolators/supports/_2d_base_unstructured.py +3 -0
  15. LoopStructural/interpolators/supports/_2d_structured_grid.py +3 -0
  16. LoopStructural/interpolators/supports/_3d_base_structured.py +28 -5
  17. LoopStructural/interpolators/supports/_3d_structured_grid.py +2 -0
  18. LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +21 -13
  19. LoopStructural/interpolators/supports/_base_support.py +4 -0
  20. LoopStructural/interpolators/supports/_support_factory.py +12 -4
  21. LoopStructural/modelling/core/geological_model.py +5 -6
  22. LoopStructural/modelling/features/_base_geological_feature.py +9 -3
  23. LoopStructural/modelling/features/_cross_product_geological_feature.py +1 -2
  24. LoopStructural/modelling/features/_geological_feature.py +3 -5
  25. LoopStructural/modelling/features/_lambda_geological_feature.py +11 -1
  26. LoopStructural/modelling/features/_projected_vector_feature.py +1 -2
  27. LoopStructural/modelling/features/_unconformity_feature.py +0 -1
  28. LoopStructural/modelling/features/builders/_base_builder.py +4 -2
  29. LoopStructural/modelling/features/builders/_geological_feature_builder.py +21 -25
  30. LoopStructural/modelling/features/builders/_structural_frame_builder.py +9 -4
  31. LoopStructural/modelling/features/fault/_fault_segment.py +1 -1
  32. LoopStructural/modelling/features/fold/__init__.py +1 -3
  33. LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +0 -1
  34. LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +0 -1
  35. LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +0 -1
  36. LoopStructural/modelling/input/process_data.py +4 -2
  37. LoopStructural/utils/_surface.py +2 -2
  38. LoopStructural/utils/_transformation.py +28 -13
  39. LoopStructural/utils/colours.py +3 -1
  40. LoopStructural/version.py +1 -1
  41. {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info}/METADATA +7 -6
  42. {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info}/RECORD +45 -45
  43. {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info}/WHEEL +1 -1
  44. {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info/licenses}/LICENSE +0 -0
  45. {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info}/top_level.txt +0 -0
@@ -1,55 +1,134 @@
1
1
  from LoopStructural.interpolators import (
2
- GeologicalInterpolator,
3
2
  InterpolatorFactory,
4
3
  InterpolatorType,
5
4
  )
6
5
  from LoopStructural.datatypes import BoundingBox
7
- from typing import Optional, Union
6
+ from typing import Union, Optional
8
7
  import numpy as np
9
8
 
9
+ from LoopStructural.interpolators._geological_interpolator import GeologicalInterpolator
10
+
10
11
 
11
12
  class InterpolatorBuilder:
12
13
  def __init__(
13
14
  self,
14
15
  interpolatortype: Union[str, InterpolatorType],
15
16
  bounding_box: BoundingBox,
16
- nelements: int = 1000,
17
- buffer: float = 0.2,
17
+ nelements: Optional[int] = None,
18
+ buffer: Optional[float] = None,
18
19
  **kwargs,
19
20
  ):
21
+ """This class helps initialise and setup a geological interpolator.
22
+
23
+ Parameters
24
+ ----------
25
+ interpolatortype : Union[str, InterpolatorType]
26
+ type of interpolator
27
+ bounding_box : BoundingBox
28
+ bounding box of the area to interpolate
29
+ nelements : int, optional
30
+ degrees of freedom of the interpolator, by default 1000
31
+ buffer : float, optional
32
+ how much of a buffer around the bounding box should be used, by default 0.2
33
+ """
20
34
  self.interpolatortype = interpolatortype
21
35
  self.bounding_box = bounding_box
22
36
  self.nelements = nelements
23
37
  self.buffer = buffer
24
38
  self.kwargs = kwargs
25
- self.interpolator : Optional[GeologicalInterpolator]= None
26
-
27
- def create_interpolator(self) -> 'InterpolatorBuilder':
28
39
  self.interpolator = InterpolatorFactory.create_interpolator(
29
- interpolatortype=self.interpolatortype,
30
- boundingbox=self.bounding_box,
31
- nelements=self.nelements,
32
- buffer=self.buffer,
33
- **self.kwargs,
34
- )
35
- return self
40
+ interpolatortype=self.interpolatortype,
41
+ boundingbox=self.bounding_box,
42
+ nelements=self.nelements,
43
+ buffer=self.buffer,
44
+ **self.kwargs,
45
+ )
36
46
 
37
- def set_value_constraints(self, value_constraints: np.ndarray) -> 'InterpolatorBuilder':
47
+ def add_value_constraints(self, value_constraints: np.ndarray) -> 'InterpolatorBuilder':
48
+ """Add value constraints to the interpolator
49
+
50
+ Parameters
51
+ ----------
52
+ value_constraints : np.ndarray
53
+ x,y,z,value of the constraints
54
+
55
+ Returns
56
+ -------
57
+ InterpolatorBuilder
58
+ reference to the builder
59
+ """
38
60
  if self.interpolator:
39
61
  self.interpolator.set_value_constraints(value_constraints)
40
62
  return self
41
63
 
42
- def set_gradient_constraints(self, gradient_constraints: np.ndarray) -> 'InterpolatorBuilder':
64
+ def add_gradient_constraints(self, gradient_constraints: np.ndarray) -> 'InterpolatorBuilder':
65
+ """Add gradient constraints to the interpolator
66
+ Where g1 and g2 are two vectors that are orthogonal to the gradient
67
+ $'(X)\cdot g1 = 0$ and $'(X)\cdot g2 = 0$
68
+
69
+ Parameters
70
+ ----------
71
+ gradient_constraints : np.ndarray
72
+ x,y,z,gradient_x,gradient_y,gradient_z of the constraints
73
+
74
+ Returns
75
+ -------
76
+ InterpolatorBuilder
77
+ reference to the builder
78
+ """
79
+
43
80
  if self.interpolator:
44
81
  self.interpolator.set_gradient_constraints(gradient_constraints)
45
82
  return self
46
83
 
47
- def set_normal_constraints(self, normal_constraints: np.ndarray) -> 'InterpolatorBuilder':
84
+ def add_normal_constraints(self, normal_constraints: np.ndarray) -> 'InterpolatorBuilder':
85
+ """Add normal constraints to the interpolator
86
+ Where n is the normal vector to the surface
87
+ $f'(X).dx = nx$
88
+ $f'(X).dy = ny$
89
+ $f'(X).dz = nz$
90
+ Parameters
91
+ ----------
92
+ normal_constraints : np.ndarray
93
+ x,y,z,nx,ny,nz of the constraints
94
+
95
+ Returns
96
+ -------
97
+ InterpolatorBuilder
98
+ reference to the builder
99
+ """
48
100
  if self.interpolator:
49
101
  self.interpolator.set_normal_constraints(normal_constraints)
50
102
  return self
103
+ #TODO implement/check inequalities
104
+ # def add_inequality_constraints(self, inequality_constraints: np.ndarray) -> 'InterpolatorBuilder':
105
+ # if self.interpolator:
106
+ # self.interpolator.set_value_inequality_constraints(inequality_constraints)
107
+ # return self
108
+ # def add_inequality_pair_constraints(self, inequality_pair_constraints: np.ndarray) -> 'InterpolatorBuilder':
109
+ # if self.interpolator:
110
+ # self.interpolator.set_inequality_pairs_constraints(inequality_pair_constraints)
111
+ # return self
112
+
113
+ def setup_interpolator(self, **kwargs) -> 'InterpolatorBuilder':
114
+ """This adds all of the constraints to the interpolator and
115
+ sets the regularisation constraints
51
116
 
52
- def setup_interpolator(self, **kwargs) -> Optional[GeologicalInterpolator]:
117
+ Returns
118
+ -------
119
+ InterpolatorBuilder
120
+ reference to the builder
121
+ """
53
122
  if self.interpolator:
54
123
  self.interpolator.setup(**kwargs)
55
- return self.interpolator
124
+ return self
125
+
126
+ def build(self)->GeologicalInterpolator:
127
+ """Builds the interpolator and returns it
128
+
129
+ Returns
130
+ -------
131
+ GeologicalInterpolator
132
+ The interpolator fitting all of the constraints provided
133
+ """
134
+ return self.interpolator
@@ -18,14 +18,13 @@ class InterpolatorFactory:
18
18
  nelements: Optional[int] = None,
19
19
  element_volume: Optional[float] = None,
20
20
  support=None,
21
- buffer: float = 0.2,
21
+ buffer: Optional[float] = None,
22
22
  ):
23
23
  if interpolatortype is None:
24
24
  raise ValueError("No interpolator type specified")
25
25
  if boundingbox is None:
26
26
  raise ValueError("No bounding box specified")
27
- if nelements is None:
28
- raise ValueError("No number of elements specified")
27
+
29
28
  if isinstance(interpolatortype, str):
30
29
  interpolatortype = interpolator_string_map[interpolatortype]
31
30
  if support is None:
@@ -35,6 +35,9 @@ class SurfeRBFInterpolator(GeologicalInterpolator):
35
35
  def set_region(self, **kwargs):
36
36
  pass
37
37
 
38
+ def set_nelements(self, nelements) -> int:
39
+ return 0
40
+
38
41
  def add_gradient_constraints(self, w=1):
39
42
  points = self.get_gradient_constraints()
40
43
  if points.shape[0] > 0:
@@ -68,6 +68,9 @@ class BaseUnstructured2d(BaseSupport):
68
68
  _initialise_aabb(self)
69
69
  return self._aabb_table
70
70
 
71
+ def set_nelements(self, nelements) -> int:
72
+ raise NotImplementedError
73
+
71
74
  @property
72
75
  def shared_elements(self):
73
76
  if np.sum(self._shared_elements) == 0:
@@ -66,6 +66,9 @@ class StructuredGrid2D(BaseSupport):
66
66
  def n_nodes(self):
67
67
  return self.nsteps[0] * self.nsteps[1]
68
68
 
69
+ def set_nelements(self, nelements) -> int:
70
+ raise NotImplementedError("Cannot set number of elements for 2D structured grid")
71
+
69
72
  @property
70
73
  def n_elements(self):
71
74
  return self.nsteps_cells[0] * self.nsteps_cells[1]
@@ -3,6 +3,7 @@ from abc import abstractmethod
3
3
  import numpy as np
4
4
  from LoopStructural.utils import getLogger
5
5
  from . import SupportType
6
+ from typing import Tuple
6
7
 
7
8
  logger = getLogger(__name__)
8
9
 
@@ -34,6 +35,11 @@ class BaseStructuredSupport(BaseSupport):
34
35
  # we use property decorators to update these when different parts of
35
36
  # the geometry need to change
36
37
  # inisialise the private attributes
38
+ # cast to numpy array, to allow list like input
39
+ origin = np.array(origin)
40
+ nsteps = np.array(nsteps)
41
+ step_vector = np.array(step_vector)
42
+
37
43
  self.type = SupportType.BaseStructured
38
44
  if np.any(step_vector == 0):
39
45
  logger.warning(f"Step vector {step_vector} has zero values")
@@ -41,10 +47,10 @@ class BaseStructuredSupport(BaseSupport):
41
47
  raise LoopException("nsteps cannot be zero")
42
48
  if np.any(nsteps < 0):
43
49
  raise LoopException("nsteps cannot be negative")
44
- if np.any(nsteps < 3):
45
- raise LoopException(
46
- "step vector cannot be less than 3. Try increasing the resolution of the interpolator"
47
- )
50
+ # if np.any(nsteps < 3):
51
+ # raise LoopException(
52
+ # "step vector cannot be less than 3. Try increasing the resolution of the interpolator"
53
+ # )
48
54
  self._nsteps = np.array(nsteps, dtype=int) + 1
49
55
  self._step_vector = np.array(step_vector)
50
56
  self._origin = np.array(origin)
@@ -56,6 +62,23 @@ class BaseStructuredSupport(BaseSupport):
56
62
  self.rotation_xy = rotation_xy
57
63
  self.interpolator = None
58
64
 
65
+ @property
66
+ def volume(self):
67
+ return np.prod(self.maximum - self.origin)
68
+
69
+ def set_nelements(self, nelements) -> int:
70
+ box_vol = self.volume
71
+ ele_vol = box_vol / nelements
72
+ # calculate the step vector of a regular cube
73
+ step_vector = np.zeros(3)
74
+
75
+ step_vector[:] = ele_vol ** (1.0 / 3.0)
76
+
77
+ # number of steps is the length of the box / step vector
78
+ nsteps = np.ceil((self.maximum - self.origin) / step_vector).astype(int)
79
+ self.nsteps = nsteps
80
+ return self.n_elements
81
+
59
82
  def to_dict(self):
60
83
  return {
61
84
  "origin": self.origin,
@@ -229,7 +252,7 @@ class BaseStructuredSupport(BaseSupport):
229
252
  """ """
230
253
  return np.einsum("ijk,ik->ij", self.rotation_xy[None, :, :], pos)
231
254
 
232
- def position_to_cell_index(self, pos: np.ndarray) -> np.ndarray:
255
+ def position_to_cell_index(self, pos: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
233
256
  """Get the indexes (i,j,k) of a cell
234
257
  that a point is inside
235
258
 
@@ -40,6 +40,8 @@ class StructuredGrid(BaseStructuredSupport):
40
40
  self.regions["everywhere"] = np.ones(self.n_nodes).astype(bool)
41
41
 
42
42
  def onGeometryChange(self):
43
+ if self.interpolator is not None:
44
+ self.interpolator.reset()
43
45
  pass
44
46
 
45
47
  @property
@@ -61,19 +61,24 @@ class UnStructuredTetMesh(BaseSupport):
61
61
  length = self.maximum - self.minimum
62
62
  self.minimum -= length * 0.1
63
63
  self.maximum += length * 0.1
64
- if aabb_nsteps is None:
65
- box_vol = np.prod(self.maximum - self.minimum)
66
- element_volume = box_vol / (len(self.elements) / 20)
67
- # calculate the step vector of a regular cube
68
- step_vector = np.zeros(3)
69
- step_vector[:] = element_volume ** (1.0 / 3.0)
70
- # number of steps is the length of the box / step vector
71
- aabb_nsteps = np.ceil((self.maximum - self.minimum) / step_vector).astype(int)
72
- # make sure there is at least one cell in every dimension
73
- aabb_nsteps[aabb_nsteps < 2] = 2
74
- aabb_nsteps = np.array(aabb_nsteps, dtype=int)
75
- step_vector = (self.maximum - self.minimum) / (aabb_nsteps - 1)
76
- self.aabb_grid = StructuredGrid(self.minimum, nsteps=aabb_nsteps, step_vector=step_vector)
64
+ if self.elements.shape[0] < 2000:
65
+ self.aabb_grid = StructuredGrid(self.minimum, nsteps=[2, 2, 2], step_vector=[1, 1, 1])
66
+ else:
67
+ if aabb_nsteps is None:
68
+ box_vol = np.prod(self.maximum - self.minimum)
69
+ element_volume = box_vol / (len(self.elements) / 20)
70
+ # calculate the step vector of a regular cube
71
+ step_vector = np.zeros(3)
72
+ step_vector[:] = element_volume ** (1.0 / 3.0)
73
+ # number of steps is the length of the box / step vector
74
+ aabb_nsteps = np.ceil((self.maximum - self.minimum) / step_vector).astype(int)
75
+ # make sure there is at least one cell in every dimension
76
+ aabb_nsteps[aabb_nsteps < 2] = 2
77
+ aabb_nsteps = np.array(aabb_nsteps, dtype=int)
78
+ step_vector = (self.maximum - self.minimum) / (aabb_nsteps - 1)
79
+ self.aabb_grid = StructuredGrid(
80
+ self.minimum, nsteps=aabb_nsteps, step_vector=step_vector
81
+ )
77
82
  # make a big table to store which tetra are in which element.
78
83
  # if this takes up too much memory it could be simplified by using sparse matrices or dict but
79
84
  # at the expense of speed
@@ -87,6 +92,9 @@ class UnStructuredTetMesh(BaseSupport):
87
92
  self._init_face_table()
88
93
  self._initialise_aabb()
89
94
 
95
+ def set_nelements(self, nelements):
96
+ raise NotImplementedError("Cannot set number of elements for unstructured mesh")
97
+
90
98
  @property
91
99
  def nodes(self):
92
100
  return self._nodes
@@ -119,3 +119,7 @@ class BaseSupport(metaclass=ABCMeta):
119
119
  Return a vtk object
120
120
  """
121
121
  pass
122
+
123
+ @abstractmethod
124
+ def set_nelements(self, nelements) -> int:
125
+ pass
@@ -1,4 +1,6 @@
1
1
  from LoopStructural.interpolators.supports import support_map, SupportType
2
+ import numpy as np
3
+ from typing import Optional
2
4
 
3
5
 
4
6
  class SupportFactory:
@@ -20,13 +22,19 @@ class SupportFactory:
20
22
 
21
23
  @staticmethod
22
24
  def create_support_from_bbox(
23
- support_type, bounding_box, nelements, element_volume=None, buffer: float = 0.2
25
+ support_type, bounding_box, nelements, element_volume=None, buffer: Optional[float] = None
24
26
  ):
25
27
  if isinstance(support_type, str):
26
28
  support_type = SupportType._member_map_[support_type].numerator
27
- bbox = bounding_box.with_buffer(buffer=buffer)
28
- bbox.nelements = nelements
29
+ if buffer is not None:
30
+ bounding_box = bounding_box.with_buffer(buffer=buffer)
31
+ if element_volume is not None:
32
+ nelements = int(np.prod(bounding_box.length) / element_volume)
33
+ if nelements is not None:
34
+ bounding_box.nelements = nelements
29
35
 
30
36
  return support_map[support_type](
31
- origin=bbox.origin, step_vector=bbox.step_vector, nsteps=bbox.nsteps
37
+ origin=bounding_box.origin,
38
+ step_vector=bounding_box.step_vector,
39
+ nsteps=bounding_box.nsteps,
32
40
  )
@@ -711,11 +711,10 @@ class GeologicalModel:
711
711
  # build feature
712
712
  # series_feature = series_builder.build(**kwargs)
713
713
  series_feature = series_builder.feature
714
- series_builder.build_arguments = kwargs
714
+ series_builder.update_build_arguments(kwargs | {"domain": True, 'tol': tol})
715
715
  # this support is built for the entire model domain? Possibly would
716
716
  # could just pass a regular grid of points - mask by any above unconformities??
717
- series_builder.build_arguments['domain'] = True
718
- series_builder.build_arguments["tol"] = tol
717
+
719
718
  series_feature.type = FeatureType.INTERPOLATED
720
719
  self._add_feature(series_feature)
721
720
  return series_feature
@@ -850,7 +849,7 @@ class GeologicalModel:
850
849
 
851
850
  # series_feature = series_builder.build(**kwargs)
852
851
  series_feature = series_builder.feature
853
- series_builder.build_arguments = kwargs
852
+ series_builder.update_build_arguments(kwargs)
854
853
  series_feature.type = FeatureType.INTERPOLATED
855
854
  series_feature.fold = fold
856
855
 
@@ -1264,7 +1263,7 @@ class GeologicalModel:
1264
1263
  # build feature
1265
1264
  # domain_fault = domain_fault_feature_builder.build(**kwargs)
1266
1265
  domain_fault = domain_fault_feature_builder.feature
1267
- domain_fault_feature_builder.build_arguments = kwargs
1266
+ domain_fault_feature_builder.update_build_arguments(kwargs)
1268
1267
  domain_fault.type = FeatureType.DOMAINFAULT
1269
1268
  self._add_feature(domain_fault)
1270
1269
  self._add_domain_fault_below(domain_fault)
@@ -1809,7 +1808,7 @@ class GeologicalModel:
1809
1808
  grid = self.bounding_box.structured_grid(name=name)
1810
1809
 
1811
1810
  grid.cell_properties['stratigraphy'] = self.evaluate_model(
1812
- self.rescale(self.bounding_box.cell_centers())
1811
+ self.rescale(self.bounding_box.cell_centres())
1813
1812
  )
1814
1813
  return grid, self.stratigraphic_ids()
1815
1814
 
@@ -334,11 +334,17 @@ class BaseFeature(metaclass=ABCMeta):
334
334
  bounding_box = self.model.bounding_box
335
335
  grid = bounding_box.structured_grid(name=self.name)
336
336
  value = self.evaluate_value(
337
- self.model.scale(bounding_box.regular_grid(local=False, order='F'))
337
+ bounding_box.regular_grid(local=False, order='F')
338
338
  )
339
+ if self.model is not None:
340
+
341
+ value = self.evaluate_value(
342
+ self.model.scale(bounding_box.regular_grid(local=False, order='F'))
343
+ )
344
+
339
345
  grid.properties[self.name] = value
340
346
 
341
- value = self.evaluate_value(bounding_box.cell_centers(order='F'))
347
+ value = self.evaluate_value(bounding_box.cell_centres(order='F'))
342
348
  grid.cell_properties[self.name] = value
343
349
  return grid
344
350
 
@@ -359,7 +365,7 @@ class BaseFeature(metaclass=ABCMeta):
359
365
  if self.model is None:
360
366
  raise ValueError("Must specify bounding box")
361
367
  bounding_box = self.model.bounding_box
362
- points = bounding_box.cell_centers()
368
+ points = bounding_box.cell_centres()
363
369
  value = self.evaluate_gradient(points)
364
370
  if self.model is not None:
365
371
  points = self.model.rescale(points)
@@ -1,5 +1,4 @@
1
- """
2
- """
1
+ """ """
3
2
 
4
3
  import numpy as np
5
4
  from typing import Optional
@@ -6,7 +6,6 @@ from LoopStructural.utils.maths import regular_tetraherdron_for_points, gradient
6
6
  from ...modelling.features import BaseFeature
7
7
  from ...utils import getLogger
8
8
  from ...modelling.features import FeatureType
9
- from ...interpolators import GeologicalInterpolator
10
9
  import numpy as np
11
10
  from typing import Optional, List, Union
12
11
  from ...datatypes import ValuePoints, VectorPoints
@@ -40,10 +39,10 @@ class GeologicalFeature(BaseFeature):
40
39
  def __init__(
41
40
  self,
42
41
  name: str,
43
- interpolator: GeologicalInterpolator,
44
- builder=None,
42
+ builder,
45
43
  regions: list = [],
46
44
  faults: list = [],
45
+ interpolator=None,
47
46
  model=None,
48
47
  ):
49
48
  """Default constructor for geological feature
@@ -61,8 +60,8 @@ class GeologicalFeature(BaseFeature):
61
60
  """
62
61
  BaseFeature.__init__(self, name, model, faults, regions, builder)
63
62
  self.name = name
64
- self.interpolator = interpolator
65
63
  self.builder = builder
64
+ self.interpolator = self.builder.interpolator if self.builder is not None else interpolator
66
65
  self.type = FeatureType.INTERPOLATED
67
66
 
68
67
  def to_json(self):
@@ -262,7 +261,6 @@ class GeologicalFeature(BaseFeature):
262
261
  regions=[], # feature.regions.copy(), # don't want to share regionsbetween unconformity and # feature.regions,
263
262
  builder=self.builder,
264
263
  model=self.model,
265
- interpolator=self.interpolator,
266
264
  )
267
265
  return feature
268
266
 
@@ -12,7 +12,6 @@ logger = getLogger(__name__)
12
12
 
13
13
 
14
14
  class LambdaGeologicalFeature(BaseFeature):
15
-
16
15
  def __init__(
17
16
  self,
18
17
  function: Optional[Callable[[np.ndarray], np.ndarray]] = None,
@@ -91,3 +90,14 @@ class LambdaGeologicalFeature(BaseFeature):
91
90
 
92
91
  def get_data(self, value_map: Optional[dict] = None):
93
92
  return
93
+
94
+ def copy(self, name: Optional[str] = None):
95
+ return LambdaGeologicalFeature(
96
+ self.function,
97
+ name if name is not None else f'{self.name}_copy',
98
+ self.gradient_function,
99
+ self.model,
100
+ self.regions,
101
+ self.faults,
102
+ self.builder,
103
+ )
@@ -1,5 +1,4 @@
1
- """
2
- """
1
+ """ """
3
2
 
4
3
  import numpy as np
5
4
  from typing import Optional
@@ -24,7 +24,6 @@ class UnconformityFeature(GeologicalFeature):
24
24
  regions=[], # feature.regions.copy(), # don't want to share regionsbetween unconformity and # feature.regions,
25
25
  builder=feature.builder,
26
26
  model=feature.model,
27
- interpolator=feature.interpolator,
28
27
  )
29
28
  self.value = value
30
29
  self.type = FeatureType.UNCONFORMITY if onlap is False else FeatureType.ONLAPUNCONFORMITY
@@ -47,10 +47,12 @@ class BaseBuilder:
47
47
  def build_arguments(self):
48
48
  return self._build_arguments
49
49
 
50
- @build_arguments.setter
51
- def build_arguments(self, build_arguments):
50
+ def update_build_arguments(self, build_arguments):
52
51
  # self._build_arguments = {}
52
+ logger.info(f"Setting build arguments for {self.name}")
53
53
  for k, i in build_arguments.items():
54
+ logger.info(f"Setting {k} to {i} for {self.name}")
55
+ logger.info(f"{k} is currrently {self._build_arguments.get(k, None)}")
54
56
  if i != self._build_arguments.get(k, None):
55
57
  logger.info(f"Setting {k} to {i} for {self.name}")
56
58
  self._build_arguments[k] = i
@@ -25,7 +25,6 @@ from ....modelling.features.builders import BaseBuilder
25
25
  from ....utils.helper import (
26
26
  get_data_bounding_box_map as get_data_bounding_box,
27
27
  )
28
- from ....utils import RegionEverywhere
29
28
  from ....interpolators import DiscreteInterpolator
30
29
  from ....interpolators import InterpolatorFactory
31
30
 
@@ -39,7 +38,6 @@ class GeologicalFeatureBuilder(BaseBuilder):
39
38
  bounding_box,
40
39
  nelements: int = 1000,
41
40
  name="Feature",
42
- interpolation_region=None,
43
41
  model=None,
44
42
  **kwargs,
45
43
  ):
@@ -61,7 +59,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
61
59
  nelements=nelements,
62
60
  buffer=kwargs.get("buffer", 0.2),
63
61
  )
64
-
62
+
65
63
  if not issubclass(type(interpolator), GeologicalInterpolator):
66
64
  raise TypeError(
67
65
  "interpolator is {} and must be a GeologicalInterpolator".format(type(interpolator))
@@ -78,21 +76,17 @@ class GeologicalFeatureBuilder(BaseBuilder):
78
76
  )
79
77
  self.data = pd.DataFrame(columns=header)
80
78
  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
79
 
86
80
  self._feature = GeologicalFeature(
87
81
  self._name,
88
- self._interpolator,
89
82
  builder=self,
90
83
  regions=[],
91
84
  faults=self.faults,
92
85
  )
93
86
  self._orthogonal_features = {}
94
87
  self._equality_constraints = {}
95
-
88
+ # add default parameters
89
+ self.update_build_arguments({'cpw':1.0,'npw':1.0,'regularisation':1.0,'nelements':self.interpolator.n_elements})
96
90
  def set_not_up_to_date(self, caller):
97
91
  logger.info(
98
92
  f"Setting {self.name} to not up to date from an instance of {caller.__class__.__name__}"
@@ -104,20 +98,12 @@ class GeologicalFeatureBuilder(BaseBuilder):
104
98
  def interpolator(self):
105
99
  return self._interpolator
106
100
 
107
- @property
108
- def interpolation_region(self):
109
- return self._interpolation_region
110
-
111
- @interpolation_region.setter
112
- def interpolation_region(self, interpolation_region):
113
- if interpolation_region is not None:
114
- self._interpolation_region = interpolation_region
115
- self._interpolator.set_region(region=self._interpolation_region)
116
- else:
117
- self._interpolation_region = RegionEverywhere()
118
- self._interpolator.set_region(region=self._interpolation_region)
119
- logger.info(f'Setting interpolation region {self.name}')
120
- self._up_to_date = False
101
+ @interpolator.setter
102
+ def interpolator(self, interpolator):
103
+ if not issubclass(type(interpolator), GeologicalInterpolator):
104
+ raise TypeError(
105
+ "interpolator is {} and must be a GeologicalInterpolator".format(type(interpolator))
106
+ )
121
107
 
122
108
  def add_data_from_data_frame(self, data_frame, overwrite=False):
123
109
  """
@@ -183,6 +169,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
183
169
 
184
170
  """
185
171
  if self.data_added:
172
+ logger.info("Data already added to interpolator")
186
173
  return
187
174
  # first move the data for the fault
188
175
  logger.info(f"Adding {len(self.faults)} faults to {self.name}")
@@ -475,7 +462,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
475
462
  is big enough to capture the faulted feature.
476
463
  """
477
464
  if self.interpolator.support is not None:
478
-
465
+ logger.info(f"Checking interpolation geometry for {self.name}")
479
466
  origin = self.interpolator.support.origin
480
467
  maximum = self.interpolator.support.maximum
481
468
  pts = self.model.bounding_box.with_buffer(buffer).regular_grid(local=True)
@@ -488,7 +475,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
488
475
  ]
489
476
  self.interpolator.support.origin = origin
490
477
  self.interpolator.support.maximum = maximum
491
-
478
+ self.update_build_arguments({'nelements':self.interpolator.n_elements})
492
479
  def build(self, data_region=None, **kwargs):
493
480
  """
494
481
  Runs the interpolation and builds the geological feature
@@ -511,6 +498,15 @@ class GeologicalFeatureBuilder(BaseBuilder):
511
498
  for f in self.faults:
512
499
  f.builder.update()
513
500
  domain = kwargs.get("domain", None)
501
+ if 'nelements' in kwargs:
502
+ # if the number of elements has changed then update the interpolator
503
+ logger.info(f'Interpolator has {self.interpolator.n_elements} elements')
504
+ logger.info(f'Kwargs has {kwargs["nelements"]} elements')
505
+ if self.interpolator.n_elements != kwargs['nelements']:
506
+ logger.info('Setting nelements to {} for {}'.format(kwargs['nelements'], self.name))
507
+ self.build_arguments['nelements'] = self.interpolator.set_nelements(kwargs['nelements'])
508
+ logger.info(f'Interpolator nelements {self.interpolator.n_elements}')
509
+ self._up_to_date = False
514
510
  if domain:
515
511
  self.check_interpolation_geometry(None)
516
512
  self.add_data_to_interpolator(**kwargs)