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
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
from LoopStructural.utils.exceptions import LoopException
|
|
2
|
-
import numpy as np
|
|
3
|
-
from typing import Optional
|
|
4
|
-
from LoopStructural.interpolators import (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
)
|
|
13
|
-
from LoopStructural.datatypes import BoundingBox
|
|
14
|
-
from LoopStructural.utils.logging import getLogger
|
|
1
|
+
# from LoopStructural.utils.exceptions import LoopException
|
|
2
|
+
# import numpy as np
|
|
3
|
+
# from typing import Optional
|
|
4
|
+
# from LoopStructural.interpolators import (
|
|
5
|
+
# P1Interpolator,
|
|
6
|
+
# P2Interpolator,
|
|
7
|
+
# FiniteDifferenceInterpolator,
|
|
8
|
+
# GeologicalInterpolator,
|
|
9
|
+
# DiscreteFoldInterpolator,
|
|
10
|
+
# StructuredGrid,
|
|
11
|
+
# TetMesh,
|
|
12
|
+
# )
|
|
13
|
+
# from LoopStructural.datatypes import BoundingBox
|
|
14
|
+
# from LoopStructural.utils.logging import getLogger
|
|
15
15
|
|
|
16
|
-
logger = getLogger(__name__)
|
|
16
|
+
# logger = getLogger(__name__)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def get_interpolator(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
) -> GeologicalInterpolator:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
19
|
+
# def get_interpolator(
|
|
20
|
+
# bounding_box: BoundingBox,
|
|
21
|
+
# interpolatortype: str,
|
|
22
|
+
# nelements: int,
|
|
23
|
+
# element_volume: Optional[float] = None,
|
|
24
|
+
# buffer: float = 0.2,
|
|
25
|
+
# dimensions: int = 3,
|
|
26
|
+
# support=None,
|
|
27
|
+
# ) -> GeologicalInterpolator:
|
|
28
|
+
# # add a buffer to the interpolation domain, this is necessary for
|
|
29
|
+
# # faults but also generally a good
|
|
30
|
+
# # idea to avoid boundary problems
|
|
31
|
+
# # buffer = bb[1, :]
|
|
32
|
+
# origin = bounding_box.with_buffer(buffer).origin
|
|
33
|
+
# maximum = bounding_box.with_buffer(buffer).maximum
|
|
34
|
+
# box_vol = np.prod(maximum - origin)
|
|
35
|
+
# if interpolatortype == "PLI":
|
|
36
|
+
# if support is None:
|
|
37
|
+
# if element_volume is None:
|
|
38
|
+
# # nelements /= 5
|
|
39
|
+
# element_volume = box_vol / nelements
|
|
40
|
+
# # calculate the step vector of a regular cube
|
|
41
|
+
# step_vector = np.zeros(3)
|
|
42
|
+
# step_vector[:] = element_volume ** (1.0 / 3.0)
|
|
43
|
+
# # step_vector /= np.array([1,1,2])
|
|
44
|
+
# # number of steps is the length of the box / step vector
|
|
45
|
+
# nsteps = np.ceil((maximum - origin) / step_vector).astype(int)
|
|
46
|
+
# if np.any(np.less(nsteps, 3)):
|
|
47
|
+
# axis_labels = ["x", "y", "z"]
|
|
48
|
+
# for i in range(3):
|
|
49
|
+
# if nsteps[i] < 3:
|
|
50
|
+
# nsteps[i] = 3
|
|
51
|
+
# logger.error(
|
|
52
|
+
# f"Number of steps in direction {axis_labels[i]} is too small, try increasing nelements"
|
|
53
|
+
# )
|
|
54
|
+
# logger.error("Cannot create interpolator: number of steps is too small")
|
|
55
|
+
# raise ValueError("Number of steps too small cannot create interpolator")
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
# support = TetMesh(origin=origin, nsteps=nsteps, step_vector=step_vector)
|
|
58
|
+
# logger.info(
|
|
59
|
+
# "Creating regular tetrahedron mesh with %i elements \n"
|
|
60
|
+
# "for modelling using PLI" % (support.ntetra)
|
|
61
|
+
# )
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
# return P1Interpolator(support)
|
|
64
|
+
# if interpolatortype == "P2":
|
|
65
|
+
# if support is not None:
|
|
66
|
+
# logger.info(
|
|
67
|
+
# "Creating regular tetrahedron mesh with %i elements \n"
|
|
68
|
+
# "for modelling using P2" % (support.ntetra)
|
|
69
|
+
# )
|
|
70
|
+
# return P2Interpolator(support)
|
|
71
|
+
# else:
|
|
72
|
+
# raise ValueError("Cannot create P2 interpolator without support, try using PLI")
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
# if interpolatortype == "FDI":
|
|
75
|
+
# # find the volume of one element
|
|
76
|
+
# if element_volume is None:
|
|
77
|
+
# element_volume = box_vol / nelements
|
|
78
|
+
# # calculate the step vector of a regular cube
|
|
79
|
+
# step_vector = np.zeros(3)
|
|
80
|
+
# step_vector[:] = element_volume ** (1.0 / 3.0)
|
|
81
|
+
# # number of steps is the length of the box / step vector
|
|
82
|
+
# nsteps = np.ceil((maximum - origin) / step_vector).astype(int)
|
|
83
|
+
# if np.any(np.less(nsteps, 3)):
|
|
84
|
+
# logger.error("Cannot create interpolator: number of steps is too small")
|
|
85
|
+
# axis_labels = ["x", "y", "z"]
|
|
86
|
+
# for i in range(3):
|
|
87
|
+
# if nsteps[i] < 3:
|
|
88
|
+
# nsteps[i] = 3
|
|
89
|
+
# # logger.error(
|
|
90
|
+
# # f"Number of steps in direction {axis_labels[i]} is too small, try increasing nelements"
|
|
91
|
+
# # )
|
|
92
|
+
# # raise ValueError("Number of steps too small cannot create interpolator")
|
|
93
|
+
# # create a structured grid using the origin and number of steps
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
# grid = StructuredGrid(origin=origin, nsteps=nsteps, step_vector=step_vector)
|
|
96
|
+
# logger.info(
|
|
97
|
+
# f"Creating regular grid with {grid.n_elements} elements \n" "for modelling using FDI"
|
|
98
|
+
# )
|
|
99
|
+
# return FiniteDifferenceInterpolator(grid)
|
|
100
|
+
# if interpolatortype == "DFI":
|
|
101
|
+
# if element_volume is None:
|
|
102
|
+
# nelements /= 5
|
|
103
|
+
# element_volume = box_vol / nelements
|
|
104
|
+
# # calculate the step vector of a regular cube
|
|
105
|
+
# step_vector = np.zeros(3)
|
|
106
|
+
# step_vector[:] = element_volume ** (1.0 / 3.0)
|
|
107
|
+
# # number of steps is the length of the box / step vector
|
|
108
|
+
# nsteps = np.ceil((maximum - origin) / step_vector).astype(int)
|
|
109
|
+
# # create a structured grid using the origin and number of steps
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
111
|
+
# mesh = TetMesh(origin=origin, nsteps=nsteps, step_vector=step_vector)
|
|
112
|
+
# logger.info(
|
|
113
|
+
# f"Creating regular tetrahedron mesh with {mesh.ntetra} elements \n"
|
|
114
|
+
# "for modelling using DFI"
|
|
115
|
+
# )
|
|
116
|
+
# return DiscreteFoldInterpolator(mesh, None)
|
|
117
|
+
# raise LoopException("No interpolator")
|
|
118
|
+
# # fi interpolatortype == "DFI" and dfi is True:
|
|
119
|
+
# # if element_volume is None:
|
|
120
|
+
# # nelements /= 5
|
|
121
|
+
# # element_volume = box_vol / nelements
|
|
122
|
+
# # # calculate the step vector of a regular cube
|
|
123
|
+
# # step_vector = np.zeros(3)
|
|
124
|
+
# # step_vector[:] = element_volume ** (1.0 / 3.0)
|
|
125
|
+
# # # number of steps is the length of the box / step vector
|
|
126
|
+
# # nsteps = np.ceil((bb[1, :] - bb[0, :]) / step_vector).astype(int)
|
|
127
|
+
# # # create a structured grid using the origin and number of steps
|
|
128
|
+
# # if "meshbuilder" in kwargs:
|
|
129
|
+
# # mesh = kwargs["meshbuilder"].build(bb, nelements)
|
|
130
|
+
# # else:
|
|
131
|
+
# # mesh = kwargs.get(
|
|
132
|
+
# # "mesh",
|
|
133
|
+
# # TetMesh(origin=bb[0, :], nsteps=nsteps, step_vector=step_vector),
|
|
134
|
+
# # )
|
|
135
|
+
# # logger.info(
|
|
136
|
+
# # f"Creating regular tetrahedron mesh with {mesh.ntetra} elements \n"
|
|
137
|
+
# # "for modelling using DFI"
|
|
138
|
+
# # )
|
|
139
|
+
# # return DFI(mesh, kwargs["fold"])
|
|
140
|
+
# # if interpolatortype == "Surfe" or interpolatortype == "surfe":
|
|
141
|
+
# # # move import of surfe to where we actually try and use it
|
|
142
|
+
# # if not surfe:
|
|
143
|
+
# # logger.warning("Cannot import Surfe, try another interpolator")
|
|
144
|
+
# # raise ImportError("Cannot import surfepy, try pip install surfe")
|
|
145
|
+
# # method = kwargs.get("method", "single_surface")
|
|
146
|
+
# # logger.info("Using surfe interpolator")
|
|
147
|
+
# # return SurfeRBFInterpolator(method)
|
|
148
|
+
# # logger.warning("No interpolator")
|
|
149
|
+
# # raise InterpolatorError("Could not create interpolator")
|
|
@@ -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:
|
|
@@ -6,7 +6,6 @@ from abc import abstractmethod
|
|
|
6
6
|
from typing import Callable, Optional, Union
|
|
7
7
|
import logging
|
|
8
8
|
|
|
9
|
-
from time import time
|
|
10
9
|
import numpy as np
|
|
11
10
|
from scipy import sparse # import sparse.coo_matrix, sparse.bmat, sparse.eye
|
|
12
11
|
from ..interpolators import InterpolatorType
|
|
@@ -33,6 +32,7 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
33
32
|
GeologicalInterpolator.__init__(self, data=data, up_to_date=up_to_date)
|
|
34
33
|
self.B = []
|
|
35
34
|
self.support = support
|
|
35
|
+
self.dimensions = support.dimension
|
|
36
36
|
self.c = (
|
|
37
37
|
np.array(c)
|
|
38
38
|
if c is not None and np.array(c).shape[0] == self.support.n_nodes
|
|
@@ -62,7 +62,6 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
62
62
|
self.interpolation_weights = {}
|
|
63
63
|
logger.info("Creating discrete interpolator with {} degrees of freedom".format(self.nx))
|
|
64
64
|
self.type = InterpolatorType.BASE_DISCRETE
|
|
65
|
-
self.c = np.zeros(self.support.n_nodes)
|
|
66
65
|
|
|
67
66
|
@property
|
|
68
67
|
def nx(self) -> int:
|
|
@@ -133,6 +132,28 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
133
132
|
self.up_to_date = False
|
|
134
133
|
self.interpolation_weights[key] = weights[key]
|
|
135
134
|
|
|
135
|
+
def _pre_solve(self):
|
|
136
|
+
"""
|
|
137
|
+
Pre solve function to be run before solving the interpolation
|
|
138
|
+
"""
|
|
139
|
+
self.c = np.zeros(self.support.n_nodes)
|
|
140
|
+
self.c[:] = np.nan
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
def _post_solve(self):
|
|
144
|
+
"""Post solve function(s) to be run after the solver has been called"""
|
|
145
|
+
self.clear_constraints()
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
def clear_constraints(self):
|
|
149
|
+
"""
|
|
150
|
+
Clear the constraints from the interpolator, this makes sure we are not storing
|
|
151
|
+
the constraints after the solver has been run
|
|
152
|
+
"""
|
|
153
|
+
self.constraints = {}
|
|
154
|
+
self.ineq_constraints = {}
|
|
155
|
+
self.equal_constraints = {}
|
|
156
|
+
|
|
136
157
|
def reset(self):
|
|
137
158
|
"""
|
|
138
159
|
Reset the interpolation constraints
|
|
@@ -140,7 +161,7 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
140
161
|
"""
|
|
141
162
|
self.constraints = {}
|
|
142
163
|
self.c_ = 0
|
|
143
|
-
logger.
|
|
164
|
+
logger.info("Resetting interpolation constraints")
|
|
144
165
|
|
|
145
166
|
def add_constraints_to_least_squares(self, A, B, idc, w=1.0, name="undefined"):
|
|
146
167
|
"""
|
|
@@ -181,7 +202,8 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
181
202
|
B = B.reshape((A.shape[0]))
|
|
182
203
|
# w = w.reshape((A.shape[0]))
|
|
183
204
|
# normalise by rows of A
|
|
184
|
-
|
|
205
|
+
# Should this be done? It should make the solution more stable
|
|
206
|
+
length = np.linalg.norm(A, axis=1)
|
|
185
207
|
B[length > 0] /= length[length > 0]
|
|
186
208
|
# going to assume if any are nan they are all nan
|
|
187
209
|
mask = np.any(np.isnan(A), axis=1)
|
|
@@ -193,9 +215,6 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
193
215
|
raise BaseException("w must be a numpy array")
|
|
194
216
|
|
|
195
217
|
if w.shape[0] != A.shape[0]:
|
|
196
|
-
# # make w the same size as A
|
|
197
|
-
# w = np.tile(w,(A.shape[1],1)).T
|
|
198
|
-
# else:
|
|
199
218
|
raise BaseException("Weight array does not match number of constraints")
|
|
200
219
|
if np.any(np.isnan(idc)) or np.any(np.isnan(A)) or np.any(np.isnan(B)):
|
|
201
220
|
logger.warning("Constraints contain nan not adding constraints: {}".format(name))
|
|
@@ -286,31 +305,43 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
286
305
|
self.add_inequality_constraints_to_matrix(a, points[:, 3:5], cols, 'inequality_value')
|
|
287
306
|
|
|
288
307
|
def add_inequality_pairs_constraints(
|
|
289
|
-
self,
|
|
308
|
+
self,
|
|
309
|
+
w: float = 1.0,
|
|
310
|
+
upper_bound=np.finfo(float).eps,
|
|
311
|
+
lower_bound=-np.inf,
|
|
312
|
+
pairs: Optional[list] = None,
|
|
290
313
|
):
|
|
291
314
|
|
|
292
315
|
points = self.get_inequality_pairs_constraints()
|
|
293
316
|
if points.shape[0] > 0:
|
|
294
|
-
|
|
295
317
|
# assemble a list of pairs in the model
|
|
296
318
|
# this will make pairs even across stratigraphic boundaries
|
|
297
319
|
# TODO add option to only add stratigraphic pairs
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
for
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
320
|
+
if not pairs:
|
|
321
|
+
pairs = {}
|
|
322
|
+
k = 0
|
|
323
|
+
for i in np.unique(points[:, self.support.dimension]):
|
|
324
|
+
for j in np.unique(points[:, self.support.dimension]):
|
|
325
|
+
if i == j:
|
|
326
|
+
continue
|
|
327
|
+
if tuple(sorted([i, j])) not in pairs:
|
|
328
|
+
pairs[tuple(sorted([i, j]))] = k
|
|
329
|
+
k += 1
|
|
330
|
+
pairs = list(pairs.keys())
|
|
308
331
|
for pair in pairs:
|
|
309
332
|
upper_points = points[points[:, self.support.dimension] == pair[0]]
|
|
310
333
|
lower_points = points[points[:, self.support.dimension] == pair[1]]
|
|
311
334
|
|
|
312
335
|
upper_interpolation = self.support.get_element_for_location(upper_points)
|
|
313
336
|
lower_interpolation = self.support.get_element_for_location(lower_points)
|
|
337
|
+
if (~upper_interpolation[3]).sum() > 0:
|
|
338
|
+
logger.warning(
|
|
339
|
+
f'Upper points not in mesh {upper_points[~upper_interpolation[3]]}'
|
|
340
|
+
)
|
|
341
|
+
if (~lower_interpolation[3]).sum() > 0:
|
|
342
|
+
logger.warning(
|
|
343
|
+
f'Lower points not in mesh {lower_points[~lower_interpolation[3]]}'
|
|
344
|
+
)
|
|
314
345
|
ij = np.array(
|
|
315
346
|
[
|
|
316
347
|
*np.meshgrid(
|
|
@@ -325,7 +356,7 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
325
356
|
rows = np.arange(0, ij.shape[0], dtype=int)
|
|
326
357
|
rows = np.tile(rows, (upper_interpolation[1].shape[-1], 1)).T
|
|
327
358
|
rows = np.hstack([rows, rows])
|
|
328
|
-
a = upper_interpolation[1][upper_interpolation[3]][ij[:, 0]]
|
|
359
|
+
a = upper_interpolation[1][upper_interpolation[3]][ij[:, 0]]
|
|
329
360
|
a = np.hstack([a, -lower_interpolation[1][lower_interpolation[3]][ij[:, 1]]])
|
|
330
361
|
cols = np.hstack(
|
|
331
362
|
[
|
|
@@ -511,7 +542,7 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
511
542
|
mats.append(c['matrix'])
|
|
512
543
|
bounds.append(c['bounds'])
|
|
513
544
|
if len(mats) == 0:
|
|
514
|
-
return
|
|
545
|
+
return sparse.csr_matrix((0, self.nx), dtype=float), np.zeros((0, 3))
|
|
515
546
|
Q = sparse.vstack(mats)
|
|
516
547
|
bounds = np.vstack(bounds)
|
|
517
548
|
return Q, bounds
|
|
@@ -519,6 +550,7 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
519
550
|
def solve_system(
|
|
520
551
|
self,
|
|
521
552
|
solver: Optional[Union[Callable[[sparse.csr_matrix, np.ndarray], np.ndarray], str]] = None,
|
|
553
|
+
tol: Optional[float] = None,
|
|
522
554
|
solver_kwargs: dict = {},
|
|
523
555
|
) -> bool:
|
|
524
556
|
"""
|
|
@@ -539,19 +571,16 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
539
571
|
True if the interpolation is run
|
|
540
572
|
|
|
541
573
|
"""
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
574
|
+
if not self._pre_solve():
|
|
575
|
+
raise ValueError("Pre solve failed")
|
|
576
|
+
|
|
545
577
|
A, b = self.build_matrix()
|
|
546
578
|
Q, bounds = self.build_inequality_matrix()
|
|
547
579
|
if callable(solver):
|
|
548
580
|
logger.warning('Using custom solver')
|
|
549
581
|
self.c = solver(A.tocsr(), b)
|
|
550
582
|
self.up_to_date = True
|
|
551
|
-
|
|
552
|
-
return True
|
|
553
|
-
## solve with lsmr
|
|
554
|
-
if isinstance(solver, str):
|
|
583
|
+
elif isinstance(solver, str) or solver is None:
|
|
555
584
|
if solver not in ['cg', 'lsmr', 'admm']:
|
|
556
585
|
logger.warning(
|
|
557
586
|
f'Unknown solver {solver} using cg. \n Available solvers are cg and lsmr or a custom solver as a callable function'
|
|
@@ -559,18 +588,29 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
559
588
|
solver = 'cg'
|
|
560
589
|
if solver == 'cg':
|
|
561
590
|
logger.info("Solving using cg")
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
591
|
+
if 'atol' not in solver_kwargs or 'rtol' not in solver_kwargs:
|
|
592
|
+
if tol is not None:
|
|
593
|
+
solver_kwargs['atol'] = tol
|
|
594
|
+
|
|
595
|
+
logger.info(f"Solver kwargs: {solver_kwargs}")
|
|
596
|
+
|
|
597
|
+
res = sparse.linalg.cg(A.T @ A, A.T @ b, **solver_kwargs)
|
|
565
598
|
if res[1] > 0:
|
|
566
599
|
logger.warning(
|
|
567
600
|
f'CG reached iteration limit ({res[1]})and did not converge, check input data. Setting solution to last iteration'
|
|
568
601
|
)
|
|
569
602
|
self.c = res[0]
|
|
570
603
|
self.up_to_date = True
|
|
571
|
-
|
|
604
|
+
|
|
572
605
|
elif solver == 'lsmr':
|
|
573
606
|
logger.info("Solving using lsmr")
|
|
607
|
+
if 'atol' not in solver_kwargs:
|
|
608
|
+
if tol is not None:
|
|
609
|
+
solver_kwargs['atol'] = tol
|
|
610
|
+
if 'btol' not in solver_kwargs:
|
|
611
|
+
if tol is not None:
|
|
612
|
+
solver_kwargs['btol'] = tol
|
|
613
|
+
logger.info(f"Solver kwargs: {solver_kwargs}")
|
|
574
614
|
res = sparse.linalg.lsmr(A, b, **solver_kwargs)
|
|
575
615
|
if res[1] == 1 or res[1] == 4 or res[1] == 2 or res[1] == 5:
|
|
576
616
|
self.c = res[0]
|
|
@@ -585,8 +625,7 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
585
625
|
)
|
|
586
626
|
self.c = res[0]
|
|
587
627
|
self.up_to_date = True
|
|
588
|
-
|
|
589
|
-
return True
|
|
628
|
+
|
|
590
629
|
elif solver == 'admm':
|
|
591
630
|
logger.info("Solving using admm")
|
|
592
631
|
|
|
@@ -601,28 +640,35 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
601
640
|
|
|
602
641
|
try:
|
|
603
642
|
from loopsolver import admm_solve
|
|
643
|
+
|
|
644
|
+
try:
|
|
645
|
+
linsys_solver = solver_kwargs.pop('linsys_solver', 'lsmr')
|
|
646
|
+
res = admm_solve(
|
|
647
|
+
A,
|
|
648
|
+
b,
|
|
649
|
+
Q,
|
|
650
|
+
bounds,
|
|
651
|
+
x0=x0,
|
|
652
|
+
admm_weight=solver_kwargs.pop('admm_weight', 0.01),
|
|
653
|
+
nmajor=solver_kwargs.pop('nmajor', 200),
|
|
654
|
+
linsys_solver_kwargs=solver_kwargs,
|
|
655
|
+
linsys_solver=linsys_solver,
|
|
656
|
+
)
|
|
657
|
+
self.c = res
|
|
658
|
+
self.up_to_date = True
|
|
659
|
+
except ValueError as e:
|
|
660
|
+
logger.error(f"ADMM solver failed: {e}")
|
|
661
|
+
self.up_to_date = False
|
|
604
662
|
except ImportError:
|
|
605
663
|
logger.warning(
|
|
606
664
|
"Cannot import admm solver. Please install loopsolver or use lsmr or cg"
|
|
607
665
|
)
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
bounds,
|
|
615
|
-
x0=x0,
|
|
616
|
-
admm_weight=solver_kwargs.pop('admm_weight', 0.01),
|
|
617
|
-
nmajor=solver_kwargs.pop('nmajor', 200),
|
|
618
|
-
linsys_solver_kwargs=solver_kwargs,
|
|
619
|
-
)
|
|
620
|
-
self.c = res
|
|
621
|
-
self.up_to_date = True
|
|
622
|
-
except ValueError as e:
|
|
623
|
-
logger.error(f"ADMM solver failed: {e}")
|
|
624
|
-
return False
|
|
625
|
-
return False
|
|
666
|
+
self.up_to_date = False
|
|
667
|
+
else:
|
|
668
|
+
logger.error(f"Unknown solver {solver}")
|
|
669
|
+
self.up_to_date = False
|
|
670
|
+
# self._post_solve()
|
|
671
|
+
return self.up_to_date
|
|
626
672
|
|
|
627
673
|
def update(self) -> bool:
|
|
628
674
|
"""
|
|
@@ -641,7 +687,8 @@ class DiscreteInterpolator(GeologicalInterpolator):
|
|
|
641
687
|
return False
|
|
642
688
|
if not self.up_to_date:
|
|
643
689
|
self.setup_interpolator()
|
|
644
|
-
|
|
690
|
+
self.up_to_date = self.solve_system(self.solver)
|
|
691
|
+
return self.up_to_date
|
|
645
692
|
|
|
646
693
|
def evaluate_value(self, locations: np.ndarray) -> np.ndarray:
|
|
647
694
|
"""Evaluate the value of the interpolator at location
|