zoomy-core 0.1.1__py3-none-any.whl → 0.1.2__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 zoomy-core might be problematic. Click here for more details.

Files changed (57) hide show
  1. zoomy_core/decorators/decorators.py +25 -0
  2. zoomy_core/fvm/flux.py +97 -0
  3. zoomy_core/fvm/nonconservative_flux.py +97 -0
  4. zoomy_core/fvm/ode.py +55 -0
  5. zoomy_core/fvm/solver_numpy.py +305 -0
  6. zoomy_core/fvm/timestepping.py +13 -0
  7. zoomy_core/mesh/gmsh_loader.py +301 -0
  8. zoomy_core/mesh/mesh.py +1192 -0
  9. zoomy_core/mesh/mesh_extrude.py +168 -0
  10. zoomy_core/mesh/mesh_util.py +487 -0
  11. zoomy_core/misc/custom_types.py +6 -0
  12. zoomy_core/misc/gui.py +61 -0
  13. zoomy_core/misc/interpolation.py +140 -0
  14. zoomy_core/misc/io.py +401 -0
  15. zoomy_core/misc/logger_config.py +18 -0
  16. zoomy_core/misc/misc.py +216 -0
  17. zoomy_core/misc/static_class.py +94 -0
  18. zoomy_core/model/analysis.py +147 -0
  19. zoomy_core/model/basefunction.py +113 -0
  20. zoomy_core/model/basemodel.py +512 -0
  21. zoomy_core/model/boundary_conditions.py +193 -0
  22. zoomy_core/model/initial_conditions.py +171 -0
  23. zoomy_core/model/model.py +63 -0
  24. zoomy_core/model/models/GN.py +70 -0
  25. zoomy_core/model/models/advection.py +53 -0
  26. zoomy_core/model/models/basisfunctions.py +181 -0
  27. zoomy_core/model/models/basismatrices.py +377 -0
  28. zoomy_core/model/models/core.py +564 -0
  29. zoomy_core/model/models/coupled_constrained.py +60 -0
  30. zoomy_core/model/models/old_smm copy.py +867 -0
  31. zoomy_core/model/models/poisson.py +41 -0
  32. zoomy_core/model/models/shallow_moments.py +757 -0
  33. zoomy_core/model/models/shallow_moments_sediment.py +378 -0
  34. zoomy_core/model/models/shallow_moments_topo.py +423 -0
  35. zoomy_core/model/models/shallow_moments_variants.py +1509 -0
  36. zoomy_core/model/models/shallow_water.py +266 -0
  37. zoomy_core/model/models/shallow_water_topo.py +111 -0
  38. zoomy_core/model/models/shear_shallow_flow.py +594 -0
  39. zoomy_core/model/models/sme_turbulent.py +613 -0
  40. zoomy_core/model/models/swe_old.py +1018 -0
  41. zoomy_core/model/models/vam.py +455 -0
  42. zoomy_core/postprocessing/postprocessing.py +72 -0
  43. zoomy_core/preprocessing/openfoam_moments.py +452 -0
  44. zoomy_core/transformation/helpers.py +25 -0
  45. zoomy_core/transformation/to_amrex.py +238 -0
  46. zoomy_core/transformation/to_c.py +181 -0
  47. zoomy_core/transformation/to_jax.py +14 -0
  48. zoomy_core/transformation/to_numpy.py +115 -0
  49. zoomy_core/transformation/to_openfoam.py +254 -0
  50. zoomy_core/transformation/to_ufl.py +67 -0
  51. {zoomy_core-0.1.1.dist-info → zoomy_core-0.1.2.dist-info}/METADATA +1 -1
  52. zoomy_core-0.1.2.dist-info/RECORD +55 -0
  53. zoomy_core-0.1.2.dist-info/top_level.txt +1 -0
  54. zoomy_core-0.1.1.dist-info/RECORD +0 -5
  55. zoomy_core-0.1.1.dist-info/top_level.txt +0 -1
  56. {zoomy_core-0.1.1.dist-info → zoomy_core-0.1.2.dist-info}/WHEEL +0 -0
  57. {zoomy_core-0.1.1.dist-info → zoomy_core-0.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -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")
@@ -0,0 +1,6 @@
1
+ import numpy as np
2
+ import numpy.typing as npt
3
+
4
+ FArray = npt.NDArray[np.float64]
5
+ IArray = npt.NDArray[np.int64]
6
+ CArray = npt.NDArray[np.str_]