LoopStructural 1.6.2__py3-none-any.whl → 1.6.6__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 (69) hide show
  1. LoopStructural/datatypes/_bounding_box.py +77 -7
  2. LoopStructural/datatypes/_point.py +67 -7
  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/_builders.py +141 -141
  9. LoopStructural/interpolators/_discrete_fold_interpolator.py +11 -4
  10. LoopStructural/interpolators/_discrete_interpolator.py +100 -53
  11. LoopStructural/interpolators/_finite_difference_interpolator.py +78 -88
  12. LoopStructural/interpolators/_geological_interpolator.py +27 -10
  13. LoopStructural/interpolators/_interpolator_builder.py +55 -0
  14. LoopStructural/interpolators/_interpolator_factory.py +7 -18
  15. LoopStructural/interpolators/_p1interpolator.py +3 -3
  16. LoopStructural/interpolators/_surfe_wrapper.py +42 -12
  17. LoopStructural/interpolators/supports/_2d_base_unstructured.py +16 -0
  18. LoopStructural/interpolators/supports/_2d_structured_grid.py +44 -9
  19. LoopStructural/interpolators/supports/_3d_base_structured.py +28 -7
  20. LoopStructural/interpolators/supports/_3d_structured_grid.py +38 -12
  21. LoopStructural/interpolators/supports/_3d_structured_tetra.py +7 -3
  22. LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +8 -2
  23. LoopStructural/interpolators/supports/__init__.py +7 -0
  24. LoopStructural/interpolators/supports/_base_support.py +7 -0
  25. LoopStructural/modelling/__init__.py +1 -3
  26. LoopStructural/modelling/core/geological_model.py +11 -12
  27. LoopStructural/modelling/features/__init__.py +1 -0
  28. LoopStructural/modelling/features/_analytical_feature.py +48 -18
  29. LoopStructural/modelling/features/_base_geological_feature.py +37 -8
  30. LoopStructural/modelling/features/_cross_product_geological_feature.py +7 -0
  31. LoopStructural/modelling/features/_geological_feature.py +50 -12
  32. LoopStructural/modelling/features/_projected_vector_feature.py +112 -0
  33. LoopStructural/modelling/features/_structural_frame.py +16 -18
  34. LoopStructural/modelling/features/_unconformity_feature.py +3 -3
  35. LoopStructural/modelling/features/builders/_base_builder.py +8 -0
  36. LoopStructural/modelling/features/builders/_folded_feature_builder.py +47 -16
  37. LoopStructural/modelling/features/builders/_geological_feature_builder.py +29 -13
  38. LoopStructural/modelling/features/builders/_structural_frame_builder.py +7 -2
  39. LoopStructural/modelling/features/fault/__init__.py +1 -1
  40. LoopStructural/modelling/features/fault/_fault_function.py +19 -1
  41. LoopStructural/modelling/features/fault/_fault_function_feature.py +3 -0
  42. LoopStructural/modelling/features/fault/_fault_segment.py +50 -53
  43. LoopStructural/modelling/features/fold/__init__.py +1 -2
  44. LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +0 -23
  45. LoopStructural/modelling/features/fold/_foldframe.py +4 -4
  46. LoopStructural/modelling/features/fold/_svariogram.py +81 -46
  47. LoopStructural/modelling/features/fold/fold_function/__init__.py +27 -0
  48. LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +253 -0
  49. LoopStructural/modelling/features/fold/fold_function/_fourier_series_fold_rotation_angle.py +153 -0
  50. LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +46 -0
  51. LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +151 -0
  52. LoopStructural/modelling/input/process_data.py +47 -26
  53. LoopStructural/modelling/input/project_file.py +49 -23
  54. LoopStructural/modelling/intrusions/intrusion_feature.py +3 -0
  55. LoopStructural/utils/__init__.py +1 -0
  56. LoopStructural/utils/_surface.py +17 -5
  57. LoopStructural/utils/_transformation.py +98 -14
  58. LoopStructural/utils/colours.py +50 -0
  59. LoopStructural/utils/features.py +5 -0
  60. LoopStructural/utils/maths.py +51 -0
  61. LoopStructural/version.py +1 -1
  62. LoopStructural-1.6.6.dist-info/METADATA +160 -0
  63. {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/RECORD +66 -59
  64. {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/WHEEL +1 -1
  65. LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
  66. LoopStructural/modelling/features/fold/_fold_rotation_angle.py +0 -149
  67. LoopStructural-1.6.2.dist-info/METADATA +0 -81
  68. {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/LICENSE +0 -0
  69. {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ from LoopStructural.utils.exceptions import LoopValueError
4
4
  from LoopStructural.utils import rng
5
5
  from LoopStructural.datatypes._structured_grid import StructuredGrid
6
6
  import numpy as np
7
+ import copy
7
8
 
8
9
  from LoopStructural.utils.logging import getLogger
9
10
 
@@ -147,7 +148,13 @@ class BoundingBox:
147
148
  ele_vol = box_vol / nelements
148
149
  # calculate the step vector of a regular cube
149
150
  step_vector = np.zeros(self.dimensions)
150
- step_vector[:] = ele_vol ** (1.0 / 3.0)
151
+ if self.dimensions == 2:
152
+ step_vector[:] = ele_vol ** (1.0 / 2.0)
153
+ elif self.dimensions == 3:
154
+ step_vector[:] = ele_vol ** (1.0 / 3.0)
155
+ else:
156
+ logger.warning("Can only set nelements for 2d or 3D bounding box")
157
+ return
151
158
  # number of steps is the length of the box / step vector
152
159
  nsteps = np.ceil((self.maximum - self.origin) / step_vector).astype(int)
153
160
  self.nsteps = nsteps
@@ -269,7 +276,10 @@ class BoundingBox:
269
276
  origin = self.origin - buffer * (self.maximum - self.origin)
270
277
  maximum = self.maximum + buffer * (self.maximum - self.origin)
271
278
  return BoundingBox(
272
- origin=origin, maximum=maximum, global_origin=self.global_origin + origin
279
+ origin=origin,
280
+ maximum=maximum,
281
+ global_origin=self.global_origin + origin,
282
+ dimensions=self.dimensions,
273
283
  )
274
284
 
275
285
  def get_value(self, name):
@@ -400,9 +410,15 @@ class BoundingBox:
400
410
  import pyvista as pv
401
411
  except ImportError:
402
412
  raise ImportError("pyvista is required for vtk support")
403
- x = np.linspace(self.global_origin[0], self.global_maximum[0], self.nsteps[0])
404
- y = np.linspace(self.global_origin[1], self.global_maximum[1], self.nsteps[1])
405
- z = np.linspace(self.global_origin[2], self.global_maximum[2], self.nsteps[2])
413
+ x = np.linspace(
414
+ self.global_origin[0] + self.origin[0], self.global_maximum[0], self.nsteps[0]
415
+ )
416
+ y = np.linspace(
417
+ self.global_origin[1] + self.origin[1], self.global_maximum[1], self.nsteps[1]
418
+ )
419
+ z = np.linspace(
420
+ self.global_origin[2] + self.origin[2], self.global_maximum[2], self.nsteps[2]
421
+ )
406
422
  return pv.RectilinearGrid(
407
423
  x,
408
424
  y,
@@ -412,11 +428,65 @@ class BoundingBox:
412
428
  def structured_grid(
413
429
  self, cell_data: Dict[str, np.ndarray] = {}, vertex_data={}, name: str = "bounding_box"
414
430
  ):
431
+ # python is passing a reference to the cell_data, vertex_data dicts so we need to
432
+ # copy them to make sure that different instances of StructuredGrid are not sharing the same
433
+ # underlying objects
434
+ _cell_data = copy.deepcopy(cell_data)
435
+ _vertex_data = copy.deepcopy(vertex_data)
415
436
  return StructuredGrid(
416
437
  origin=self.global_origin,
417
438
  step_vector=self.step_vector,
418
439
  nsteps=self.nsteps,
419
- cell_properties=cell_data,
420
- properties=vertex_data,
440
+ cell_properties=_cell_data,
441
+ properties=_vertex_data,
421
442
  name=name,
422
443
  )
444
+
445
+ def project(self, xyz):
446
+ """Project a point into the bounding box
447
+
448
+ Parameters
449
+ ----------
450
+ xyz : np.ndarray
451
+ point to project
452
+
453
+ Returns
454
+ -------
455
+ np.ndarray
456
+ projected point
457
+ """
458
+
459
+ return (xyz - self.global_origin) / np.max(
460
+ (self.global_maximum - self.global_origin)
461
+ ) # np.clip(xyz, self.origin, self.maximum)
462
+
463
+ def reproject(self, xyz):
464
+ """Reproject a point from the bounding box to the global space
465
+
466
+ Parameters
467
+ ----------
468
+ xyz : np.ndarray
469
+ point to reproject
470
+
471
+ Returns
472
+ -------
473
+ np.ndarray
474
+ reprojected point
475
+ """
476
+
477
+ return xyz * np.max((self.global_maximum - self.global_origin)) + self.global_origin
478
+
479
+ def __repr__(self):
480
+ return f"BoundingBox({self.origin}, {self.maximum}, {self.nsteps})"
481
+
482
+ def __str__(self):
483
+ return f"BoundingBox({self.origin}, {self.maximum}, {self.nsteps})"
484
+
485
+ def __eq__(self, other):
486
+ if not isinstance(other, BoundingBox):
487
+ return False
488
+ return (
489
+ np.allclose(self.origin, other.origin)
490
+ and np.allclose(self.maximum, other.maximum)
491
+ and np.allclose(self.nsteps, other.nsteps)
492
+ )
@@ -3,6 +3,9 @@ import numpy as np
3
3
 
4
4
  from typing import Optional, Union
5
5
  import io
6
+ from LoopStructural.utils import getLogger
7
+
8
+ logger = getLogger(__name__)
6
9
 
7
10
 
8
11
  @dataclass
@@ -22,13 +25,30 @@ class ValuePoints:
22
25
  ),
23
26
  }
24
27
 
25
- def vtk(self):
28
+ def vtk(self, scalars=None):
26
29
  import pyvista as pv
27
30
 
28
31
  points = pv.PolyData(self.locations)
29
- points["values"] = self.values
32
+ if scalars is not None and len(scalars) == len(self.locations):
33
+ points.point_data['scalars'] = scalars
34
+ else:
35
+ points["values"] = self.values
30
36
  return points
31
37
 
38
+ def plot(self, pyvista_kwargs={}):
39
+ """Calls pyvista plot on the vtk object
40
+
41
+ Parameters
42
+ ----------
43
+ pyvista_kwargs : dict, optional
44
+ kwargs passed to pyvista.DataSet.plot(), by default {}
45
+ """
46
+ try:
47
+ self.vtk().plot(**pyvista_kwargs)
48
+ return
49
+ except ImportError:
50
+ logger.error("pyvista is required for vtk")
51
+
32
52
  def save(self, filename: Union[str, io.StringIO], ext=None):
33
53
  if isinstance(filename, io.StringIO):
34
54
  if ext is None:
@@ -106,22 +126,62 @@ class VectorPoints:
106
126
  def from_dict(self, d):
107
127
  return VectorPoints(d['locations'], d['vectors'], d['name'], d.get('properties', None))
108
128
 
109
- def vtk(self, geom='arrow', scale=1.0, scale_function=None, tolerance=0.05):
129
+ def vtk(
130
+ self,
131
+ geom='arrow',
132
+ scale=0.10,
133
+ scale_function=None,
134
+ normalise=True,
135
+ tolerance=0.05,
136
+ bb=None,
137
+ scalars=None,
138
+ ):
110
139
  import pyvista as pv
111
140
 
141
+ _projected = False
112
142
  vectors = np.copy(self.vectors)
143
+ if normalise:
144
+ norm = np.linalg.norm(vectors, axis=1)
145
+ vectors[norm > 0, :] /= norm[norm > 0][:, None]
113
146
  if scale_function is not None:
114
- vectors /= np.linalg.norm(vectors, axis=1)[:, None]
147
+ # vectors /= np.linalg.norm(vectors, axis=1)[:, None]
115
148
  vectors *= scale_function(self.locations)[:, None]
116
- points = pv.PolyData(self.locations)
149
+ locations = self.locations
150
+ if bb is not None:
151
+ try:
152
+ locations = bb.project(locations)
153
+ _projected = True
154
+ except Exception as e:
155
+ logger.error(f'Failed to project points to bounding box: {e}')
156
+ logger.error('Using unprojected points, this may cause issues with the glyphing')
157
+ points = pv.PolyData(locations)
158
+ if scalars is not None and len(scalars) == len(self.locations):
159
+ points['scalars'] = scalars
117
160
  points.point_data.set_vectors(vectors, 'vectors')
118
161
  if geom == 'arrow':
119
162
  geom = pv.Arrow(scale=scale)
120
163
  elif geom == 'disc':
121
- geom = pv.Disc(inner=0, outer=scale)
164
+ geom = pv.Disc(inner=0, outer=scale).rotate_y(90)
122
165
 
123
166
  # Perform the glyph
124
- return points.glyph(orient="vectors", geom=geom, tolerance=tolerance)
167
+ glyphed = points.glyph(orient="vectors", geom=geom, tolerance=tolerance, scale=False)
168
+ if _projected:
169
+ glyphed.points = bb.reproject(glyphed.points)
170
+ return glyphed
171
+
172
+ def plot(self, pyvista_kwargs={}):
173
+ """Calls pyvista plot on the vtk object
174
+
175
+ Parameters
176
+ ----------
177
+ pyvista_kwargs : dict, optional
178
+ kwargs passed to pyvista.DataSet.plot(), by default {}
179
+ """
180
+ try:
181
+ self.vtk().plot(**pyvista_kwargs)
182
+ return
183
+ except ImportError:
184
+ logger.error("pyvista is required for vtk")
125
185
 
126
186
  def save(self, filename):
127
187
  filename = str(filename)
@@ -1,6 +1,9 @@
1
1
  from typing import Dict
2
2
  import numpy as np
3
3
  from dataclasses import dataclass
4
+ from LoopStructural.utils import getLogger
5
+
6
+ logger = getLogger(__name__)
4
7
 
5
8
 
6
9
  @dataclass
@@ -46,6 +49,20 @@ class StructuredGrid:
46
49
  grid.cell_data[name] = data.flatten(order="F")
47
50
  return grid
48
51
 
52
+ def plot(self, pyvista_kwargs={}):
53
+ """Calls pyvista plot on the vtk object
54
+
55
+ Parameters
56
+ ----------
57
+ pyvista_kwargs : dict, optional
58
+ kwargs passed to pyvista.DataSet.plot(), by default {}
59
+ """
60
+ try:
61
+ self.vtk().plot(**pyvista_kwargs)
62
+ return
63
+ except ImportError:
64
+ logger.error("pyvista is required for vtk")
65
+
49
66
  def merge(self, other):
50
67
  if not np.all(np.isclose(self.origin, other.origin)):
51
68
  raise ValueError("Origin of grids must be the same")
@@ -2,6 +2,9 @@ from dataclasses import dataclass
2
2
  from typing import Optional
3
3
  import numpy as np
4
4
  import io
5
+ from LoopStructural.utils import getLogger
6
+
7
+ logger = getLogger(__name__)
5
8
 
6
9
 
7
10
  @dataclass
@@ -85,6 +88,20 @@ class Surface:
85
88
  surface.cell_data[k] = np.array(v)
86
89
  return surface
87
90
 
91
+ def plot(self, pyvista_kwargs={}):
92
+ """Calls pyvista plot on the vtk object
93
+
94
+ Parameters
95
+ ----------
96
+ pyvista_kwargs : dict, optional
97
+ kwargs passed to pyvista.DataSet.plot(), by default {}
98
+ """
99
+ try:
100
+ self.vtk().plot(**pyvista_kwargs)
101
+ return
102
+ except ImportError:
103
+ logger.error("pyvista is required for vtk")
104
+
88
105
  def to_dict(self, flatten=False):
89
106
  triangles = self.triangles
90
107
  vertices = self.vertices
@@ -3,35 +3,55 @@ try:
3
3
  except ImportError:
4
4
  raise ImportError(
5
5
  "You need to install the omf package to use this feature. "
6
- "You can install it with: pip install --pre omf"
6
+ "You can install it with: pip install mira-omf"
7
7
  )
8
+ import numpy as np
9
+ import datetime
10
+ import os
8
11
 
9
12
 
10
13
  def get_project(filename):
11
- try:
12
- project = omf.load(filename)
13
- except FileNotFoundError:
14
- project = omf.Project(name='LoopStructural Model')
15
- return project
14
+ if os.path.exists(filename):
15
+
16
+ try:
17
+ reader = omf.OMFReader(filename)
18
+ project = reader.get_project()
19
+ except (FileNotFoundError, ValueError):
20
+ project = omf.Project(name='LoopStructural Model')
21
+ return project
22
+ else:
23
+ return omf.Project(name='LoopStructural Model')
16
24
 
17
25
 
18
26
  def get_cell_attributes(loopobject):
19
27
  attributes = []
20
28
  if loopobject.cell_properties:
21
- attributes += [
22
- omf.NumericAttribute(name=k, array=v, location="faces")
23
- for k, v in loopobject.cell_properties.items()
24
- ]
29
+ for k, v in loopobject.cell_properties.items():
30
+ v = np.array(v)
31
+ if len(v.shape) > 1 and v.shape[1] > 1:
32
+ for i in range(v.shape[1]):
33
+ attributes.append(
34
+ omf.ScalarData(name=f'{k}_{i}', array=v[:, i], location="faces")
35
+ )
36
+ else:
37
+ attributes.append(omf.ScalarData(name=k, array=v, location="faces"))
38
+
25
39
  return attributes
26
40
 
27
41
 
28
42
  def get_point_attributed(loopobject):
29
43
  attributes = []
30
44
  if loopobject.properties:
31
- attributes += [
32
- omf.NumericAttribute(name=k, array=v, location="vertices")
33
- for k, v in loopobject.properties.items()
34
- ]
45
+ for k, v in loopobject.properties.items():
46
+ v = np.array(v)
47
+ if len(v.shape) > 1 and v.shape[1] > 1:
48
+ for i in range(v.shape[1]):
49
+ attributes.append(
50
+ omf.ScalarData(name=f'{k}_{i}', array=v[:, i], location="vertices")
51
+ )
52
+ else:
53
+ attributes.append(omf.ScalarData(name=k, array=v, location="vertices"))
54
+
35
55
  return attributes
36
56
 
37
57
 
@@ -40,16 +60,24 @@ def add_surface_to_omf(surface, filename):
40
60
  attributes = []
41
61
  attributes += get_cell_attributes(surface)
42
62
  attributes += get_point_attributed(surface)
43
- surface = omf.Surface(
44
- vertices=surface.vertices,
45
- triangles=surface.triangles,
46
- attributes=attributes,
63
+ surface = omf.SurfaceElement(
64
+ geometry=omf.SurfaceGeometry(
65
+ vertices=surface.vertices,
66
+ triangles=surface.triangles,
67
+ ),
68
+ data=attributes,
47
69
  name=surface.name,
48
70
  )
49
71
  project = get_project(filename)
50
72
 
51
73
  project.elements += [surface]
52
- omf.save(project, filename, mode='w')
74
+ project.metadata = {
75
+ "coordinate_reference_system": "epsg 3857",
76
+ "date_created": datetime.datetime.utcnow(),
77
+ "version": "v1.3",
78
+ "revision": "10",
79
+ }
80
+ omf.OMFWriter(project, filename)
53
81
 
54
82
 
55
83
  def add_pointset_to_omf(points, filename):
@@ -57,7 +85,7 @@ def add_pointset_to_omf(points, filename):
57
85
  attributes = []
58
86
  attributes += get_point_attributed(points)
59
87
 
60
- points = omf.PointSet(
88
+ points = omf.PointSetElement(
61
89
  vertices=points.locations,
62
90
  attributes=attributes,
63
91
  name=points.name,
@@ -65,7 +93,7 @@ def add_pointset_to_omf(points, filename):
65
93
 
66
94
  project = get_project(filename)
67
95
  project.elements += [points]
68
- omf.save(project, filename, mode='w')
96
+ omf.OMFWriter(project, filename)
69
97
 
70
98
 
71
99
  def add_structured_grid_to_omf(grid, filename):
@@ -52,6 +52,7 @@ interpolator_string_map = {
52
52
  "P2": InterpolatorType.PIECEWISE_QUADRATIC,
53
53
  "P1": InterpolatorType.PIECEWISE_LINEAR,
54
54
  "DFI": InterpolatorType.DISCRETE_FOLD,
55
+ 'surfe': InterpolatorType.SURFE,
55
56
  }
56
57
  from ..interpolators._geological_interpolator import GeologicalInterpolator
57
58
  from ..interpolators._discrete_interpolator import DiscreteInterpolator
@@ -79,6 +80,11 @@ from ..interpolators._discrete_fold_interpolator import (
79
80
  from ..interpolators._p2interpolator import P2Interpolator
80
81
  from ..interpolators._p1interpolator import P1Interpolator
81
82
 
83
+ try:
84
+ from ..interpolators._surfe_wrapper import SurfeRBFInterpolator
85
+ except ImportError:
86
+ logger.warning("Surfe is not installed, SurfeRBFInterpolator will not be available")
87
+ SurfeRBFInterpolator = None
82
88
  interpolator_map = {
83
89
  InterpolatorType.BASE: GeologicalInterpolator,
84
90
  InterpolatorType.BASE_DISCRETE: DiscreteInterpolator,
@@ -87,6 +93,7 @@ interpolator_map = {
87
93
  InterpolatorType.PIECEWISE_LINEAR: P1Interpolator,
88
94
  InterpolatorType.PIECEWISE_QUADRATIC: P2Interpolator,
89
95
  InterpolatorType.BASE_DATA_SUPPORTED: GeologicalInterpolator,
96
+ InterpolatorType.SURFE: SurfeRBFInterpolator,
90
97
  }
91
98
 
92
99
  support_interpolator_map = {
@@ -100,6 +107,12 @@ support_interpolator_map = {
100
107
  3: SupportType.P2UnstructuredTetMesh,
101
108
  2: SupportType.P2Unstructured2d,
102
109
  },
110
+ InterpolatorType.SURFE: {
111
+ 3: SupportType.DataSupported,
112
+ 2: SupportType.DataSupported,
113
+ },
103
114
  }
104
115
 
105
116
  from ._interpolator_factory import InterpolatorFactory
117
+ from ._interpolator_builder import InterpolatorBuilder
118
+ # from ._api import LoopInterpolator
@@ -19,6 +19,8 @@ class LoopInterpolator:
19
19
  dimensions: int = 3,
20
20
  type=InterpolatorType.FINITE_DIFFERENCE,
21
21
  nelements: int = 1000,
22
+ interpolator_setup_kwargs={},
23
+ buffer: float = 0.2,
22
24
  ):
23
25
  """Scikitlearn like interface for LoopStructural interpolators
24
26
  useful for quickly building an interpolator to apply to a dataset
@@ -37,21 +39,22 @@ class LoopInterpolator:
37
39
  nelements : int, optional
38
40
  degrees of freedom for interpolator, by default 1000
39
41
  """
42
+ logger.warning("LoopInterpolator is experimental and the API is subject to change")
40
43
  self.dimensions = dimensions
41
44
  self.type = "FDI"
42
45
  self.bounding_box = bounding_box
43
46
  self.interpolator: GeologicalInterpolator = InterpolatorFactory.create_interpolator(
44
- type,
45
- bounding_box,
46
- nelements,
47
+ type, bounding_box, nelements, buffer=buffer
47
48
  )
49
+ self.interpolator_setup_kwargs = interpolator_setup_kwargs
48
50
 
49
51
  def fit(
50
52
  self,
51
53
  values: Optional[np.ndarray] = None,
52
54
  tangent_vectors: Optional[np.ndarray] = None,
53
55
  normal_vectors: Optional[np.ndarray] = None,
54
- inequality_constraints: Optional[np.ndarray] = None,
56
+ inequality_value_constraints: Optional[np.ndarray] = None,
57
+ inequality_pairs_constraints: Optional[np.ndarray] = None,
55
58
  ):
56
59
  """_summary_
57
60
 
@@ -66,16 +69,18 @@ class LoopInterpolator:
66
69
  inequality_constraints : Optional[np.ndarray], optional
67
70
  _description_, by default None
68
71
  """
72
+
69
73
  if values is not None:
70
74
  self.interpolator.set_value_constraints(values)
71
75
  if tangent_vectors is not None:
72
76
  self.interpolator.set_tangent_constraints(tangent_vectors)
73
77
  if normal_vectors is not None:
74
78
  self.interpolator.set_normal_constraints(normal_vectors)
75
- if inequality_constraints:
76
- pass
77
-
78
- self.interpolator.setup()
79
+ if inequality_value_constraints is not None:
80
+ self.interpolator.set_value_inequality_constraints(inequality_value_constraints)
81
+ if inequality_pairs_constraints is not None:
82
+ self.interpolator.set_inequality_pairs_constraints(inequality_pairs_constraints)
83
+ self.interpolator.setup(**self.interpolator_setup_kwargs)
79
84
 
80
85
  def evaluate_scalar_value(self, locations: np.ndarray) -> np.ndarray:
81
86
  """Evaluate the value of the interpolator at locations
@@ -114,30 +119,93 @@ class LoopInterpolator:
114
119
  values: Optional[np.ndarray] = None,
115
120
  tangent_vectors: Optional[np.ndarray] = None,
116
121
  normal_vectors: Optional[np.ndarray] = None,
117
- inequality_constraints: Optional[np.ndarray] = None,
122
+ inequality_value_constraints: Optional[np.ndarray] = None,
123
+ inequality_pairs_constraints: Optional[np.ndarray] = None,
118
124
  ):
119
125
  # get locations
120
126
  self.fit(
121
127
  values=values,
122
128
  tangent_vectors=tangent_vectors,
123
129
  normal_vectors=normal_vectors,
124
- inequality_constraints=inequality_constraints,
130
+ inequality_value_constraints=inequality_value_constraints,
131
+ inequality_pairs_constraints=inequality_pairs_constraints,
125
132
  )
126
133
  locations = self.interpolator.get_data_locations()
127
- return self.evalute_scalar_value(locations)
134
+ return self.evaluate_scalar_value(locations)
128
135
 
129
136
  def fit_and_evaluate_gradient(
130
137
  self,
131
138
  values: Optional[np.ndarray] = None,
132
139
  tangent_vectors: Optional[np.ndarray] = None,
133
140
  normal_vectors: Optional[np.ndarray] = None,
134
- inequality_constraints: Optional[np.ndarray] = None,
141
+ inequality_value_constraints: Optional[np.ndarray] = None,
142
+ inequality_pairs_constraints: Optional[np.ndarray] = None,
135
143
  ):
136
144
  self.fit(
137
145
  values=values,
138
146
  tangent_vectors=tangent_vectors,
139
147
  normal_vectors=normal_vectors,
140
- inequality_constraints=inequality_constraints,
148
+ inequality_value_constraints=inequality_value_constraints,
149
+ inequality_pairs_constraints=inequality_pairs_constraints,
141
150
  )
142
151
  locations = self.interpolator.get_data_locations()
143
152
  return self.evaluate_gradient(locations)
153
+
154
+ def fit_and_evaluate_value_and_gradient(
155
+ self,
156
+ values: Optional[np.ndarray] = None,
157
+ tangent_vectors: Optional[np.ndarray] = None,
158
+ normal_vectors: Optional[np.ndarray] = None,
159
+ inequality_value_constraints: Optional[np.ndarray] = None,
160
+ inequality_pairs_constraints: Optional[np.ndarray] = None,
161
+ ):
162
+ self.fit(
163
+ values=values,
164
+ tangent_vectors=tangent_vectors,
165
+ normal_vectors=normal_vectors,
166
+ inequality_value_constraints=inequality_value_constraints,
167
+ inequality_pairs_constraints=inequality_pairs_constraints,
168
+ )
169
+ locations = self.interpolator.get_data_locations()
170
+ return self.evaluate_scalar_value(locations), self.evaluate_gradient(locations)
171
+
172
+ def plot(self, ax=None, **kwargs):
173
+ """Plots a 2d map scalar field or 3d pyvista plot
174
+
175
+ Parameters
176
+ ----------
177
+ ax : matplotlib axes, optional
178
+ The axes you want to add the plot to, otherwise it will make one, by default None
179
+
180
+ Returns
181
+ -------
182
+ _type_
183
+ _description_
184
+ """
185
+ if self.dimensions == 3:
186
+ vtkgrid = self.interpolator.support.vtk()
187
+ vtkgrid['val'] = self.interpolator.c
188
+ vtkgrid.plot(**kwargs)
189
+ return vtkgrid
190
+ elif self.dimensions == 2:
191
+ if ax is None:
192
+ import matplotlib.pyplot as plt
193
+
194
+ fig, ax = plt.subplots()
195
+ val = self.interpolator.c
196
+ val = np.rot90(val.reshape(self.interpolator.support.nsteps, order='F'), 3)
197
+ ax.imshow(
198
+ val,
199
+ origin='lower',
200
+ extent=[
201
+ self.bounding_box.origin[0],
202
+ self.bounding_box.maximum[0],
203
+ self.bounding_box.origin[1],
204
+ self.bounding_box.maximum[1],
205
+ ],
206
+ **kwargs,
207
+ )
208
+ return val, ax
209
+
210
+ # def isovalue(self, value: float, **kwargs):
211
+ # self.interpolator.isovalue(value, **kwargs)