Skip to content

Commit b7ac901

Browse files
authored
Merge pull request #89 from Loop3D/unstructured_tetra.py
adding an unstructured tetrahedron class
2 parents 8414112 + 479ae2b commit b7ac901

File tree

16 files changed

+6594
-160
lines changed

16 files changed

+6594
-160
lines changed

LoopStructural/interpolators/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from LoopStructural.interpolators.piecewiselinear_interpolator import \
99
PiecewiseLinearInterpolator
1010
from LoopStructural.interpolators.structured_tetra import TetMesh
11+
from LoopStructural.interpolators.unstructured_tetra import UnStructuredTetMesh
1112
from LoopStructural.interpolators.structured_grid import StructuredGrid
1213
from LoopStructural.interpolators.geological_interpolator import GeologicalInterpolator
1314
from LoopStructural.interpolators.discrete_interpolator import DiscreteInterpolator
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# class LinearTetrahedron:
2+
# def __init__(self):
3+
# pass
4+
5+
# def element_gradient(self, elements):
6+
# ps = self.
7+
# m = np.array(
8+
# [[(ps[:, 1, 0] - ps[:, 0, 0]), (ps[:, 1, 1] - ps[:, 0, 1]),
9+
# (ps[:, 1, 2] - ps[:, 0, 2])],
10+
# [(ps[:, 2, 0] - ps[:, 0, 0]), (ps[:, 2, 1] - ps[:, 0, 1]),
11+
# (ps[:, 2, 2] - ps[:, 0, 2])],
12+
# [(ps[:, 3, 0] - ps[:, 0, 0]), (ps[:, 3, 1] - ps[:, 0, 1]),
13+
# (ps[:, 3, 2] - ps[:, 0, 2])]])
14+
# I = np.array(
15+
# [[-1., 1., 0., 0.],
16+
# [-1., 0., 1., 0.],
17+
# [-1., 0., 0., 1.]])
18+
# m = np.swapaxes(m, 0, 2)
19+
# element_gradients = np.linalg.inv(m)
20+
21+
# element_gradients = element_gradients.swapaxes(1, 2)
22+
# element_gradients = element_gradients @ I
23+
24+
# return element_gradients[elements,:,:]

LoopStructural/interpolators/base_structured_3d_support.py

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def __init__(self,
2222
# we use property decorators to update these when different parts of
2323
# the geometry need to change
2424
# inisialise the private attributes
25-
self._nsteps = np.array(nsteps)
25+
self._nsteps = np.array(nsteps,dtype=int)
2626
self._step_vector = np.array(step_vector)
2727
self._origin = np.array(origin)
2828
self.supporttype='Base'
@@ -181,4 +181,125 @@ def check_position(self, pos):
181181
if len(pos.shape) != 2:
182182
print("Position array needs to be a list of points or a point")
183183
return False
184-
return pos
184+
return pos
185+
186+
def global_cell_indicies(self, indexes):
187+
"""
188+
Convert from cell indexes to global cell index
189+
190+
Parameters
191+
----------
192+
indexes
193+
194+
Returns
195+
-------
196+
197+
"""
198+
indexes = np.array(indexes).swapaxes(0, 2)
199+
return indexes[:, :, 0] + self.nsteps_cells[None, None, 0] \
200+
* indexes[:, :, 1] + self.nsteps_cells[None, None, 0] * \
201+
self.nsteps_cells[None, None, 1] * indexes[:, :, 2]
202+
203+
def cell_corner_indexes(self, x_cell_index, y_cell_index, z_cell_index):
204+
"""
205+
Returns the indexes of the corners of a cell given its location xi,
206+
yi, zi
207+
208+
Parameters
209+
----------
210+
x_cell_index
211+
y_cell_index
212+
z_cell_index
213+
214+
Returns
215+
-------
216+
217+
"""
218+
xcorner = np.array([0, 1, 0, 0, 1, 0, 1, 1])
219+
ycorner = np.array([0, 0, 1, 0, 0, 1, 1, 1])
220+
zcorner = np.array([0, 0, 0, 1, 1, 1, 0, 1])
221+
xcorners = x_cell_index[:, None] + xcorner[None, :]
222+
ycorners = y_cell_index[:, None] + ycorner[None, :]
223+
zcorners = z_cell_index[:, None] + zcorner[None, :]
224+
return xcorners, ycorners, zcorners
225+
226+
def position_to_cell_corners(self, pos):
227+
228+
inside = self.inside(pos)
229+
ix, iy, iz = self.position_to_cell_index(pos)
230+
cornersx, cornersy, cornersz = self.cell_corner_indexes(ix, iy, iz)
231+
globalidx = self.global_indicies(
232+
np.dstack([cornersx, cornersy, cornersz]).T)
233+
# if global index is not inside the support set to -1
234+
globalidx[~inside] = -1
235+
return globalidx, inside
236+
237+
def node_indexes_to_position(self, xindex, yindex, zindex):
238+
239+
x = self.origin[0] + self.step_vector[0] * xindex
240+
y = self.origin[1] + self.step_vector[1] * yindex
241+
z = self.origin[2] + self.step_vector[2] * zindex
242+
243+
return x, y, z
244+
245+
def global_index_to_cell_index(self, global_index):
246+
"""
247+
Convert from global indexes to xi,yi,zi
248+
249+
Parameters
250+
----------
251+
global_index
252+
253+
Returns
254+
-------
255+
256+
"""
257+
# determine the ijk indices for the global index.
258+
# remainder when dividing by nx = i
259+
# remained when dividing modulus of nx by ny is j
260+
261+
x_index = global_index % self.nsteps_cells[0, None]
262+
y_index = global_index // self.nsteps_cells[0, None] % \
263+
self.nsteps_cells[1, None]
264+
z_index = global_index // self.nsteps_cells[0, None] // \
265+
self.nsteps_cells[1, None]
266+
return x_index, y_index, z_index
267+
268+
269+
def global_index_to_node_index(self, global_index):
270+
"""
271+
Convert from global indexes to xi,yi,zi
272+
273+
Parameters
274+
----------
275+
global_index
276+
277+
Returns
278+
-------
279+
280+
"""
281+
# determine the ijk indices for the global index.
282+
# remainder when dividing by nx = i
283+
# remained when dividing modulus of nx by ny is j
284+
x_index = global_index % self.nsteps[0, None]
285+
y_index = global_index // self.nsteps[0, None] % \
286+
self.nsteps[1, None]
287+
z_index = global_index // self.nsteps[0, None] // \
288+
self.nsteps[1, None]
289+
return x_index, y_index, z_index
290+
def global_node_indicies(self, indexes):
291+
"""
292+
Convert from node indexes to global node index
293+
294+
Parameters
295+
----------
296+
indexes
297+
298+
Returns
299+
-------
300+
301+
"""
302+
indexes = np.array(indexes).swapaxes(0, 2)
303+
return indexes[:, :, 0] + self.nsteps[None, None, 0] \
304+
* indexes[:, :, 1] + self.nsteps[None, None, 0] * \
305+
self.nsteps[None, None, 1] * indexes[:, :, 2]

LoopStructural/interpolators/finite_difference_interpolator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ def add_gradient_constraints(self, w=1.):
266266
# normalise constraint vector and scale element matrix by this
267267
norm = np.linalg.norm(points[:,3:6],axis=1)
268268
points[:,3:6]/=norm[:,None]
269-
T/=norm[:,None,None]
269+
T/=norm[inside,None,None]
270270
# calculate two orthogonal vectors to constraint (strike and dip vector)
271271
strike_vector, dip_vector = get_vectors(points[inside, 3:6])
272272
A = np.einsum('ij,ijk->ik', strike_vector.T, T)

LoopStructural/interpolators/piecewiselinear_interpolator.py

Lines changed: 52 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55

66
import numpy as np
7+
from LoopStructural.interpolators.cython.dsi_helper import cg, constant_norm, fold_cg
78

89
from LoopStructural.interpolators.discrete_interpolator import \
910
DiscreteInterpolator
@@ -17,23 +18,23 @@ class PiecewiseLinearInterpolator(DiscreteInterpolator):
1718
"""
1819
1920
"""
20-
def __init__(self, mesh):
21+
def __init__(self, support):
2122
"""
2223
Piecewise Linear Interpolator
2324
Approximates scalar field by finding coefficients to a piecewise linear
2425
equation on a tetrahedral mesh. Uses constant gradient regularisation.
2526
2627
Parameters
2728
----------
28-
mesh - TetMesh
29+
support - TetMesh
2930
interpolation support
3031
"""
3132

3233
self.shape = 'rectangular'
33-
DiscreteInterpolator.__init__(self, mesh)
34+
DiscreteInterpolator.__init__(self, support)
3435
# whether to assemble a rectangular matrix or a square matrix
3536
self.interpolator_type = 'PLI'
36-
self.support = mesh
37+
self.support = support
3738

3839
self.interpolation_weights = {'cgw': 0.1, 'cpw': 1., 'npw': 1.,
3940
'gpw': 1., 'tpw': 1., 'ipw': 1.}
@@ -103,73 +104,58 @@ def add_constant_gradient(self, w= 0.1, direction_vector=None, direction_feature
103104
104105
"""
105106
if direction_feature is not None:
106-
print('dir fe')
107107
direction_vector = direction_feature.evaluate_gradient(self.support.barycentre())
108108
if direction_vector is not None:
109109
if direction_vector.shape[0] == 1:
110110
# if using a constant direction, tile array so it works for cg calc
111111
direction_vector = np.tile(direction_vector,(self.support.barycentre().shape[0],1))
112+
if direction_vector is not None:
113+
logger.info("Running constant gradient")
114+
elements_gradients = self.support.get_element_gradients(np.arange(self.ntetra))
115+
if elements_gradients.shape[0] != direction_vector.shape[0]:
116+
logger.error('Cannot add directional CG, vector field is not the correct length')
117+
return
118+
region = self.region.astype('int64')
119+
120+
neighbours = self.support.get_neighbours()
121+
elements = self.support.get_elements()
122+
idc, c, ncons = fold_cg(elements_gradients, direction_vector, neighbours.astype('int64'), elements.astype('int64'), self.nodes)
123+
124+
idc = np.array(idc[:ncons, :])
125+
A = np.array(c[:ncons, :])
126+
B = np.zeros(c.shape[0])
127+
gi = np.zeros(self.support.n_nodes)
128+
gi[:] = -1
129+
gi[self.region] = np.arange(0, self.nx)
130+
idc = gi[idc]
131+
outside = ~np.any(idc == -1, axis=1)
112132

113-
114-
# iterate over all elements
115-
A, idc, B = self.support.get_constant_gradient(region=self.region,direction=direction_vector)
116-
A = np.array(A)
117-
B = np.array(B)
118-
idc = np.array(idc)
119-
120-
gi = np.zeros(self.support.n_nodes)
121-
gi[:] = -1
122-
gi[self.region] = np.arange(0, self.nx)
123-
idc = gi[idc]
124-
outside = ~np.any(idc == -1, axis=1)
125-
126-
# w/=A.shape[0]
127-
self.add_constraints_to_least_squares(A[outside, :] * w,
128-
B[outside] * w, idc[outside, :],
129-
name='regularisation')
130-
return
131-
132-
def add_direction_constant_gradient(self, w= 0.1, direction_vector=None, direction_feature=None):
133-
"""
134-
Add the constant gradient regularisation to the system where regularisation is projected
135-
on a vector
136-
137-
Parameters
138-
----------
139-
w (double) - weighting of the cg parameter
140-
direction_vector
141-
direction_feature
142-
143-
Returns
144-
-------
145-
146-
"""
147-
if direction_feature:
148-
print('dir fe')
149-
direction_vector = direction_feature.evaluate_gradient(self.support.barycentre())
150-
if direction_vector:
151-
if direction_vector.shape[0] == 1:
152-
# if using a constant direction, tile array so it works for cg calc
153-
direction_vector = np.tile(direction_vector,(self.support.barycentre().shape[0],1))
154-
155-
156-
# iterate over all elements
157-
A, idc, B = self.support.get_constant_gradient(region=self.region,direction=direction_vector)
158-
A = np.array(A)
159-
B = np.array(B)
160-
idc = np.array(idc)
161-
162-
gi = np.zeros(self.support.n_nodes)
163-
gi[:] = -1
164-
gi[self.region] = np.arange(0, self.nx)
165-
idc = gi[idc]
166-
outside = ~np.any(idc == -1, axis=1)
167-
168-
# w/=A.shape[0]
169-
self.add_constraints_to_least_squares(A[outside, :] * w,
170-
B[outside] * w, idc[outside, :],
171-
name='directional regularisation')
172-
return
133+
# w/=A.shape[0]
134+
self.add_constraints_to_least_squares(A[outside, :] * w,
135+
B[outside] * w, idc[outside, :],
136+
name='direction_regularisation')
137+
else:
138+
logger.info("Running constant gradient")
139+
elements_gradients = self.support.get_element_gradients(np.arange(self.support.ntetra))
140+
region = self.region.astype('int64')
141+
142+
neighbours = self.support.get_neighbours()
143+
elements = self.support.get_elements()
144+
idc, c, ncons = cg(elements_gradients, neighbours.astype('int64'), elements.astype('int64'), self.support.nodes,
145+
region.astype('int64'))
146+
147+
idc = np.array(idc[:ncons, :])
148+
A = np.array(c[:ncons, :])
149+
B = np.zeros(A.shape[0])
150+
gi = np.zeros(self.support.n_nodes)
151+
gi[:] = -1
152+
gi[self.region] = np.arange(0, self.nx)
153+
idc = gi[idc]
154+
outside = ~np.any(idc == -1, axis=1)
155+
# w/=A.shape[0]
156+
self.add_constraints_to_least_squares(A[outside, :] * w,
157+
B[outside] * w, idc[outside, :],
158+
name='regularisation')
173159

174160

175161
def add_gradient_constraints(self, w=1.0):
@@ -212,7 +198,7 @@ def add_gradient_constraints(self, w=1.0):
212198
gi = np.zeros(self.support.n_nodes).astype(int)
213199
gi[:] = -1
214200
gi[self.region] = np.arange(0, self.nx).astype(int)
215-
w /= 3
201+
# w /= 3
216202
idc = gi[tetras]
217203
B = np.zeros(idc.shape[0])
218204
outside = ~np.any(idc == -1, axis=1)
@@ -329,7 +315,6 @@ def add_interface_constraints(self, w=1.0): # for now weight all value points t
329315
points = self.get_interface_constraints()
330316
if points.shape[0] > 1:
331317
vertices, c, tetras, inside = self.support.get_tetra_for_location(points[:,:3])
332-
# print(points[inside,:].shape)
333318

334319
gi = np.zeros(self.support.n_nodes)
335320
gi[:] = -1

0 commit comments

Comments
 (0)