LoopStructural 1.6.1__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.
- LoopStructural/datatypes/_bounding_box.py +77 -7
- LoopStructural/datatypes/_point.py +67 -7
- 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/_builders.py +141 -141
- LoopStructural/interpolators/_discrete_fold_interpolator.py +11 -4
- LoopStructural/interpolators/_discrete_interpolator.py +100 -53
- LoopStructural/interpolators/_finite_difference_interpolator.py +78 -88
- LoopStructural/interpolators/_geological_interpolator.py +27 -10
- LoopStructural/interpolators/_interpolator_builder.py +55 -0
- LoopStructural/interpolators/_interpolator_factory.py +7 -18
- 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 +28 -7
- LoopStructural/interpolators/supports/_3d_structured_grid.py +38 -12
- 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 -3
- LoopStructural/modelling/core/geological_model.py +11 -12
- LoopStructural/modelling/features/__init__.py +1 -0
- LoopStructural/modelling/features/_analytical_feature.py +48 -18
- LoopStructural/modelling/features/_base_geological_feature.py +37 -8
- LoopStructural/modelling/features/_cross_product_geological_feature.py +7 -0
- LoopStructural/modelling/features/_geological_feature.py +50 -12
- LoopStructural/modelling/features/_projected_vector_feature.py +112 -0
- LoopStructural/modelling/features/_structural_frame.py +16 -18
- LoopStructural/modelling/features/_unconformity_feature.py +3 -3
- LoopStructural/modelling/features/builders/_base_builder.py +8 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +47 -16
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +29 -13
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +7 -2
- LoopStructural/modelling/features/fault/__init__.py +1 -1
- LoopStructural/modelling/features/fault/_fault_function.py +19 -1
- LoopStructural/modelling/features/fault/_fault_function_feature.py +3 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +50 -53
- 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 +47 -26
- LoopStructural/modelling/input/project_file.py +49 -23
- LoopStructural/modelling/intrusions/intrusion_feature.py +3 -0
- LoopStructural/utils/__init__.py +1 -0
- LoopStructural/utils/_surface.py +18 -6
- LoopStructural/utils/_transformation.py +98 -14
- LoopStructural/utils/colours.py +50 -0
- LoopStructural/utils/features.py +5 -0
- LoopStructural/utils/maths.py +53 -1
- LoopStructural/version.py +1 -1
- LoopStructural-1.6.6.dist-info/METADATA +160 -0
- {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.6.dist-info}/RECORD +66 -59
- {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.6.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.6.dist-info}/LICENSE +0 -0
- {LoopStructural-1.6.1.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
|
-
|
|
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,
|
|
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(
|
|
404
|
-
|
|
405
|
-
|
|
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=
|
|
420
|
-
properties=
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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)
|