LoopStructural 1.6.1__py3-none-any.whl → 1.6.3__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 +31 -0
- 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 -9
- 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 -1
- LoopStructural/modelling/core/geological_model.py +0 -2
- LoopStructural/modelling/features/_analytical_feature.py +25 -16
- LoopStructural/modelling/features/_geological_feature.py +47 -11
- 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 +15 -49
- 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 +6 -0
- LoopStructural/modelling/input/project_file.py +24 -3
- LoopStructural/utils/_surface.py +6 -3
- LoopStructural/utils/colours.py +26 -0
- LoopStructural/utils/features.py +5 -0
- LoopStructural/utils/maths.py +53 -1
- LoopStructural/version.py +1 -1
- LoopStructural-1.6.3.dist-info/METADATA +146 -0
- {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.dist-info}/RECORD +53 -48
- {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.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.3.dist-info}/LICENSE +0 -0
- {LoopStructural-1.6.1.dist-info → LoopStructural-1.6.3.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
@@ -8,8 +8,6 @@ from ..utils import get_vectors
|
|
|
8
8
|
from ._discrete_interpolator import DiscreteInterpolator
|
|
9
9
|
from ..interpolators import InterpolatorType
|
|
10
10
|
|
|
11
|
-
from ._operator import Operator
|
|
12
|
-
|
|
13
11
|
from LoopStructural.utils import getLogger
|
|
14
12
|
|
|
15
13
|
logger = getLogger(__name__)
|
|
@@ -26,13 +24,11 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
26
24
|
"""
|
|
27
25
|
self.shape = "rectangular"
|
|
28
26
|
DiscreteInterpolator.__init__(self, grid, data=data)
|
|
29
|
-
# default weights for the interpolation matrix are 1 in x,y,z and
|
|
30
|
-
# 1/
|
|
31
27
|
self.set_interpolation_weights(
|
|
32
28
|
{
|
|
33
|
-
"dxy": 0
|
|
34
|
-
"dyz": 0
|
|
35
|
-
"dxz": 0
|
|
29
|
+
"dxy": 1.0,
|
|
30
|
+
"dyz": 1.0,
|
|
31
|
+
"dxz": 1.0,
|
|
36
32
|
"dxx": 1.0,
|
|
37
33
|
"dyy": 1.0,
|
|
38
34
|
"dzz": 1.0,
|
|
@@ -47,7 +43,6 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
47
43
|
}
|
|
48
44
|
)
|
|
49
45
|
|
|
50
|
-
# grid.step_vector[2]
|
|
51
46
|
self.type = InterpolatorType.FINITE_DIFFERENCE
|
|
52
47
|
|
|
53
48
|
def setup_interpolator(self, **kwargs):
|
|
@@ -88,43 +83,24 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
88
83
|
self.interpolation_weights["dyy"] = 0.1 * kwargs["regularisation"]
|
|
89
84
|
self.interpolation_weights["dzz"] = 0.1 * kwargs["regularisation"]
|
|
90
85
|
self.interpolation_weights[key] = kwargs[key]
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
weight = (
|
|
99
|
-
self.interpolation_weights["dxy"] / 4
|
|
100
|
-
) # (4*self.support.step_vector[0]*self.support.step_vector[1])
|
|
101
|
-
self.assemble_inner(operator, weight)
|
|
102
|
-
operator = Operator.Dyz_mask
|
|
103
|
-
weight = (
|
|
104
|
-
self.interpolation_weights["dyz"] / 4
|
|
105
|
-
) # (4*self.support.step_vector[1]*self.support.step_vector[2])
|
|
106
|
-
self.assemble_inner(operator, weight)
|
|
107
|
-
operator = Operator.Dxz_mask
|
|
108
|
-
weight = (
|
|
109
|
-
self.interpolation_weights["dxz"] / 4
|
|
110
|
-
) # (4*self.support.step_vector[0]*self.support.step_vector[2])
|
|
111
|
-
self.assemble_inner(operator, weight)
|
|
112
|
-
operator = Operator.Dxx_mask
|
|
113
|
-
weight = self.interpolation_weights["dxx"] / 1 # self.support.step_vector[0]**2
|
|
114
|
-
self.assemble_inner(operator, weight)
|
|
115
|
-
operator = Operator.Dyy_mask
|
|
116
|
-
weight = self.interpolation_weights["dyy"] / 1 # self.support.step_vector[1]**2
|
|
117
|
-
self.assemble_inner(operator, weight)
|
|
118
|
-
operator = Operator.Dzz_mask
|
|
119
|
-
weight = self.interpolation_weights["dzz"] / 1 # self.support.step_vector[2]**2
|
|
120
|
-
self.assemble_inner(operator, weight)
|
|
86
|
+
# either use the default operators or the ones passed to the function
|
|
87
|
+
operators = kwargs.get(
|
|
88
|
+
"operators", self.support.get_operators(weights=self.interpolation_weights)
|
|
89
|
+
)
|
|
90
|
+
for k, o in operators.items():
|
|
91
|
+
self.assemble_inner(o[0], o[1], name=k)
|
|
92
|
+
|
|
121
93
|
self.add_norm_constraints(self.interpolation_weights["npw"])
|
|
122
94
|
self.add_gradient_constraints(self.interpolation_weights["gpw"])
|
|
123
95
|
self.add_value_constraints(self.interpolation_weights["cpw"])
|
|
124
96
|
self.add_tangent_constraints(self.interpolation_weights["tpw"])
|
|
125
97
|
self.add_interface_constraints(self.interpolation_weights["ipw"])
|
|
126
98
|
self.add_value_inequality_constraints()
|
|
127
|
-
self.add_inequality_pairs_constraints(
|
|
99
|
+
self.add_inequality_pairs_constraints(
|
|
100
|
+
kwargs.get('inequality_pairs', None),
|
|
101
|
+
upper_bound=kwargs.get('inequality_pair_upper_bound', np.finfo(float).eps),
|
|
102
|
+
lower_bound=kwargs.get('inequality_pair_lower_bound', -np.inf),
|
|
103
|
+
)
|
|
128
104
|
|
|
129
105
|
def copy(self):
|
|
130
106
|
"""
|
|
@@ -167,9 +143,9 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
167
143
|
# a/=self.support.enp.product(self.support.step_vector)
|
|
168
144
|
self.add_constraints_to_least_squares(
|
|
169
145
|
a,
|
|
170
|
-
points[inside,
|
|
146
|
+
points[inside, self.support.dimension],
|
|
171
147
|
idc[inside, :],
|
|
172
|
-
w=w * points[inside,
|
|
148
|
+
w=w * points[inside, self.support.dimension + 1],
|
|
173
149
|
name="value",
|
|
174
150
|
)
|
|
175
151
|
if np.sum(inside) <= 0:
|
|
@@ -206,8 +182,13 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
206
182
|
# A *= vol[:,None]
|
|
207
183
|
idc = tetras[inside, :]
|
|
208
184
|
|
|
209
|
-
for unique_id in np.unique(
|
|
210
|
-
|
|
185
|
+
for unique_id in np.unique(
|
|
186
|
+
points[
|
|
187
|
+
np.logical_and(~np.isnan(points[:, self.support.dimension]), inside),
|
|
188
|
+
self.support.dimension,
|
|
189
|
+
]
|
|
190
|
+
):
|
|
191
|
+
mask = points[inside, self.support.dimension] == unique_id
|
|
211
192
|
ij = np.array(
|
|
212
193
|
np.meshgrid(
|
|
213
194
|
np.arange(0, A[mask, :].shape[0]),
|
|
@@ -250,7 +231,9 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
250
231
|
# calculate unit vector for orientation data
|
|
251
232
|
# points[:,3:]/=np.linalg.norm(points[:,3:],axis=1)[:,None]
|
|
252
233
|
|
|
253
|
-
node_idx, inside = self.support.position_to_cell_corners(
|
|
234
|
+
node_idx, inside = self.support.position_to_cell_corners(
|
|
235
|
+
points[:, : self.support.dimension]
|
|
236
|
+
)
|
|
254
237
|
# calculate unit vector for node gradients
|
|
255
238
|
# this means we are only constraining direction of grad not the
|
|
256
239
|
# magnitude
|
|
@@ -267,13 +250,22 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
267
250
|
T,
|
|
268
251
|
elements,
|
|
269
252
|
inside_,
|
|
270
|
-
) = self.support.get_element_gradient_for_location(
|
|
253
|
+
) = self.support.get_element_gradient_for_location(
|
|
254
|
+
points[inside, : self.support.dimension]
|
|
255
|
+
)
|
|
271
256
|
# normalise constraint vector and scale element matrix by this
|
|
272
|
-
norm = np.linalg.norm(
|
|
257
|
+
norm = np.linalg.norm(
|
|
258
|
+
points[:, self.support.dimension : self.support.dimension + self.support.dimension],
|
|
259
|
+
axis=1,
|
|
260
|
+
)
|
|
273
261
|
points[:, 3:6] /= norm[:, None]
|
|
274
262
|
T /= norm[inside, None, None]
|
|
275
263
|
# calculate two orthogonal vectors to constraint (strike and dip vector)
|
|
276
|
-
strike_vector, dip_vector = get_vectors(
|
|
264
|
+
strike_vector, dip_vector = get_vectors(
|
|
265
|
+
points[
|
|
266
|
+
inside, self.support.dimension : self.support.dimension + self.support.dimension
|
|
267
|
+
]
|
|
268
|
+
)
|
|
277
269
|
A = np.einsum("ij,ijk->ik", strike_vector.T, T)
|
|
278
270
|
B = np.zeros(points[inside, :].shape[0])
|
|
279
271
|
self.add_constraints_to_least_squares(A, B, idc[inside, :], w=w, name="gradient")
|
|
@@ -302,7 +294,9 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
302
294
|
if points.shape[0] > 0:
|
|
303
295
|
# calculate unit vector for orientation data
|
|
304
296
|
# points[:,3:]/=np.linalg.norm(points[:,3:],axis=1)[:,None]
|
|
305
|
-
node_idx, inside = self.support.position_to_cell_corners(
|
|
297
|
+
node_idx, inside = self.support.position_to_cell_corners(
|
|
298
|
+
points[:, : self.support.dimension]
|
|
299
|
+
)
|
|
306
300
|
gi = np.zeros(self.support.n_nodes)
|
|
307
301
|
gi[:] = -1
|
|
308
302
|
gi[self.region] = np.arange(0, self.nx)
|
|
@@ -319,31 +313,23 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
319
313
|
T,
|
|
320
314
|
elements,
|
|
321
315
|
inside_,
|
|
322
|
-
) = self.support.get_element_gradient_for_location(
|
|
316
|
+
) = self.support.get_element_gradient_for_location(
|
|
317
|
+
points[inside, : self.support.dimension]
|
|
318
|
+
)
|
|
323
319
|
# T*=np.product(self.support.step_vector)
|
|
324
320
|
# T/=self.support.step_vector[0]
|
|
321
|
+
|
|
325
322
|
w /= 3
|
|
326
|
-
self.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
idc[inside, :],
|
|
337
|
-
w=w,
|
|
338
|
-
name="norm",
|
|
339
|
-
)
|
|
340
|
-
self.add_constraints_to_least_squares(
|
|
341
|
-
T[:, 2, :],
|
|
342
|
-
points[inside, 5],
|
|
343
|
-
idc[inside, :],
|
|
344
|
-
w=w,
|
|
345
|
-
name="norm",
|
|
346
|
-
)
|
|
323
|
+
for d in range(self.support.dimension):
|
|
324
|
+
|
|
325
|
+
self.add_constraints_to_least_squares(
|
|
326
|
+
T[:, d, :],
|
|
327
|
+
points[inside, self.support.dimension + d],
|
|
328
|
+
idc[inside, :],
|
|
329
|
+
w=w,
|
|
330
|
+
name=f"norm_{d}",
|
|
331
|
+
)
|
|
332
|
+
|
|
347
333
|
if np.sum(inside) <= 0:
|
|
348
334
|
logger.warning(
|
|
349
335
|
f"{np.sum(~inside)} \
|
|
@@ -356,7 +342,7 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
356
342
|
points: np.ndarray,
|
|
357
343
|
vectors: np.ndarray,
|
|
358
344
|
w: float = 1.0,
|
|
359
|
-
|
|
345
|
+
b: float = 0,
|
|
360
346
|
name="gradient orthogonal",
|
|
361
347
|
):
|
|
362
348
|
"""
|
|
@@ -378,7 +364,9 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
378
364
|
if points.shape[0] > 0:
|
|
379
365
|
|
|
380
366
|
# calculate unit vector for orientation data
|
|
381
|
-
node_idx, inside = self.support.position_to_cell_corners(
|
|
367
|
+
node_idx, inside = self.support.position_to_cell_corners(
|
|
368
|
+
points[:, : self.support.dimension]
|
|
369
|
+
)
|
|
382
370
|
# calculate unit vector for node gradients
|
|
383
371
|
# this means we are only constraining direction of grad not the
|
|
384
372
|
# magnitude
|
|
@@ -400,13 +388,15 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
400
388
|
T,
|
|
401
389
|
elements,
|
|
402
390
|
inside_,
|
|
403
|
-
) = self.support.get_element_gradient_for_location(
|
|
391
|
+
) = self.support.get_element_gradient_for_location(
|
|
392
|
+
points[inside, : self.support.dimension]
|
|
393
|
+
)
|
|
404
394
|
T[norm > 0, :, :] /= norm[norm > 0, None, None]
|
|
405
395
|
|
|
406
396
|
# dot product of vector and element gradient = 0
|
|
407
|
-
A = np.einsum("ij,ijk->ik", vectors[inside, :
|
|
408
|
-
|
|
409
|
-
self.add_constraints_to_least_squares(A,
|
|
397
|
+
A = np.einsum("ij,ijk->ik", vectors[inside, : self.support.dimension], T)
|
|
398
|
+
b_ = np.zeros(points[inside, :].shape[0]) + b
|
|
399
|
+
self.add_constraints_to_least_squares(A, b_, idc[inside, :], w=w, name=name)
|
|
410
400
|
|
|
411
401
|
if np.sum(inside) <= 0:
|
|
412
402
|
logger.warning(
|
|
@@ -432,7 +422,7 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
432
422
|
|
|
433
423
|
# def assemble_borders(self, operator, w):
|
|
434
424
|
|
|
435
|
-
def assemble_inner(self, operator, w):
|
|
425
|
+
def assemble_inner(self, operator, w, name='regularisation'):
|
|
436
426
|
"""
|
|
437
427
|
|
|
438
428
|
Parameters
|
|
@@ -465,6 +455,6 @@ class FiniteDifferenceInterpolator(DiscreteInterpolator):
|
|
|
465
455
|
B[inside],
|
|
466
456
|
idc[inside, :],
|
|
467
457
|
w=w,
|
|
468
|
-
name=
|
|
458
|
+
name=name,
|
|
469
459
|
)
|
|
470
460
|
return
|
|
@@ -7,6 +7,7 @@ from LoopStructural.utils.exceptions import LoopTypeError
|
|
|
7
7
|
from ..interpolators import InterpolatorType
|
|
8
8
|
import numpy as np
|
|
9
9
|
|
|
10
|
+
from typing import Optional
|
|
10
11
|
from ..utils import getLogger
|
|
11
12
|
|
|
12
13
|
logger = getLogger(__name__)
|
|
@@ -42,6 +43,8 @@ class GeologicalInterpolator(metaclass=ABCMeta):
|
|
|
42
43
|
self.constraints = []
|
|
43
44
|
self.__str = "Base Geological Interpolator"
|
|
44
45
|
self.valid = True
|
|
46
|
+
self.dimensions = 3 # default to 3d
|
|
47
|
+
self.support = None
|
|
45
48
|
|
|
46
49
|
@property
|
|
47
50
|
def data(self):
|
|
@@ -106,9 +109,9 @@ class GeologicalInterpolator(metaclass=ABCMeta):
|
|
|
106
109
|
|
|
107
110
|
"""
|
|
108
111
|
points = self.check_array(points)
|
|
109
|
-
if points.shape[1] ==
|
|
112
|
+
if points.shape[1] == self.dimensions + 1:
|
|
110
113
|
points = np.hstack([points, np.ones((points.shape[0], 1))])
|
|
111
|
-
if points.shape[1] <
|
|
114
|
+
if points.shape[1] < self.dimensions + 2:
|
|
112
115
|
raise ValueError("Value points must at least have X,Y,Z,val,w")
|
|
113
116
|
self.data["value"] = points
|
|
114
117
|
self.n_i = points.shape[0]
|
|
@@ -127,9 +130,9 @@ class GeologicalInterpolator(metaclass=ABCMeta):
|
|
|
127
130
|
-------
|
|
128
131
|
|
|
129
132
|
"""
|
|
130
|
-
if points.shape[1] ==
|
|
133
|
+
if points.shape[1] == self.dimensions * 2:
|
|
131
134
|
points = np.hstack([points, np.ones((points.shape[0], 1))])
|
|
132
|
-
if points.shape[1] <
|
|
135
|
+
if points.shape[1] < self.dimensions * 2 + 1:
|
|
133
136
|
raise ValueError("Gradient constraints must at least have X,Y,Z,gx,gy,gz")
|
|
134
137
|
self.n_g = points.shape[0]
|
|
135
138
|
self.data["gradient"] = points
|
|
@@ -148,9 +151,9 @@ class GeologicalInterpolator(metaclass=ABCMeta):
|
|
|
148
151
|
-------
|
|
149
152
|
|
|
150
153
|
"""
|
|
151
|
-
if points.shape[1] ==
|
|
154
|
+
if points.shape[1] == self.dimensions * 2:
|
|
152
155
|
points = np.hstack([points, np.ones((points.shape[0], 1))])
|
|
153
|
-
if points.shape[1] <
|
|
156
|
+
if points.shape[1] < self.dimensions * 2 + 1:
|
|
154
157
|
raise ValueError("Nonrmal constraints must at least have X,Y,Z,nx,ny,nz")
|
|
155
158
|
self.n_n = points.shape[0]
|
|
156
159
|
self.data["normal"] = points
|
|
@@ -169,9 +172,9 @@ class GeologicalInterpolator(metaclass=ABCMeta):
|
|
|
169
172
|
-------
|
|
170
173
|
|
|
171
174
|
"""
|
|
172
|
-
if points.shape[1] ==
|
|
175
|
+
if points.shape[1] == self.dimensions * 2:
|
|
173
176
|
points = np.hstack([points, np.ones((points.shape[0], 1))])
|
|
174
|
-
if points.shape[1] <
|
|
177
|
+
if points.shape[1] < self.dimensions * 2 + 1:
|
|
175
178
|
raise ValueError("Tangent constraints must at least have X,Y,Z,tx,ty,tz")
|
|
176
179
|
self.data["tangent"] = points
|
|
177
180
|
self.up_to_date = False
|
|
@@ -181,13 +184,13 @@ class GeologicalInterpolator(metaclass=ABCMeta):
|
|
|
181
184
|
self.up_to_date = False
|
|
182
185
|
|
|
183
186
|
def set_value_inequality_constraints(self, points: np.ndarray):
|
|
184
|
-
if points.shape[1] <
|
|
187
|
+
if points.shape[1] < self.dimensions + 2:
|
|
185
188
|
raise ValueError("Inequality constraints must at least have X,Y,Z,lower,upper")
|
|
186
189
|
self.data["inequality"] = points
|
|
187
190
|
self.up_to_date = False
|
|
188
191
|
|
|
189
192
|
def set_inequality_pairs_constraints(self, points: np.ndarray):
|
|
190
|
-
if points.shape[1] <
|
|
193
|
+
if points.shape[1] < self.dimensions + 1:
|
|
191
194
|
raise ValueError("Inequality pairs constraints must at least have X,Y,Z,rock_id")
|
|
192
195
|
|
|
193
196
|
self.data["inequality_pairs"] = points
|
|
@@ -313,6 +316,20 @@ class GeologicalInterpolator(metaclass=ABCMeta):
|
|
|
313
316
|
def add_interface_constraints(self, w: float = 1.0):
|
|
314
317
|
pass
|
|
315
318
|
|
|
319
|
+
@abstractmethod
|
|
320
|
+
def add_value_inequality_constraints(self, w: float = 1.0):
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
@abstractmethod
|
|
324
|
+
def add_inequality_pairs_constraints(
|
|
325
|
+
self,
|
|
326
|
+
w: float = 1.0,
|
|
327
|
+
upper_bound=np.finfo(float).eps,
|
|
328
|
+
lower_bound=-np.inf,
|
|
329
|
+
pairs: Optional[list] = None,
|
|
330
|
+
):
|
|
331
|
+
pass
|
|
332
|
+
|
|
316
333
|
def to_dict(self):
|
|
317
334
|
return {
|
|
318
335
|
"type": self.type,
|
|
@@ -181,7 +181,7 @@ class P1Interpolator(DiscreteInterpolator):
|
|
|
181
181
|
points: np.ndarray,
|
|
182
182
|
vectors: np.ndarray,
|
|
183
183
|
w: float = 1.0,
|
|
184
|
-
|
|
184
|
+
b: float = 0,
|
|
185
185
|
name='undefined gradient orthogonal constraint',
|
|
186
186
|
):
|
|
187
187
|
"""
|
|
@@ -215,8 +215,8 @@ class P1Interpolator(DiscreteInterpolator):
|
|
|
215
215
|
norm = np.linalg.norm(vectors, axis=1)
|
|
216
216
|
vectors[norm > 0, :] /= norm[norm > 0, None]
|
|
217
217
|
A = np.einsum("ij,ijk->ik", vectors[inside, :3], grad[inside, :, :])
|
|
218
|
-
B = np.zeros(points[inside, :].shape[0]) +
|
|
219
|
-
self.add_constraints_to_least_squares(A, B, elements, w=wt, name=
|
|
218
|
+
B = np.zeros(points[inside, :].shape[0]) + b
|
|
219
|
+
self.add_constraints_to_least_squares(A, B, elements, w=wt, name=name)
|
|
220
220
|
if np.sum(inside) <= 0:
|
|
221
221
|
logger.warning(
|
|
222
222
|
f"{np.sum(~inside)} \
|