LoopStructural 1.6.2__py3-none-any.whl → 1.6.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of LoopStructural might be problematic. Click here for more details.
- LoopStructural/datatypes/_bounding_box.py +19 -4
- LoopStructural/datatypes/_point.py +36 -2
- LoopStructural/datatypes/_structured_grid.py +17 -0
- LoopStructural/datatypes/_surface.py +17 -0
- LoopStructural/export/omf_wrapper.py +49 -21
- LoopStructural/interpolators/__init__.py +13 -0
- LoopStructural/interpolators/_api.py +81 -13
- LoopStructural/interpolators/_discrete_fold_interpolator.py +11 -4
- LoopStructural/interpolators/_discrete_interpolator.py +100 -53
- LoopStructural/interpolators/_finite_difference_interpolator.py +68 -78
- LoopStructural/interpolators/_geological_interpolator.py +27 -10
- LoopStructural/interpolators/_p1interpolator.py +3 -3
- LoopStructural/interpolators/_surfe_wrapper.py +42 -12
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +16 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +44 -9
- LoopStructural/interpolators/supports/_3d_base_structured.py +24 -7
- LoopStructural/interpolators/supports/_3d_structured_grid.py +38 -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 +2 -4
- LoopStructural/modelling/features/_analytical_feature.py +25 -16
- LoopStructural/modelling/features/_base_geological_feature.py +21 -8
- LoopStructural/modelling/features/_geological_feature.py +47 -11
- LoopStructural/modelling/features/_structural_frame.py +10 -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 +45 -14
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +29 -13
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +5 -0
- LoopStructural/modelling/features/fault/__init__.py +1 -1
- LoopStructural/modelling/features/fault/_fault_function.py +19 -1
- LoopStructural/modelling/features/fault/_fault_segment.py +40 -51
- 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/utils/__init__.py +1 -0
- LoopStructural/utils/_surface.py +11 -4
- LoopStructural/utils/colours.py +26 -0
- LoopStructural/utils/features.py +5 -0
- LoopStructural/utils/maths.py +51 -0
- LoopStructural/version.py +1 -1
- LoopStructural-1.6.5.dist-info/METADATA +146 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.5.dist-info}/RECORD +57 -52
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.5.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.2.dist-info/METADATA +0 -81
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.5.dist-info}/LICENSE +0 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.5.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):
|
|
@@ -412,11 +422,16 @@ class BoundingBox:
|
|
|
412
422
|
def structured_grid(
|
|
413
423
|
self, cell_data: Dict[str, np.ndarray] = {}, vertex_data={}, name: str = "bounding_box"
|
|
414
424
|
):
|
|
425
|
+
# python is passing a reference to the cell_data, vertex_data dicts so we need to
|
|
426
|
+
# copy them to make sure that different instances of StructuredGrid are not sharing the same
|
|
427
|
+
# underlying objects
|
|
428
|
+
_cell_data = copy.deepcopy(cell_data)
|
|
429
|
+
_vertex_data = copy.deepcopy(vertex_data)
|
|
415
430
|
return StructuredGrid(
|
|
416
431
|
origin=self.global_origin,
|
|
417
432
|
step_vector=self.step_vector,
|
|
418
433
|
nsteps=self.nsteps,
|
|
419
|
-
cell_properties=
|
|
420
|
-
properties=
|
|
434
|
+
cell_properties=_cell_data,
|
|
435
|
+
properties=_vertex_data,
|
|
421
436
|
name=name,
|
|
422
437
|
)
|
|
@@ -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
|
|
@@ -29,6 +32,20 @@ class ValuePoints:
|
|
|
29
32
|
points["values"] = self.values
|
|
30
33
|
return points
|
|
31
34
|
|
|
35
|
+
def plot(self, pyvista_kwargs={}):
|
|
36
|
+
"""Calls pyvista plot on the vtk object
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
pyvista_kwargs : dict, optional
|
|
41
|
+
kwargs passed to pyvista.DataSet.plot(), by default {}
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
self.vtk().plot(**pyvista_kwargs)
|
|
45
|
+
return
|
|
46
|
+
except ImportError:
|
|
47
|
+
logger.error("pyvista is required for vtk")
|
|
48
|
+
|
|
32
49
|
def save(self, filename: Union[str, io.StringIO], ext=None):
|
|
33
50
|
if isinstance(filename, io.StringIO):
|
|
34
51
|
if ext is None:
|
|
@@ -106,12 +123,15 @@ class VectorPoints:
|
|
|
106
123
|
def from_dict(self, d):
|
|
107
124
|
return VectorPoints(d['locations'], d['vectors'], d['name'], d.get('properties', None))
|
|
108
125
|
|
|
109
|
-
def vtk(self, geom='arrow', scale=1.0, scale_function=None, tolerance=0.05):
|
|
126
|
+
def vtk(self, geom='arrow', scale=1.0, scale_function=None, normalise=True, tolerance=0.05):
|
|
110
127
|
import pyvista as pv
|
|
111
128
|
|
|
112
129
|
vectors = np.copy(self.vectors)
|
|
130
|
+
if normalise:
|
|
131
|
+
norm = np.linalg.norm(vectors, axis=1)
|
|
132
|
+
vectors[norm > 0, :] /= norm[norm > 0][:, None]
|
|
113
133
|
if scale_function is not None:
|
|
114
|
-
vectors /= np.linalg.norm(vectors, axis=1)[:, None]
|
|
134
|
+
# vectors /= np.linalg.norm(vectors, axis=1)[:, None]
|
|
115
135
|
vectors *= scale_function(self.locations)[:, None]
|
|
116
136
|
points = pv.PolyData(self.locations)
|
|
117
137
|
points.point_data.set_vectors(vectors, 'vectors')
|
|
@@ -123,6 +143,20 @@ class VectorPoints:
|
|
|
123
143
|
# Perform the glyph
|
|
124
144
|
return points.glyph(orient="vectors", geom=geom, tolerance=tolerance)
|
|
125
145
|
|
|
146
|
+
def plot(self, pyvista_kwargs={}):
|
|
147
|
+
"""Calls pyvista plot on the vtk object
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
pyvista_kwargs : dict, optional
|
|
152
|
+
kwargs passed to pyvista.DataSet.plot(), by default {}
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
self.vtk().plot(**pyvista_kwargs)
|
|
156
|
+
return
|
|
157
|
+
except ImportError:
|
|
158
|
+
logger.error("pyvista is required for vtk")
|
|
159
|
+
|
|
126
160
|
def save(self, filename):
|
|
127
161
|
filename = str(filename)
|
|
128
162
|
ext = filename.split('.')[-1]
|
|
@@ -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
|
+
|
|
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)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Piecewise linear interpolator using folds
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Optional
|
|
5
|
+
from typing import Optional, Callable
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
|
|
@@ -64,6 +64,7 @@ class DiscreteFoldInterpolator(PiecewiseLinearInterpolator):
|
|
|
64
64
|
fold_normalisation=1.0,
|
|
65
65
|
fold_norm=1.0,
|
|
66
66
|
step=2,
|
|
67
|
+
mask_fn: Optional[Callable] = None,
|
|
67
68
|
):
|
|
68
69
|
"""
|
|
69
70
|
|
|
@@ -104,6 +105,10 @@ class DiscreteFoldInterpolator(PiecewiseLinearInterpolator):
|
|
|
104
105
|
# calculate element volume for weighting
|
|
105
106
|
vecs = nodes[:, 1:, :] - nodes[:, 0, None, :]
|
|
106
107
|
vol = np.abs(np.linalg.det(vecs)) / 6
|
|
108
|
+
weight = np.ones(self.support.n_elements, dtype=float)
|
|
109
|
+
if mask_fn is not None:
|
|
110
|
+
weight[mask_fn(self.support.barycentre)] = 0
|
|
111
|
+
weight = weight[::step]
|
|
107
112
|
if fold_orientation is not None:
|
|
108
113
|
"""
|
|
109
114
|
dot product between vector in deformed ori plane = 0
|
|
@@ -120,7 +125,7 @@ class DiscreteFoldInterpolator(PiecewiseLinearInterpolator):
|
|
|
120
125
|
B = np.zeros(A.shape[0])
|
|
121
126
|
idc = self.support.get_elements()[element_idx[::step], :]
|
|
122
127
|
self.add_constraints_to_least_squares(
|
|
123
|
-
A, B, idc, w=fold_orientation, name="fold orientation"
|
|
128
|
+
A, B, idc, w=weight * fold_orientation, name="fold orientation"
|
|
124
129
|
)
|
|
125
130
|
|
|
126
131
|
if fold_axis_w is not None:
|
|
@@ -139,7 +144,9 @@ class DiscreteFoldInterpolator(PiecewiseLinearInterpolator):
|
|
|
139
144
|
B = np.zeros(A.shape[0]).tolist()
|
|
140
145
|
idc = self.support.get_elements()[element_idx[::step], :]
|
|
141
146
|
|
|
142
|
-
self.add_constraints_to_least_squares(
|
|
147
|
+
self.add_constraints_to_least_squares(
|
|
148
|
+
A, B, idc, w=weight * fold_axis_w, name="fold axis"
|
|
149
|
+
)
|
|
143
150
|
|
|
144
151
|
if fold_normalisation is not None:
|
|
145
152
|
"""
|
|
@@ -160,7 +167,7 @@ class DiscreteFoldInterpolator(PiecewiseLinearInterpolator):
|
|
|
160
167
|
idc = self.support.get_elements()[element_idx[::step], :]
|
|
161
168
|
|
|
162
169
|
self.add_constraints_to_least_squares(
|
|
163
|
-
A, B, idc, w=fold_normalisation, name="fold normalisation"
|
|
170
|
+
A, B, idc, w=weight * fold_normalisation, name="fold normalisation"
|
|
164
171
|
)
|
|
165
172
|
|
|
166
173
|
if fold_regularisation is not None:
|