zoomy-core 0.1.14__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.
- decorators/decorators.py +25 -0
- fvm/__init__.py +0 -0
- fvm/flux.py +52 -0
- fvm/nonconservative_flux.py +97 -0
- fvm/ode.py +55 -0
- fvm/solver_numpy.py +297 -0
- fvm/timestepping.py +13 -0
- mesh/__init__.py +0 -0
- mesh/mesh.py +1239 -0
- mesh/mesh_extrude.py +168 -0
- mesh/mesh_util.py +487 -0
- misc/__init__.py +0 -0
- misc/custom_types.py +6 -0
- misc/interpolation.py +140 -0
- misc/io.py +448 -0
- misc/logger_config.py +18 -0
- misc/misc.py +218 -0
- model/__init__.py +0 -0
- model/analysis.py +147 -0
- model/basefunction.py +113 -0
- model/basemodel.py +513 -0
- model/boundary_conditions.py +193 -0
- model/initial_conditions.py +171 -0
- model/model.py +65 -0
- model/models/GN.py +70 -0
- model/models/advection.py +53 -0
- model/models/basisfunctions.py +181 -0
- model/models/basismatrices.py +381 -0
- model/models/coupled_constrained.py +60 -0
- model/models/poisson.py +41 -0
- model/models/shallow_moments.py +757 -0
- model/models/shallow_moments_sediment.py +378 -0
- model/models/shallow_moments_topo.py +423 -0
- model/models/shallow_moments_variants.py +1509 -0
- model/models/shallow_water.py +266 -0
- model/models/shallow_water_topo.py +111 -0
- model/models/shear_shallow_flow.py +594 -0
- model/models/sme_turbulent.py +613 -0
- model/models/vam.py +455 -0
- postprocessing/__init__.py +0 -0
- postprocessing/plotting.py +244 -0
- postprocessing/postprocessing.py +75 -0
- preprocessing/__init__.py +0 -0
- preprocessing/openfoam_moments.py +453 -0
- transformation/__init__.py +0 -0
- transformation/helpers.py +25 -0
- transformation/to_amrex.py +241 -0
- transformation/to_c.py +185 -0
- transformation/to_jax.py +14 -0
- transformation/to_numpy.py +118 -0
- transformation/to_openfoam.py +258 -0
- transformation/to_ufl.py +67 -0
- zoomy_core-0.1.14.dist-info/METADATA +52 -0
- zoomy_core-0.1.14.dist-info/RECORD +57 -0
- zoomy_core-0.1.14.dist-info/WHEEL +5 -0
- zoomy_core-0.1.14.dist-info/licenses/LICENSE +674 -0
- zoomy_core-0.1.14.dist-info/top_level.txt +8 -0
mesh/mesh_extrude.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def extrude_points(points, Z):
|
|
5
|
+
n_points = points.shape[0]
|
|
6
|
+
dim = points.shape[1]
|
|
7
|
+
Nz = Z.shape[0]
|
|
8
|
+
points_ext = np.empty((n_points * (Nz), dim + 1), dtype=float)
|
|
9
|
+
for i in range(n_points):
|
|
10
|
+
for iz, z in enumerate(Z):
|
|
11
|
+
offset = iz * n_points
|
|
12
|
+
points_ext[i + offset, :dim] = points[i]
|
|
13
|
+
points_ext[i + offset, -1] = z
|
|
14
|
+
return points_ext
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extrude_element_vertices(element_vertices, n_vertices, Nz):
|
|
18
|
+
|
|
19
|
+
n_elements = element_vertices.shape[0]
|
|
20
|
+
n_vertices_per_element = element_vertices.shape[1]
|
|
21
|
+
if n_vertices_per_element == 2:
|
|
22
|
+
return extrude_element_vertices_line(element_vertices, n_vertices, Nz)
|
|
23
|
+
element_vertices_ext = np.empty(
|
|
24
|
+
(n_elements * Nz, 2 * n_vertices_per_element), dtype=int
|
|
25
|
+
)
|
|
26
|
+
for i in range(n_elements):
|
|
27
|
+
for iz in range(Nz):
|
|
28
|
+
offset = iz * n_elements
|
|
29
|
+
element_vertices_ext[i + offset, :n_vertices_per_element] = (
|
|
30
|
+
element_vertices[i, :] + iz * n_vertices
|
|
31
|
+
)
|
|
32
|
+
element_vertices_ext[i + offset, n_vertices_per_element:] = (
|
|
33
|
+
element_vertices[i, :] + (iz + 1) * n_vertices
|
|
34
|
+
)
|
|
35
|
+
return element_vertices_ext
|
|
36
|
+
|
|
37
|
+
def extrude_element_vertices_line(element_vertices, n_vertices, Nz):
|
|
38
|
+
n_elements = element_vertices.shape[0]
|
|
39
|
+
n_vertices_per_element = 2
|
|
40
|
+
element_vertices_ext = np.empty(
|
|
41
|
+
(n_elements * Nz, 2 * n_vertices_per_element), dtype=int
|
|
42
|
+
)
|
|
43
|
+
for i in range(n_elements):
|
|
44
|
+
for iz in range(Nz):
|
|
45
|
+
offset = iz * n_elements
|
|
46
|
+
element_vertices_ext[i + offset, [0, 3]] = (
|
|
47
|
+
element_vertices[i, :] + iz * n_vertices
|
|
48
|
+
)
|
|
49
|
+
element_vertices_ext[i + offset, [1,2]] = (
|
|
50
|
+
element_vertices[i, :] + (iz + 1) * n_vertices
|
|
51
|
+
)
|
|
52
|
+
return element_vertices_ext
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def extrude_boundary_face_vertices(msh, Nz):
|
|
56
|
+
"""
|
|
57
|
+
convenction: 1. bottom 2. sides 3. top
|
|
58
|
+
"""
|
|
59
|
+
n_boundary_elements = msh.n_boundary_elements * Nz + 2 * msh.n_elements
|
|
60
|
+
n_vertices_per_face = msh.boundary_face_vertices.shape[1]
|
|
61
|
+
n_vertices_per_face_ext = 2 * n_vertices_per_face
|
|
62
|
+
boundary_face_vertices = np.empty(
|
|
63
|
+
(n_boundary_elements, n_vertices_per_face_ext), dtype=int
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# for wface, boundary elements may have 3 or 4 vertices. In case of 3, I want to overwrite the other entries to be dublicates of existing entries
|
|
67
|
+
side_offset = 0
|
|
68
|
+
# bottom
|
|
69
|
+
for i in range(msh.n_elements):
|
|
70
|
+
boundary_face_vertices[i, : msh.element_vertices[i].shape[0]] = (
|
|
71
|
+
msh.element_vertices[i]
|
|
72
|
+
)
|
|
73
|
+
boundary_face_vertices[i, msh.element_vertices[i].shape[0] :] = (
|
|
74
|
+
msh.element_vertices[i][-1]
|
|
75
|
+
)
|
|
76
|
+
side_offset += msh.n_elements
|
|
77
|
+
|
|
78
|
+
n_vertices_per_layer = msh.n_vertices
|
|
79
|
+
# sides
|
|
80
|
+
for i in range(msh.n_boundary_elements):
|
|
81
|
+
face_vertices = msh.boundary_face_vertices[i]
|
|
82
|
+
for iz in range(Nz):
|
|
83
|
+
offset = iz * msh.n_boundary_elements + side_offset
|
|
84
|
+
boundary_face_vertices[i + offset, :n_vertices_per_face] = (
|
|
85
|
+
face_vertices + iz * n_vertices_per_layer
|
|
86
|
+
)
|
|
87
|
+
boundary_face_vertices[i + offset, n_vertices_per_face:] = (
|
|
88
|
+
face_vertices + (iz + 1) * n_vertices_per_layer
|
|
89
|
+
)
|
|
90
|
+
side_offset += Nz * msh.n_boundary_elements
|
|
91
|
+
|
|
92
|
+
# top
|
|
93
|
+
for i in range(msh.n_elements):
|
|
94
|
+
# boundary_face_vertices[i + side_offset] = msh.element_vertices[i] + Nz * msh.n_vertices
|
|
95
|
+
boundary_face_vertices[i + side_offset, : msh.element_vertices[i].shape[0]] = (
|
|
96
|
+
msh.element_vertices[i] + Nz * msh.n_vertices
|
|
97
|
+
)
|
|
98
|
+
boundary_face_vertices[i + side_offset, msh.element_vertices[i].shape[0] :] = (
|
|
99
|
+
msh.element_vertices[i][-1] + Nz * msh.n_vertices
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return boundary_face_vertices
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def extrude_boundary_face_corresponding_element(msh, Nz):
|
|
106
|
+
"""
|
|
107
|
+
convenction: 1. bottom 2. sides 3. top
|
|
108
|
+
"""
|
|
109
|
+
n_boundary_elements = msh.n_boundary_elements * Nz + 2 * msh.n_elements
|
|
110
|
+
boundary_face_corresponding_element = np.empty((n_boundary_elements), dtype=int)
|
|
111
|
+
|
|
112
|
+
side_offset = 0
|
|
113
|
+
# bottom
|
|
114
|
+
for i in range(msh.n_elements):
|
|
115
|
+
boundary_face_corresponding_element[i] = i
|
|
116
|
+
side_offset += msh.n_elements
|
|
117
|
+
|
|
118
|
+
# sides
|
|
119
|
+
for i in range(msh.n_boundary_elements):
|
|
120
|
+
i_elem = msh.boundary_face_corresponding_element[i]
|
|
121
|
+
for iz in range(Nz):
|
|
122
|
+
offset_boundary = iz * msh.n_boundary_elements + side_offset
|
|
123
|
+
offset_elements = iz * msh.n_elements
|
|
124
|
+
boundary_face_corresponding_element[i + offset_boundary] = (
|
|
125
|
+
i_elem + offset_elements
|
|
126
|
+
)
|
|
127
|
+
side_offset += Nz * msh.n_boundary_elements
|
|
128
|
+
|
|
129
|
+
# top
|
|
130
|
+
for i in range(msh.n_elements):
|
|
131
|
+
boundary_face_corresponding_element[i + side_offset] = (
|
|
132
|
+
i + (Nz - 1) * msh.n_elements
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return boundary_face_corresponding_element
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def extrude_boundary_face_tags(msh, Nz):
|
|
139
|
+
"""
|
|
140
|
+
convenction: 1. bottom 2. sides 3. top
|
|
141
|
+
"""
|
|
142
|
+
n_boundary_elements = msh.n_boundary_elements * Nz + 2 * msh.n_elements
|
|
143
|
+
boundary_face_tags = np.empty((n_boundary_elements), int)
|
|
144
|
+
|
|
145
|
+
n_existing_tags = len(msh.boundary_tag_names)
|
|
146
|
+
tag_bottom = n_existing_tags
|
|
147
|
+
tag_top = n_existing_tags + 1
|
|
148
|
+
|
|
149
|
+
side_offset = 0
|
|
150
|
+
# bottom
|
|
151
|
+
for i in range(msh.n_elements):
|
|
152
|
+
boundary_face_tags[i] = tag_bottom
|
|
153
|
+
side_offset += msh.n_elements
|
|
154
|
+
|
|
155
|
+
# sides
|
|
156
|
+
for i in range(msh.n_boundary_elements):
|
|
157
|
+
i_elem = msh.boundary_face_corresponding_element[i]
|
|
158
|
+
for iz in range(Nz):
|
|
159
|
+
offset_boundary = iz * msh.n_boundary_elements + side_offset
|
|
160
|
+
offset_elements = iz * msh.n_elements
|
|
161
|
+
boundary_face_tags[i + offset_boundary] = msh.boundary_face_tag[i]
|
|
162
|
+
side_offset += Nz * msh.n_boundary_elements
|
|
163
|
+
|
|
164
|
+
# top
|
|
165
|
+
for i in range(msh.n_elements):
|
|
166
|
+
boundary_face_tags[i + side_offset] = tag_top
|
|
167
|
+
|
|
168
|
+
return boundary_face_tags
|
mesh/mesh_util.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def convert_mesh_type_to_meshio_mesh_type(mesh_type: str) -> str:
|
|
4
|
+
if mesh_type == "triangle":
|
|
5
|
+
return "triangle"
|
|
6
|
+
elif mesh_type == "hexahedron":
|
|
7
|
+
return "hexahedron"
|
|
8
|
+
elif mesh_type == "line":
|
|
9
|
+
return "line"
|
|
10
|
+
elif mesh_type == "quad":
|
|
11
|
+
return "quad"
|
|
12
|
+
elif mesh_type == "wface":
|
|
13
|
+
return "wedge"
|
|
14
|
+
elif mesh_type == "tetra":
|
|
15
|
+
return "tetra"
|
|
16
|
+
else:
|
|
17
|
+
assert False
|
|
18
|
+
|
|
19
|
+
def _get_n_nodes_per_element(mesh_type: str) -> int:
|
|
20
|
+
if (mesh_type) == "quad":
|
|
21
|
+
return 4
|
|
22
|
+
elif (mesh_type) == "triangle":
|
|
23
|
+
return 3
|
|
24
|
+
elif (mesh_type) == "wface":
|
|
25
|
+
return 6
|
|
26
|
+
elif (mesh_type) == "hexahedron":
|
|
27
|
+
return 8
|
|
28
|
+
elif (mesh_type) == "tetra":
|
|
29
|
+
return 4
|
|
30
|
+
else:
|
|
31
|
+
assert False
|
|
32
|
+
|
|
33
|
+
def get_extruded_mesh_type(mesh_type: str) -> str:
|
|
34
|
+
if (mesh_type) == "quad":
|
|
35
|
+
return "hexahedron"
|
|
36
|
+
elif (mesh_type) == "triangle":
|
|
37
|
+
return "wface"
|
|
38
|
+
elif (mesh_type) == "line":
|
|
39
|
+
return "quad"
|
|
40
|
+
else:
|
|
41
|
+
assert False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_global_cell_index_from_vertices(
|
|
45
|
+
cells, coordinates, return_first=True, offset=0
|
|
46
|
+
):
|
|
47
|
+
hits = []
|
|
48
|
+
for index, cell in enumerate(cells):
|
|
49
|
+
if set(coordinates).issubset(set(cell)):
|
|
50
|
+
hits.append(offset + index)
|
|
51
|
+
if return_first:
|
|
52
|
+
return offset + index
|
|
53
|
+
# if hits == []:
|
|
54
|
+
# assert False
|
|
55
|
+
return hits
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_element_neighbors(element_vertices, current_elem, mesh_type):
|
|
59
|
+
num_vertices_per_face = _get_num_vertices_per_face(mesh_type)
|
|
60
|
+
max_num_neighbors = _get_faces_per_element(mesh_type)
|
|
61
|
+
element_neighbor_indices = np.zeros((max_num_neighbors), dtype=int)
|
|
62
|
+
element_neighbor_face_index = np.zeros((max_num_neighbors), dtype=int)
|
|
63
|
+
n_found_neighbors = 0
|
|
64
|
+
for i_elem, elem in enumerate(element_vertices):
|
|
65
|
+
found_overlapping_vertices = set(elem).intersection(set(current_elem))
|
|
66
|
+
n_found_overlapping_vertices = len(found_overlapping_vertices)
|
|
67
|
+
|
|
68
|
+
if n_found_overlapping_vertices == np.min(num_vertices_per_face):
|
|
69
|
+
for i_elem_face_index, edge in enumerate(
|
|
70
|
+
_face_order(current_elem, mesh_type)
|
|
71
|
+
):
|
|
72
|
+
if set(edge).issubset(found_overlapping_vertices):
|
|
73
|
+
element_neighbor_face_index[n_found_neighbors] = i_elem_face_index
|
|
74
|
+
break
|
|
75
|
+
element_neighbor_indices[n_found_neighbors] = i_elem
|
|
76
|
+
n_found_neighbors += 1
|
|
77
|
+
if n_found_neighbors == max_num_neighbors:
|
|
78
|
+
break
|
|
79
|
+
return n_found_neighbors, element_neighbor_indices, element_neighbor_face_index
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def face_normals(coordinates, element, mesh_type) -> float:
|
|
83
|
+
if mesh_type == "triangle":
|
|
84
|
+
return _face_normals_2d(coordinates, element, mesh_type)
|
|
85
|
+
elif mesh_type == "quad":
|
|
86
|
+
return _face_normals_2d(coordinates, element, mesh_type)
|
|
87
|
+
elif mesh_type == "tetra":
|
|
88
|
+
return _face_normals_3d(coordinates, element, mesh_type)
|
|
89
|
+
elif mesh_type == "hexahedron":
|
|
90
|
+
return _face_normals_3d(coordinates, element, mesh_type)
|
|
91
|
+
elif mesh_type == "wface":
|
|
92
|
+
return _face_normals_3d(coordinates, element, mesh_type)
|
|
93
|
+
assert False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _face_normals_2d(coordinates, element, mesh_type) -> float:
|
|
97
|
+
edges = _face_order(element, mesh_type)
|
|
98
|
+
ez = np.array([0, 0, 1], dtype=float)
|
|
99
|
+
normals = np.zeros((len(edges), 3), dtype=float)
|
|
100
|
+
for i_edge, edge in enumerate(edges):
|
|
101
|
+
edge_direction = coordinates[edge[1]] - coordinates[edge[0]]
|
|
102
|
+
normals[i_edge, :] = -np.cross(edge_direction, ez)
|
|
103
|
+
normals[i_edge, :] /= np.linalg.norm(normals[i_edge, :])
|
|
104
|
+
|
|
105
|
+
# DEBUG: check that the normal goes into the right direction
|
|
106
|
+
# center_point = center(coordinates, element)
|
|
107
|
+
# ec = coordinates[edges]
|
|
108
|
+
# edge_centers = [np.mean(ec[i], axis=0) for i in range(4)]
|
|
109
|
+
# for i in range(4):
|
|
110
|
+
# assert np.allclose(center_point + 0.125 * normals[i], edge_centers[i])
|
|
111
|
+
|
|
112
|
+
return normals
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _face_normals_3d(coordinates, element, mesh_type) -> float:
|
|
116
|
+
faces = _face_order(element, mesh_type)
|
|
117
|
+
boundary_mesh_type = _get_boundary_element_type(mesh_type)
|
|
118
|
+
normals = np.zeros((len(faces), 3), dtype=float)
|
|
119
|
+
for i_face, face in enumerate(faces):
|
|
120
|
+
# I will only consider the first 2 edges, regardless of the element type.
|
|
121
|
+
# flat elements, where 2 edges already span the plane and normal
|
|
122
|
+
edges = _face_order(face, boundary_mesh_type[i_face])
|
|
123
|
+
edge_1 = edges[0]
|
|
124
|
+
edge_2 = edges[1]
|
|
125
|
+
edge_direction_1 = coordinates[edge_1[1]] - coordinates[edge_1[0]]
|
|
126
|
+
edge_direction_2 = coordinates[edge_2[1]] - coordinates[edge_2[0]]
|
|
127
|
+
normals[i_face, :] = -np.cross(edge_direction_1, edge_direction_2)
|
|
128
|
+
normals[i_face, :] /= np.linalg.norm(normals[i_face, :])
|
|
129
|
+
return normals
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _face_normals_wface(coordinates, element, mesh_type) -> float:
|
|
133
|
+
faces = _face_order(element, mesh_type)
|
|
134
|
+
boundary_mesh_type = _get_boundary_element_type(mesh_type)
|
|
135
|
+
normals = np.zeros((len(faces), 3), dtype=float)
|
|
136
|
+
for i_face, face in enumerate(faces):
|
|
137
|
+
# I will only consider the first 2 edges, regardless of the element type.
|
|
138
|
+
# flat elements, where 2 edges already span the plane and normal
|
|
139
|
+
edges = _face_order(face, boundary_mesh_type[i_face])
|
|
140
|
+
edge_1 = edges[0]
|
|
141
|
+
edge_2 = edges[1]
|
|
142
|
+
edge_direction_1 = coordinates[edge_1[1]] - coordinates[edge_1[0]]
|
|
143
|
+
edge_direction_2 = coordinates[edge_2[1]] - coordinates[edge_2[0]]
|
|
144
|
+
normals[i_face, :] = -np.cross(edge_direction_1, edge_direction_2)
|
|
145
|
+
normals[i_face, :] /= np.linalg.norm(normals[i_face, :])
|
|
146
|
+
return normals
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def face_areas(coordinates, element, mesh_type) -> float:
|
|
150
|
+
num_faces = _get_faces_per_element(mesh_type)
|
|
151
|
+
faces = _face_order(element, mesh_type)
|
|
152
|
+
boundary_mesh_type = _get_boundary_element_type(mesh_type)
|
|
153
|
+
face_areas = np.zeros(num_faces, dtype=float)
|
|
154
|
+
|
|
155
|
+
for i_face, face in enumerate(faces):
|
|
156
|
+
face_areas[i_face] = volume(coordinates, face, boundary_mesh_type[i_face])
|
|
157
|
+
|
|
158
|
+
return face_areas
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def center(coordinates, element) -> float:
|
|
162
|
+
center = np.zeros(3, dtype=float)
|
|
163
|
+
dim = coordinates.shape[1]
|
|
164
|
+
for vertex_coord in coordinates[np.array(element)]:
|
|
165
|
+
center[:dim] += vertex_coord
|
|
166
|
+
center /= np.array(element).shape[0]
|
|
167
|
+
return center[:dim]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def volume(coordinates, element, mesh_type) -> float:
|
|
171
|
+
if mesh_type == "line":
|
|
172
|
+
return edge_length(coordinates, element)
|
|
173
|
+
elif mesh_type == "triangle":
|
|
174
|
+
return _area_triangle(coordinates, element)
|
|
175
|
+
elif mesh_type == "quad":
|
|
176
|
+
return _area_quad(coordinates, element)
|
|
177
|
+
elif mesh_type == "tetra":
|
|
178
|
+
return _volume_tetra(coordinates, element)
|
|
179
|
+
elif mesh_type == "hexahedron":
|
|
180
|
+
return _volume_hex(coordinates, element)
|
|
181
|
+
elif mesh_type == "wface":
|
|
182
|
+
return _volume_wface(coordinates, element)
|
|
183
|
+
assert False
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _area_triangle(coordinates, element) -> float:
|
|
187
|
+
edges = _edge_order_triangle(element)
|
|
188
|
+
return _area_triangle_heron_formula(coordinates, edges)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _area_triangle_heron_formula(coordinates, edges):
|
|
192
|
+
perimeter = 0.0
|
|
193
|
+
for edge in edges:
|
|
194
|
+
perimeter += edge_length(coordinates, edge)
|
|
195
|
+
s = perimeter / 2
|
|
196
|
+
a = edge_length(coordinates, edges[0])
|
|
197
|
+
b = edge_length(coordinates, edges[1])
|
|
198
|
+
c = edge_length(coordinates, edges[2])
|
|
199
|
+
return np.sqrt(s * (s - a) * (s - b) * (s - c))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _area_quad(coordinates, element) -> float:
|
|
203
|
+
# compute area by splitting in 2 triangles.
|
|
204
|
+
edges_tri_1 = [
|
|
205
|
+
(element[0], element[1]),
|
|
206
|
+
(element[1], element[2]),
|
|
207
|
+
(element[2], element[0]),
|
|
208
|
+
]
|
|
209
|
+
edges_tri_2 = [
|
|
210
|
+
(element[2], element[3]),
|
|
211
|
+
(element[3], element[0]),
|
|
212
|
+
(element[0], element[2]),
|
|
213
|
+
]
|
|
214
|
+
return _area_triangle_heron_formula(
|
|
215
|
+
coordinates, edges_tri_1
|
|
216
|
+
) + _area_triangle_heron_formula(coordinates, edges_tri_2)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# formula from https://en.wikipedia.org/wiki/Tetrahedron, section 'general properties'
|
|
220
|
+
def _volume_tetra(coordinates, element) -> float:
|
|
221
|
+
a = coordinates[element[0]]
|
|
222
|
+
b = coordinates[element[1]]
|
|
223
|
+
c = coordinates[element[2]]
|
|
224
|
+
d = coordinates[element[3]]
|
|
225
|
+
volume = np.abs(np.dot((a - d), np.cross((b - d), (c - d)))) / 6
|
|
226
|
+
return volume
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _volume_hex(coordinates, element) -> float:
|
|
230
|
+
# devide into tetraheda and use formula above
|
|
231
|
+
# e.g. make up a point in the middle
|
|
232
|
+
volume = 0
|
|
233
|
+
center_point = center(coordinates, element)
|
|
234
|
+
faces = _face_order_hex(element)
|
|
235
|
+
for face in faces:
|
|
236
|
+
a = coordinates[face[0]]
|
|
237
|
+
b = coordinates[face[1]]
|
|
238
|
+
c = coordinates[face[2]]
|
|
239
|
+
d = center_point
|
|
240
|
+
volume += np.abs(np.dot((a - d), np.cross((b - d), (c - d)))) / 6
|
|
241
|
+
return volume
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _volume_wface(coordinates, element) -> float:
|
|
245
|
+
volume = 0
|
|
246
|
+
faces = _face_order_wface(element)
|
|
247
|
+
area_triangle = _area_triangle(coordinates, faces[0])
|
|
248
|
+
height = np.linalg.norm(coordinates[element[0]] - coordinates[element[3]])
|
|
249
|
+
return area_triangle * height
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def inradius(coordinates, element, mesh_type) -> float:
|
|
253
|
+
if mesh_type == "triangle":
|
|
254
|
+
return _inradius_triangle(coordinates, element)
|
|
255
|
+
elif mesh_type == "quad":
|
|
256
|
+
return _inradius_quad(coordinates, element)
|
|
257
|
+
elif mesh_type == "tetra":
|
|
258
|
+
return _inradius_tetra(coordinates, element)
|
|
259
|
+
elif mesh_type == "hexahedron":
|
|
260
|
+
return _inradius_generic(coordinates, element, "hexahedron")
|
|
261
|
+
elif mesh_type == "wface":
|
|
262
|
+
return _inradius_generic(coordinates, element, "wface")
|
|
263
|
+
assert False
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _inradius_triangle(coordinates, element) -> float:
|
|
267
|
+
area = _area_triangle(coordinates, element)
|
|
268
|
+
edges = _edge_order_triangle(element)
|
|
269
|
+
perimeter = 0.0
|
|
270
|
+
for edge in edges:
|
|
271
|
+
perimeter += edge_length(coordinates, edge)
|
|
272
|
+
s = perimeter / 2
|
|
273
|
+
result = 1.0
|
|
274
|
+
for edge in edges:
|
|
275
|
+
result *= s - edge_length(coordinates, edge)
|
|
276
|
+
result /= s
|
|
277
|
+
return float(np.sqrt(result))
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _inradius_quad(coordinates, element) -> float:
|
|
281
|
+
area = _area_quad(coordinates, element)
|
|
282
|
+
edges = _edge_order_quad(element)
|
|
283
|
+
perimeter = 0.0
|
|
284
|
+
for edge in edges:
|
|
285
|
+
perimeter += edge_length(coordinates, edge)
|
|
286
|
+
s = perimeter / 2
|
|
287
|
+
return float(area / s)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# see https://en.wikipedia.org/wiki/Tetrahedron, "Inradius"
|
|
291
|
+
def _inradius_tetra(coordinates, element) -> float:
|
|
292
|
+
faces = _face_order_tetra(element)
|
|
293
|
+
area = 0
|
|
294
|
+
for face in faces:
|
|
295
|
+
area += _area_triangle(coordinates, face)
|
|
296
|
+
volume = _volume_tetra(coordinates, element)
|
|
297
|
+
return 3 * volume / area
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _inradius_generic(coordinates, element, mesh_type) -> float:
|
|
301
|
+
"""
|
|
302
|
+
strategy: find the shortest path from the center to each side (defined by face center and normal)
|
|
303
|
+
use the minimum of all these shortest paths
|
|
304
|
+
"""
|
|
305
|
+
faces = _face_order(element, mesh_type)
|
|
306
|
+
normals = face_normals(coordinates, element, mesh_type)
|
|
307
|
+
element_center = center(coordinates, element)
|
|
308
|
+
n_faces = len(faces)
|
|
309
|
+
distances = np.empty(n_faces, dtype=float)
|
|
310
|
+
for i_face, face in enumerate(faces):
|
|
311
|
+
face_center = center(coordinates, face)
|
|
312
|
+
face_normal = normals[i_face]
|
|
313
|
+
distances[i_face] = np.abs(np.dot(face_center - element_center, face_normal))
|
|
314
|
+
return np.min(distances)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def edge_length(coordinates, edge) -> float:
|
|
318
|
+
x0 = coordinates[edge[0]]
|
|
319
|
+
x1 = coordinates[edge[1]]
|
|
320
|
+
return np.linalg.norm(x1 - x0, 2)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _face_order(element, mesh_type):
|
|
324
|
+
if mesh_type == "triangle":
|
|
325
|
+
return _edge_order_triangle(element)
|
|
326
|
+
elif mesh_type == "quad":
|
|
327
|
+
return _edge_order_quad(element)
|
|
328
|
+
elif mesh_type == "tetra":
|
|
329
|
+
return _face_order_tetra(element)
|
|
330
|
+
elif mesh_type == "hexahedron":
|
|
331
|
+
return _face_order_hex(element)
|
|
332
|
+
elif mesh_type == "wface":
|
|
333
|
+
return _face_order_wface(element)
|
|
334
|
+
else:
|
|
335
|
+
assert False
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _edge_order_triangle(element):
|
|
339
|
+
return [
|
|
340
|
+
(element[0], element[1]),
|
|
341
|
+
(element[1], element[2]),
|
|
342
|
+
(element[2], element[0]),
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _edge_order_quad(element):
|
|
347
|
+
return [
|
|
348
|
+
(element[0], element[1]),
|
|
349
|
+
(element[1], element[2]),
|
|
350
|
+
(element[2], element[3]),
|
|
351
|
+
(element[3], element[0]),
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _face_order_tetra(element):
|
|
356
|
+
return [
|
|
357
|
+
(element[0], element[1], element[2]),
|
|
358
|
+
(element[0], element[1], element[3]),
|
|
359
|
+
(element[1], element[2], element[3]),
|
|
360
|
+
(element[2], element[0], element[3]),
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _face_order_hex(element):
|
|
365
|
+
return [
|
|
366
|
+
(element[0], element[1], element[2], element[3]),
|
|
367
|
+
(element[4], element[5], element[6], element[7]),
|
|
368
|
+
(element[0], element[1], element[5], element[4]),
|
|
369
|
+
(element[1], element[2], element[6], element[5]),
|
|
370
|
+
(element[2], element[3], element[7], element[6]),
|
|
371
|
+
(element[3], element[0], element[4], element[7]),
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _face_order_wface(element):
|
|
376
|
+
return [
|
|
377
|
+
(element[0], element[1], element[2]),
|
|
378
|
+
(element[3], element[4], element[5]),
|
|
379
|
+
(element[0], element[3], element[4], element[1]),
|
|
380
|
+
(element[1], element[4], element[5], element[2]),
|
|
381
|
+
(element[2], element[5], element[3], element[0]),
|
|
382
|
+
]
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _get_num_vertices_per_face(mesh_type) -> float:
|
|
386
|
+
if mesh_type == "triangle":
|
|
387
|
+
return [2, 2, 2]
|
|
388
|
+
elif mesh_type == "quad":
|
|
389
|
+
return [2, 2, 2, 2]
|
|
390
|
+
elif mesh_type == "tetra":
|
|
391
|
+
return [3, 3, 3, 3]
|
|
392
|
+
elif mesh_type == "hexahedron":
|
|
393
|
+
return [4, 4, 4, 4, 4, 4]
|
|
394
|
+
elif mesh_type == "wface":
|
|
395
|
+
return [3, 3, 4, 4, 4]
|
|
396
|
+
assert False
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _get_dimension(mesh_type):
|
|
400
|
+
if mesh_type == "triangle":
|
|
401
|
+
return 2
|
|
402
|
+
elif mesh_type == "quad":
|
|
403
|
+
return 2
|
|
404
|
+
elif mesh_type == "tetra":
|
|
405
|
+
return 3
|
|
406
|
+
elif mesh_type == "hexahedron":
|
|
407
|
+
return 3
|
|
408
|
+
else:
|
|
409
|
+
assert False
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _get_faces_per_element(mesh_type):
|
|
413
|
+
if mesh_type == "line":
|
|
414
|
+
return 2
|
|
415
|
+
elif mesh_type == "triangle":
|
|
416
|
+
return 3
|
|
417
|
+
elif mesh_type == "quad":
|
|
418
|
+
return 4
|
|
419
|
+
elif mesh_type == "tetra":
|
|
420
|
+
return 4
|
|
421
|
+
elif mesh_type == "hexahedron":
|
|
422
|
+
return 6
|
|
423
|
+
elif mesh_type == "wface":
|
|
424
|
+
return 5
|
|
425
|
+
else:
|
|
426
|
+
assert False
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _get_boundary_element_type(mesh_type):
|
|
430
|
+
if mesh_type == "triangle":
|
|
431
|
+
return ["line", "line", "line"]
|
|
432
|
+
elif mesh_type == "quad":
|
|
433
|
+
return ["line", "line", "line", "line"]
|
|
434
|
+
elif mesh_type == "tetra":
|
|
435
|
+
return ["triangle", "triangle", "triangle", "triangle"]
|
|
436
|
+
elif mesh_type == "hexahedron":
|
|
437
|
+
return ["quad", "quad", "quad", "quad", "quad", "quad"]
|
|
438
|
+
elif mesh_type == "wface":
|
|
439
|
+
return ["triangle", "triangle", "quad", "quad", "quad"]
|
|
440
|
+
else:
|
|
441
|
+
assert False
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def find_edge_index(element, edge_vertices, element_type):
|
|
445
|
+
edges = _face_order(element, element_type)
|
|
446
|
+
for i_edge, edge in enumerate(edges):
|
|
447
|
+
if set(edge).issubset(edge_vertices):
|
|
448
|
+
return i_edge
|
|
449
|
+
assert False
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def compute_subvolume(face_vertices, cell_center, dim):
|
|
453
|
+
"""
|
|
454
|
+
Computes the subvolume of a face given its vertices and the cell center.
|
|
455
|
+
|
|
456
|
+
Parameters:
|
|
457
|
+
face_vertices (np.ndarray): The coordinates of the vertices of the face.
|
|
458
|
+
cell_center (np.ndarray): The coordinates of the cell center.
|
|
459
|
+
dim (int): The dimensionality of the problem (1, 2, or 3).
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
float: The subvolume of the face.
|
|
463
|
+
"""
|
|
464
|
+
if dim == 1:
|
|
465
|
+
# 1D: Length of the line segment
|
|
466
|
+
return np.abs(face_vertices[1] - face_vertices[0])
|
|
467
|
+
|
|
468
|
+
elif dim == 2:
|
|
469
|
+
# 2D: Area of the triangle
|
|
470
|
+
v0, v1 = face_vertices
|
|
471
|
+
return 0.5 * np.abs(np.cross(v1 - v0, cell_center - v0))
|
|
472
|
+
|
|
473
|
+
elif dim == 3:
|
|
474
|
+
if face_vertices.shape[0] == 3:
|
|
475
|
+
# 3D: Volume of the tetrahedron
|
|
476
|
+
v0, v1, v2 = face_vertices
|
|
477
|
+
return np.abs(np.dot(np.cross(v1 - v0, v2 - v0), cell_center - v0)) / 6.0
|
|
478
|
+
elif face_vertices.shape[0] == 4:
|
|
479
|
+
# 3D: Volume of the pyramid
|
|
480
|
+
# WARNING crude approximation
|
|
481
|
+
v0, v1, v2 = face_vertices
|
|
482
|
+
return np.abs(np.dot(np.cross(v1 - v0, v2 - v0), cell_center - v0)) / 6.0
|
|
483
|
+
else:
|
|
484
|
+
assert False
|
|
485
|
+
|
|
486
|
+
else:
|
|
487
|
+
raise ValueError("Unsupported dimensionality")
|
misc/__init__.py
ADDED
|
File without changes
|