LoopStructural 1.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of LoopStructural might be problematic. Click here for more details.

Files changed (129) hide show
  1. LoopStructural/__init__.py +52 -0
  2. LoopStructural/datasets/__init__.py +23 -0
  3. LoopStructural/datasets/_base.py +301 -0
  4. LoopStructural/datasets/_example_models.py +10 -0
  5. LoopStructural/datasets/data/claudius.csv +21049 -0
  6. LoopStructural/datasets/data/claudiusbb.txt +2 -0
  7. LoopStructural/datasets/data/duplex.csv +126 -0
  8. LoopStructural/datasets/data/duplexbb.txt +2 -0
  9. LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
  10. LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
  11. LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
  12. LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
  13. LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
  14. LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
  15. LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
  16. LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
  17. LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
  18. LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
  19. LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
  20. LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
  21. LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
  22. LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
  23. LoopStructural/datasets/data/intrusion.csv +1017 -0
  24. LoopStructural/datasets/data/intrusionbb.txt +2 -0
  25. LoopStructural/datasets/data/onefoldbb.txt +2 -0
  26. LoopStructural/datasets/data/onefolddata.csv +2226 -0
  27. LoopStructural/datasets/data/refolded_bb.txt +2 -0
  28. LoopStructural/datasets/data/refolded_fold.csv +205 -0
  29. LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
  30. LoopStructural/datatypes/__init__.py +4 -0
  31. LoopStructural/datatypes/_bounding_box.py +422 -0
  32. LoopStructural/datatypes/_point.py +166 -0
  33. LoopStructural/datatypes/_structured_grid.py +94 -0
  34. LoopStructural/datatypes/_surface.py +184 -0
  35. LoopStructural/export/exporters.py +554 -0
  36. LoopStructural/export/file_formats.py +15 -0
  37. LoopStructural/export/geoh5.py +100 -0
  38. LoopStructural/export/gocad.py +126 -0
  39. LoopStructural/export/omf_wrapper.py +88 -0
  40. LoopStructural/interpolators/__init__.py +105 -0
  41. LoopStructural/interpolators/_api.py +143 -0
  42. LoopStructural/interpolators/_builders.py +149 -0
  43. LoopStructural/interpolators/_cython/__init__.py +0 -0
  44. LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
  45. LoopStructural/interpolators/_discrete_interpolator.py +692 -0
  46. LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
  47. LoopStructural/interpolators/_geological_interpolator.py +380 -0
  48. LoopStructural/interpolators/_interpolator_factory.py +89 -0
  49. LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
  50. LoopStructural/interpolators/_operator.py +38 -0
  51. LoopStructural/interpolators/_p1interpolator.py +228 -0
  52. LoopStructural/interpolators/_p2interpolator.py +277 -0
  53. LoopStructural/interpolators/_surfe_wrapper.py +174 -0
  54. LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
  55. LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
  56. LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
  57. LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
  58. LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
  59. LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
  60. LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
  61. LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
  62. LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
  63. LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
  64. LoopStructural/interpolators/supports/__init__.py +55 -0
  65. LoopStructural/interpolators/supports/_aabb.py +77 -0
  66. LoopStructural/interpolators/supports/_base_support.py +114 -0
  67. LoopStructural/interpolators/supports/_face_table.py +70 -0
  68. LoopStructural/interpolators/supports/_support_factory.py +32 -0
  69. LoopStructural/modelling/__init__.py +29 -0
  70. LoopStructural/modelling/core/__init__.py +0 -0
  71. LoopStructural/modelling/core/geological_model.py +1867 -0
  72. LoopStructural/modelling/features/__init__.py +32 -0
  73. LoopStructural/modelling/features/_analytical_feature.py +79 -0
  74. LoopStructural/modelling/features/_base_geological_feature.py +364 -0
  75. LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
  76. LoopStructural/modelling/features/_geological_feature.py +288 -0
  77. LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
  78. LoopStructural/modelling/features/_region.py +18 -0
  79. LoopStructural/modelling/features/_structural_frame.py +186 -0
  80. LoopStructural/modelling/features/_unconformity_feature.py +83 -0
  81. LoopStructural/modelling/features/builders/__init__.py +5 -0
  82. LoopStructural/modelling/features/builders/_base_builder.py +111 -0
  83. LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
  84. LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
  85. LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
  86. LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
  87. LoopStructural/modelling/features/fault/__init__.py +3 -0
  88. LoopStructural/modelling/features/fault/_fault_function.py +444 -0
  89. LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
  90. LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
  91. LoopStructural/modelling/features/fold/__init__.py +9 -0
  92. LoopStructural/modelling/features/fold/_fold.py +167 -0
  93. LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
  94. LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
  95. LoopStructural/modelling/features/fold/_foldframe.py +194 -0
  96. LoopStructural/modelling/features/fold/_svariogram.py +188 -0
  97. LoopStructural/modelling/input/__init__.py +2 -0
  98. LoopStructural/modelling/input/fault_network.py +80 -0
  99. LoopStructural/modelling/input/map2loop_processor.py +165 -0
  100. LoopStructural/modelling/input/process_data.py +650 -0
  101. LoopStructural/modelling/input/project_file.py +84 -0
  102. LoopStructural/modelling/intrusions/__init__.py +25 -0
  103. LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
  104. LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
  105. LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
  106. LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
  107. LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
  108. LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
  109. LoopStructural/utils/__init__.py +38 -0
  110. LoopStructural/utils/_surface.py +143 -0
  111. LoopStructural/utils/_transformation.py +76 -0
  112. LoopStructural/utils/config.py +18 -0
  113. LoopStructural/utils/dtm_creator.py +17 -0
  114. LoopStructural/utils/exceptions.py +31 -0
  115. LoopStructural/utils/helper.py +292 -0
  116. LoopStructural/utils/json_encoder.py +18 -0
  117. LoopStructural/utils/linalg.py +8 -0
  118. LoopStructural/utils/logging.py +79 -0
  119. LoopStructural/utils/maths.py +245 -0
  120. LoopStructural/utils/regions.py +103 -0
  121. LoopStructural/utils/typing.py +7 -0
  122. LoopStructural/utils/utils.py +68 -0
  123. LoopStructural/version.py +1 -0
  124. LoopStructural/visualisation/__init__.py +11 -0
  125. LoopStructural-1.6.1.dist-info/LICENSE +21 -0
  126. LoopStructural-1.6.1.dist-info/METADATA +81 -0
  127. LoopStructural-1.6.1.dist-info/RECORD +129 -0
  128. LoopStructural-1.6.1.dist-info/WHEEL +5 -0
  129. LoopStructural-1.6.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,228 @@
1
+ """
2
+ Piecewise linear interpolator
3
+ """
4
+
5
+ import logging
6
+
7
+ import numpy as np
8
+
9
+ from ._discrete_interpolator import DiscreteInterpolator
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class P1Interpolator(DiscreteInterpolator):
15
+ def __init__(self, mesh):
16
+ """
17
+ Piecewise Linear Interpolator
18
+ Approximates scalar field by finding coefficients to a piecewise linear
19
+ equation on a tetrahedral mesh. Uses constant gradient regularisation.
20
+
21
+ Parameters
22
+ ----------
23
+ mesh - TetMesh
24
+ interpolation support
25
+ """
26
+
27
+ self.shape = "rectangular"
28
+ DiscreteInterpolator.__init__(self, mesh)
29
+ # whether to assemble a rectangular matrix or a square matrix
30
+ self.support = mesh
31
+
32
+ self.interpolation_weights = {
33
+ "cgw": 0.1,
34
+ "cpw": 1.0,
35
+ "npw": 1.0,
36
+ "gpw": 1.0,
37
+ "tpw": 1.0,
38
+ "ipw": 1.0,
39
+ }
40
+
41
+ def add_gradient_constraints(self, w=1.0):
42
+ pass
43
+
44
+ def add_norm_constraints(self, w=1.0):
45
+ points = self.get_norm_constraints()
46
+ if points.shape[0] > 0:
47
+ grad, elements, inside = self.support.evaluate_shape_derivatives(points[:, :3])
48
+ size = self.support.element_size[elements[inside]]
49
+ wt = np.ones(size.shape[0])
50
+ wt *= w # s* size
51
+ elements = np.tile(self.support.elements[elements[inside]], (3, 1, 1))
52
+
53
+ elements = elements.swapaxes(0, 1)
54
+ # elements = elements.swapaxes(0, 2)
55
+ # grad = grad.swapaxes(1, 2)
56
+ # elements = elements.swapaxes(1, 2)
57
+
58
+ self.add_constraints_to_least_squares(
59
+ grad[inside, :, :],
60
+ points[inside, 3:6],
61
+ elements,
62
+ w=wt,
63
+ name="norm",
64
+ )
65
+ self.up_to_date = False
66
+ pass
67
+
68
+ def add_value_constraints(self, w=1.0):
69
+ points = self.get_value_constraints()
70
+ if points.shape[0] > 1:
71
+ N, elements, inside = self.support.evaluate_shape(points[:, :3])
72
+ size = self.support.element_size[elements[inside]]
73
+ wt = np.ones(size.shape[0])
74
+ wt *= w # * size
75
+ self.add_constraints_to_least_squares(
76
+ N[inside, :],
77
+ points[inside, 3],
78
+ self.support.elements[elements[inside], :],
79
+ w=wt,
80
+ name="value",
81
+ )
82
+ self.up_to_date = False
83
+
84
+ def minimise_edge_jumps(self, w=0.1, vector_func=None, vector=None, name="edge jump"):
85
+ # NOTE: imposes \phi_T1(xi)-\phi_T2(xi) dot n =0
86
+ # iterate over all triangles
87
+ # flag inidicate which triangles have had all their relationships added
88
+ v1 = self.support.nodes[self.support.shared_elements][:, 0, :]
89
+ v2 = self.support.nodes[self.support.shared_elements][:, 1, :]
90
+ bc_t1 = self.support.barycentre[self.support.shared_element_relationships[:, 0]]
91
+ bc_t2 = self.support.barycentre[self.support.shared_element_relationships[:, 1]]
92
+ norm = self.support.shared_element_norm
93
+ # shared_element_scale = self.support.shared_element_scale
94
+
95
+ # evaluate normal if using vector func for cp2
96
+ if vector_func:
97
+ norm = vector_func((v1 + v2) / 2)
98
+ if vector is not None:
99
+ if bc_t1.shape[0] == vector.shape[0]:
100
+ norm = vector
101
+ # evaluate the shape function for the edges for each neighbouring triangle
102
+ Dt, tri1, inside = self.support.evaluate_shape_derivatives(
103
+ bc_t1, elements=self.support.shared_element_relationships[:, 0]
104
+ )
105
+ Dn, tri2, inside = self.support.evaluate_shape_derivatives(
106
+ bc_t2, elements=self.support.shared_element_relationships[:, 1]
107
+ )
108
+ # constraint for each cp is triangle - neighbour create a Nx12 matrix
109
+ const_t = np.einsum("ij,ijk->ik", norm, Dt)
110
+ const_n = -np.einsum("ij,ijk->ik", norm, Dn)
111
+ # const_t_cp2 = np.einsum('ij,ikj->ik',normal,cp2_Dt)
112
+ # const_n_cp2 = -np.einsum('ij,ikj->ik',normal,cp2_Dn)
113
+
114
+ const = np.hstack([const_t, const_n])
115
+
116
+ # get vertex indexes
117
+ tri_cp1 = np.hstack([self.support.elements[tri1], self.support.elements[tri2]])
118
+ # tri_cp2 = np.hstack([self.support.elements[cp2_tri1],self.support.elements[tri2]])
119
+ # add cp1 and cp2 to the least squares system
120
+ self.add_constraints_to_least_squares(
121
+ const,
122
+ np.zeros(const.shape[0]),
123
+ tri_cp1,
124
+ w=w,
125
+ name=name,
126
+ )
127
+ self.up_to_date = False
128
+ # p2.add_constraints_to_least_squares(const_cp2*e_len[:,None]*w,np.zeros(const_cp1.shape[0]),tri_cp2, name='edge jump cp2')
129
+
130
+ def setup_interpolator(self, **kwargs):
131
+ """
132
+ Searches through kwargs for any interpolation weights and updates
133
+ the dictionary.
134
+ Then adds the constraints to the linear system using the
135
+ interpolation weights values
136
+ Parameters
137
+ ----------
138
+ kwargs -
139
+ interpolation weights
140
+
141
+ Returns
142
+ -------
143
+
144
+ """
145
+ # can't reset here, clears fold constraints
146
+ self.reset()
147
+ for key in kwargs:
148
+ if "regularisation" in kwargs:
149
+ self.interpolation_weights["cgw"] = 0.1 * kwargs["regularisation"]
150
+ self.up_to_date = False
151
+ self.interpolation_weights[key] = kwargs[key]
152
+ if self.interpolation_weights["cgw"] > 0.0:
153
+ self.up_to_date = False
154
+ self.minimise_edge_jumps(self.interpolation_weights["cgw"])
155
+ # direction_feature=kwargs.get("direction_feature", None),
156
+ # direction_vector=kwargs.get("direction_vector", None),
157
+ # )
158
+ # self.minimise_grad_steepness(
159
+ # w=self.interpolation_weights.get("steepness_weight", 0.01),
160
+ # wtfunc=self.interpolation_weights.get("steepness_wtfunc", None),
161
+ # )
162
+ logger.info(
163
+ "Using constant gradient regularisation w = %f" % self.interpolation_weights["cgw"]
164
+ )
165
+
166
+ logger.info(
167
+ "Added %i gradient constraints, %i normal constraints,"
168
+ "%i tangent constraints and %i value constraints"
169
+ % (self.n_g, self.n_n, self.n_t, self.n_i)
170
+ )
171
+ self.add_gradient_constraints(self.interpolation_weights["gpw"])
172
+ self.add_norm_constraints(self.interpolation_weights["npw"])
173
+ self.add_value_constraints(self.interpolation_weights["cpw"])
174
+ self.add_tangent_constraints(self.interpolation_weights["tpw"])
175
+ self.add_value_inequality_constraints()
176
+ self.add_inequality_pairs_constraints()
177
+ # self.add_interface_constraints(self.interpolation_weights["ipw"])
178
+
179
+ def add_gradient_orthogonal_constraints(
180
+ self,
181
+ points: np.ndarray,
182
+ vectors: np.ndarray,
183
+ w: float = 1.0,
184
+ B: float = 0,
185
+ name='undefined gradient orthogonal constraint',
186
+ ):
187
+ """
188
+ constraints scalar field to be orthogonal to a given vector
189
+
190
+ Parameters
191
+ ----------
192
+ points : np.darray
193
+ location to add gradient orthogonal constraint
194
+ vector : np.darray
195
+ vector to be orthogonal to, should be the same shape as points
196
+ w : double
197
+ B : np.array
198
+
199
+ Returns
200
+ -------
201
+
202
+ """
203
+ if points.shape[0] > 0:
204
+ grad, elements, inside = self.support.evaluate_shape_derivatives(points[:, :3])
205
+ size = self.support.element_size[elements[inside]]
206
+ wt = np.ones(size.shape[0])
207
+ wt *= w * size
208
+ elements = self.support.elements[elements[inside], :]
209
+ # elements = np.tile(self.support.elements[elements[inside]], (3, 1, 1))
210
+
211
+ # elements = elements.swapaxes(0, 1)
212
+ # elements = elements.swapaxes(0, 2)
213
+ # grad = grad.swapaxes(1, 2)
214
+ # elements = elements.swapaxes(1, 2)
215
+ norm = np.linalg.norm(vectors, axis=1)
216
+ vectors[norm > 0, :] /= norm[norm > 0, None]
217
+ A = np.einsum("ij,ijk->ik", vectors[inside, :3], grad[inside, :, :])
218
+ B = np.zeros(points[inside, :].shape[0]) + B
219
+ self.add_constraints_to_least_squares(A, B, elements, w=wt, name="gradient orthogonal")
220
+ if np.sum(inside) <= 0:
221
+ logger.warning(
222
+ f"{np.sum(~inside)} \
223
+ gradient constraints not added: outside of model bounding box"
224
+ )
225
+ self.up_to_date = False
226
+
227
+ def add_interface_constraints(self, w: float = 1):
228
+ raise NotImplementedError
@@ -0,0 +1,277 @@
1
+ """
2
+ Piecewise linear interpolator
3
+ """
4
+
5
+ import logging
6
+ from typing import Optional, Callable
7
+
8
+ import numpy as np
9
+
10
+ from ..interpolators import DiscreteInterpolator
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class P2Interpolator(DiscreteInterpolator):
16
+ """ """
17
+
18
+ def __init__(self, mesh):
19
+ """
20
+ Piecewise Linear Interpolator
21
+ Approximates scalar field by finding coefficients to a piecewise linear
22
+ equation on a tetrahedral mesh. Uses constant gradient regularisation.
23
+
24
+ Parameters
25
+ ----------
26
+ mesh - TetMesh
27
+ interpolation support
28
+ """
29
+
30
+ self.shape = "rectangular"
31
+ DiscreteInterpolator.__init__(self, mesh)
32
+ # whether to assemble a rectangular matrix or a square matrix
33
+ self.interpolator_type = "P2"
34
+ self.support = mesh
35
+
36
+ self.interpolation_weights = {
37
+ "cgw": 0.1,
38
+ "cpw": 1.0,
39
+ "npw": 1.0,
40
+ "gpw": 1.0,
41
+ "tpw": 1.0,
42
+ "ipw": 1.0,
43
+ }
44
+
45
+ def setup_interpolator(self, **kwargs):
46
+ """
47
+ Searches through kwargs for any interpolation weights and updates
48
+ the dictionary.
49
+ Then adds the constraints to the linear system using the
50
+ interpolation weights values
51
+ Parameters
52
+ ----------
53
+ kwargs -
54
+ interpolation weights
55
+
56
+ Returns
57
+ -------
58
+
59
+ """
60
+ # can't reset here, clears fold constraints
61
+ # self.reset()
62
+ for key in kwargs:
63
+ if "regularisation" in kwargs:
64
+ self.interpolation_weights["cgw"] = 0.1 * kwargs["regularisation"]
65
+ self.up_to_date = False
66
+ self.interpolation_weights[key] = kwargs[key]
67
+ if self.interpolation_weights["cgw"] > 0.0:
68
+ self.up_to_date = False
69
+ self.minimise_edge_jumps(self.interpolation_weights["cgw"])
70
+ # direction_feature=kwargs.get("direction_feature", None),
71
+ # direction_vector=kwargs.get("direction_vector", None),
72
+ # )
73
+ self.minimise_grad_steepness(
74
+ w=self.interpolation_weights.get("steepness_weight", 0.01),
75
+ wtfunc=self.interpolation_weights.get("steepness_wtfunc", None),
76
+ )
77
+ logger.info(
78
+ "Using constant gradient regularisation w = %f" % self.interpolation_weights["cgw"]
79
+ )
80
+
81
+ logger.info(
82
+ "Added %i gradient constraints, %i normal constraints,"
83
+ "%i tangent constraints and %i value constraints"
84
+ % (self.n_g, self.n_n, self.n_t, self.n_i)
85
+ )
86
+ self.add_gradient_constraints(self.interpolation_weights["gpw"])
87
+ self.add_norm_constraints(self.interpolation_weights["npw"])
88
+ self.add_value_constraints(self.interpolation_weights["cpw"])
89
+ self.add_tangent_constraints(self.interpolation_weights["tpw"])
90
+ # self.add_interface_constraints(self.interpolation_weights["ipw"])
91
+
92
+ def copy(self):
93
+ return P2Interpolator(self.support)
94
+
95
+ def add_gradient_constraints(self, w: float = 1.0):
96
+ points = self.get_gradient_constraints()
97
+ if points.shape[0] > 0:
98
+ grad, elements = self.support.evaluate_shape_derivatives(points[:, :3])
99
+ inside = elements > -1
100
+ area = self.support.element_size[elements[inside]]
101
+ wt = np.ones(area.shape[0])
102
+ wt *= w * area
103
+ A = np.einsum("ikj,ij->ik", grad[inside, :], points[inside, 3:6])
104
+ B = np.zeros(A.shape[0])
105
+ elements = self.support[elements[inside]]
106
+ self.add_constraints_to_least_squares(A * wt[:, None], B, elements, name="gradient")
107
+
108
+ def add_gradient_orthogonal_constraints(
109
+ self, points: np.ndarray, vector: np.ndarray, w=1.0, B=0
110
+ ):
111
+ """
112
+ constraints scalar field to be orthogonal to a given vector
113
+
114
+ Parameters
115
+ ----------
116
+ position
117
+ normals
118
+ w
119
+ B
120
+
121
+ Returns
122
+ -------
123
+
124
+ """
125
+ if points.shape[0] > 0:
126
+ grad, elements = self.support.evaluate_shape_derivatives(points[:, :3])
127
+ inside = elements > -1
128
+ area = self.support.element_size[elements[inside]]
129
+ wt = np.ones(area.shape[0])
130
+ wt *= w * area
131
+ A = np.einsum("ijk,ij->ik", grad[inside, :], vector[inside, :])
132
+ B = np.zeros(A.shape[0])
133
+ elements = self.support.elements[elements[inside]]
134
+ self.add_constraints_to_least_squares(
135
+ A * wt[:, None], B, elements, name="gradient orthogonal"
136
+ )
137
+
138
+ def add_norm_constraints(self, w: float = 1.0):
139
+ points = self.get_norm_constraints()
140
+ if points.shape[0] > 0:
141
+ grad, elements = self.support.evaluate_shape_derivatives(points[:, :3])
142
+ inside = elements > -1
143
+ area = self.support.element_size[elements[inside]]
144
+ wt = np.ones(area.shape[0])
145
+ wt *= w * area
146
+ elements = np.tile(self.support.elements[elements[inside]], (3, 1, 1))
147
+ elements = elements.swapaxes(0, 1)
148
+ self.add_constraints_to_least_squares(
149
+ grad[inside, :, :] * wt[:, None, None],
150
+ points[inside, 3:6] * wt[:, None],
151
+ elements,
152
+ name="norm",
153
+ )
154
+
155
+ def add_value_constraints(self, w: float = 1.0):
156
+ points = self.get_value_constraints()
157
+ if points.shape[0] > 1:
158
+ N, elements, mask = self.support.evaluate_shape(points[:, :3])
159
+ # mask = elements > 0
160
+ size = self.support.element_size[elements[mask]]
161
+ wt = np.ones(size.shape[0])
162
+ wt *= w
163
+ self.add_constraints_to_least_squares(
164
+ N[mask, :],
165
+ points[mask, 3],
166
+ self.support.elements[elements[mask], :],
167
+ w=wt,
168
+ name="value",
169
+ )
170
+
171
+ def minimise_grad_steepness(
172
+ self,
173
+ w: float = 0.1,
174
+ maskall: bool = False,
175
+ wtfunc: Optional[Callable[[np.ndarray], np.ndarray]] = None,
176
+ ):
177
+ """This constraint minimises the second derivative of the gradient
178
+ mimimising the 2nd derivative should prevent high curvature solutions
179
+ It is not added on the borders
180
+
181
+ Parameters
182
+ ----------
183
+ w : float, optional
184
+ [description], by default 0.1
185
+ maskall : bool, default False
186
+ whether to apply on all elements or just internal elements (default)
187
+ wtfunc : callable, optional
188
+ a function that returns the weight to be applied at xyz. Called on the barycentre
189
+ of the tetrahedron
190
+ """
191
+ elements = np.arange(0, len(self.support.elements))
192
+ mask = np.ones(self.support.neighbours.shape[0], dtype=bool)
193
+ if not maskall:
194
+ mask[:] = np.all(self.support.neighbours > 3, axis=1)
195
+
196
+ d2 = self.support.evaluate_shape_d2(elements[mask])
197
+ # d2 shape is [ele_idx, deriv, node]
198
+ wt = np.ones(d2.shape[0])
199
+ wt *= w # * self.support.element_size[mask]
200
+ if callable(wtfunc):
201
+ logger.info("Using function to weight gradient steepness")
202
+ wt = wtfunc(self.support.barycentre) * self.support.element_size[mask]
203
+ idc = self.support.elements[elements[mask]]
204
+ for i in range(d2.shape[1]):
205
+ self.add_constraints_to_least_squares(
206
+ d2[:, i, :],
207
+ np.zeros(d2.shape[0]),
208
+ idc[:, :],
209
+ w=wt,
210
+ name=f"gradsteepness_{i}",
211
+ )
212
+
213
+ def minimise_edge_jumps(
214
+ self,
215
+ w: float = 0.1,
216
+ wtfunc: Optional[Callable[[np.ndarray], np.ndarray]] = None,
217
+ vector_func: Optional[Callable[[np.ndarray], np.ndarray]] = None,
218
+ quadrature_points: Optional[int] = None,
219
+ ):
220
+ """_summary_
221
+
222
+ Parameters
223
+ ----------
224
+ w : float, optional
225
+ _description_, by default 0.1
226
+ wtfunc : callable, optional
227
+ _description_, by default None
228
+ vector_func : callable, optional
229
+ _description_, by default None
230
+ """
231
+ # NOTE: imposes \phi_T1(xi)-\phi_T2(xi) dot n =0
232
+ # iterate over all triangles
233
+
234
+ cp, weight = self.support.get_quadrature_points()
235
+
236
+ norm = self.support.shared_element_norm
237
+
238
+ # evaluate normal if using vector func for cp1
239
+ for i in range(cp.shape[1]):
240
+ if callable(vector_func):
241
+ norm = vector_func(cp[:, i, :])
242
+ # evaluate the shape function for the edges for each neighbouring triangle
243
+ cp_Dt, cp_tri1 = self.support.evaluate_shape_derivatives(
244
+ cp[:, i, :], elements=self.support.shared_element_relationships[:, 0]
245
+ )
246
+ cp_Dn, cp_tri2 = self.support.evaluate_shape_derivatives(
247
+ cp[:, i, :], elements=self.support.shared_element_relationships[:, 1]
248
+ )
249
+ # constraint for each cp is triangle - neighbour create a Nx12 matrix
250
+ const_t_cp = np.einsum("ij,ijk->ik", norm, cp_Dt)
251
+ const_n_cp = -np.einsum("ij,ijk->ik", norm, cp_Dn)
252
+
253
+ const_cp = np.hstack([const_t_cp, const_n_cp])
254
+ tri_cp = np.hstack([self.support.elements[cp_tri1], self.support.elements[cp_tri2]])
255
+ wt = np.zeros(tri_cp.shape[0])
256
+ wt[:] = w * weight[:, i]
257
+ if wtfunc:
258
+ wt = wtfunc(tri_cp)
259
+ self.add_constraints_to_least_squares(
260
+ const_cp,
261
+ np.zeros(const_cp.shape[0]),
262
+ tri_cp,
263
+ w=wt,
264
+ name=f"shared element jump cp{i}",
265
+ )
266
+
267
+ def evaluate_d2(self, evaluation_points: np.ndarray) -> np.ndarray:
268
+ evaluation_points = np.array(evaluation_points)
269
+ evaluated = np.zeros(evaluation_points.shape[0])
270
+ mask = np.any(evaluation_points == np.nan, axis=1)
271
+
272
+ if evaluation_points[~mask, :].shape[0] > 0:
273
+ evaluated[~mask] = self.support.evaluate_d2(evaluation_points[~mask], self.c)
274
+ return evaluated
275
+
276
+ def add_interface_constraints(self, w: float = 1):
277
+ raise NotImplementedError
@@ -0,0 +1,174 @@
1
+ """
2
+ Wrapper for using surfepy
3
+ """
4
+
5
+ from ..utils.helper import get_vectors
6
+ from ..interpolators import GeologicalInterpolator
7
+
8
+ import numpy as np
9
+
10
+ from ..utils import getLogger
11
+
12
+ logger = getLogger(__name__)
13
+ import surfepy
14
+
15
+
16
+ class SurfeRBFInterpolator(GeologicalInterpolator):
17
+ """ """
18
+
19
+ def __init__(self, method="single_surface"):
20
+ GeologicalInterpolator.__init__(self)
21
+ self.surfe = None
22
+ if method == "single_surface":
23
+ logger.info("Using single surface interpolator")
24
+ self.surfe = surfepy.Surfe_API(1)
25
+ if method == "Lajaunie" or method == "increments":
26
+ logger.info("Using Lajaunie method")
27
+ self.surfe = surfepy.Surfe_API(2)
28
+ if method == "horizons":
29
+ logger.info("Using surfe horizon")
30
+ self.surfe = surfepy.Surfe_API(4)
31
+
32
+ def add_gradient_ctr_pts(self):
33
+ points = self.get_gradient_constraints()
34
+ if points.shape[0] > 0:
35
+ logger.info("Adding ")
36
+ strike_vector, dip_vector = get_vectors(points[:, 3:6])
37
+
38
+ strike_vector = np.hstack([points[:, :3], strike_vector.T])
39
+ dip_vector = np.hstack([points[:, :3], dip_vector.T])
40
+ self.surfe.SetTangentConstraints(strike_vector)
41
+ self.surfe.SetTangentConstraints(dip_vector)
42
+
43
+ def add_norm_ctr_pts(self):
44
+ points = self.get_norm_constraints()
45
+ if points.shape[0] > 0:
46
+ self.surfe.SetPlanarConstraints(points[:, :6])
47
+
48
+ def add_ctr_pts(self):
49
+
50
+ points = self.get_value_constraints()
51
+ if points.shape[0] > 0:
52
+ # self.surfe.SetInterfaceConstraints(points[:,:4])
53
+ for i in range(points.shape[0]):
54
+
55
+ self.surfe.AddInterfaceConstraint(
56
+ points[i, 0],
57
+ points[i, 1],
58
+ points[i, 2],
59
+ points[i, 3],
60
+ )
61
+
62
+ def add_tangent_ctr_pts(self):
63
+ points = self.get_tangent_constraints()
64
+ if points.shape[0] > 0:
65
+ self.surfe.SetTangentConstraints(points[:, :6])
66
+
67
+ def _solve(self, **kwargs):
68
+ self.surfe.ComputeInterpolant()
69
+
70
+ def _setup_interpolator(self, **kwargs):
71
+ """
72
+ Setup the interpolator
73
+
74
+ Parameters
75
+ ----------
76
+ kernel: str
77
+ kernel for interpolation r3, r, Gaussian, Multiquadrics, Inverse Multiquadrics
78
+ Thin Plate Spline, WendlandC2, MaternC4
79
+ regression: float
80
+ smoothing parameter default 0
81
+ greedy: tuple
82
+ greedy parameters first is interface threshold, second is angular threshold
83
+ default (0,0)
84
+ poly_order: int
85
+ order of the polynomial used for interpolation, default 1
86
+ radius: float
87
+ radius of the kernel, default None but required for SPD kernels
88
+ anisotropy: bool
89
+ apply global anisotropy from eigenvectors of orientation constraints, default False
90
+
91
+
92
+ """
93
+ self.add_gradient_ctr_pts()
94
+ self.add_norm_ctr_pts()
95
+ self.add_ctr_pts()
96
+ self.add_tangent_ctr_pts()
97
+
98
+ kernel = kwargs.get("kernel", "r3")
99
+ logger.info("Setting surfe RBF kernel to %s" % kernel)
100
+ self.surfe.SetRBFKernel(kernel)
101
+ regression = kwargs.get("regression_smoothing", 0.0)
102
+ if regression > 0:
103
+ logger.info("Using regression smoothing %f" % regression)
104
+ self.surfe.SetRegressionSmoothing(True, regression)
105
+ greedy = kwargs.get("greedy", (0, 0))
106
+
107
+ if greedy[0] > 0 or greedy[1] > 0:
108
+ logger.info(
109
+ "Using greedy algorithm: inferface %f and angular %f" % (greedy[0], greedy[1])
110
+ )
111
+ self.surfe.SetGreedyAlgorithm(True, greedy[0], greedy[1])
112
+ poly_order = kwargs.get("poly_order", None)
113
+ if poly_order:
114
+ logger.info("Setting poly order to %i" % poly_order)
115
+ self.surfe.SetPolynomialOrder(poly_order)
116
+ global_anisotropy = kwargs.get("anisotropy", False)
117
+ if global_anisotropy:
118
+ logger.info("Using global anisotropy")
119
+ self.surfe.SetGlobalAnisotropy(global_anisotropy)
120
+ radius = kwargs.get("radius", False)
121
+ if radius:
122
+ logger.info("Setting RBF radius to %f" % radius)
123
+ self.surfe.SetRBFShapeParameter(radius)
124
+
125
+ def update(self):
126
+ return self.surfe.InterpolantComputed()
127
+
128
+ def evaluate_value(self, evaluation_points):
129
+ """Evaluate surfe interpolant at points
130
+
131
+ Parameters
132
+ ----------
133
+ evaluation_points : array of locations N,3
134
+ xyz of locations to evaluate
135
+
136
+ Returns
137
+ -------
138
+ np.array (N)
139
+ value of interpolant at points
140
+ """
141
+ evaluation_points = np.array(evaluation_points)
142
+ evaluated = np.zeros(evaluation_points.shape[0])
143
+ mask = np.any(evaluation_points == np.nan, axis=1)
144
+
145
+ if evaluation_points[~mask, :].shape[0] > 0:
146
+ evaluated[~mask] = self.surfe.EvaluateInterpolantAtPoints(evaluation_points[~mask])
147
+ return evaluated
148
+
149
+ def evaluate_gradient(self, evaluation_points):
150
+ """Evaluate surfe interpolant gradient at points
151
+
152
+ Parameters
153
+ ----------
154
+ evaluation_points : array of locations N,3
155
+ xyz of locations to evaluate
156
+
157
+ Returns
158
+ -------
159
+ np.array (N,3)
160
+ gradient of interpolant at points
161
+
162
+ """
163
+ evaluation_points = np.array(evaluation_points)
164
+ evaluated = np.zeros(evaluation_points.shape)
165
+ mask = np.any(evaluation_points == np.nan, axis=1)
166
+ if evaluation_points[~mask, :].shape[0] > 0:
167
+ evaluated[~mask, :] = self.surfe.EvaluateVectorInterpolantAtPoints(
168
+ evaluation_points[~mask]
169
+ )
170
+ return
171
+
172
+ @property
173
+ def nx(self):
174
+ return self.get_data_locations().shape[0]