zoomy-core 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl

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

Potentially problematic release.


This version of zoomy-core might be problematic. Click here for more details.

Files changed (53) 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/mesh.py +1234 -0
  8. zoomy_core/mesh/mesh_extrude.py +168 -0
  9. zoomy_core/mesh/mesh_util.py +487 -0
  10. zoomy_core/misc/custom_types.py +6 -0
  11. zoomy_core/misc/interpolation.py +140 -0
  12. zoomy_core/misc/io.py +438 -0
  13. zoomy_core/misc/logger_config.py +18 -0
  14. zoomy_core/misc/misc.py +216 -0
  15. zoomy_core/misc/static_class.py +94 -0
  16. zoomy_core/model/analysis.py +147 -0
  17. zoomy_core/model/basefunction.py +113 -0
  18. zoomy_core/model/basemodel.py +512 -0
  19. zoomy_core/model/boundary_conditions.py +193 -0
  20. zoomy_core/model/initial_conditions.py +171 -0
  21. zoomy_core/model/model.py +63 -0
  22. zoomy_core/model/models/GN.py +70 -0
  23. zoomy_core/model/models/advection.py +53 -0
  24. zoomy_core/model/models/basisfunctions.py +181 -0
  25. zoomy_core/model/models/basismatrices.py +377 -0
  26. zoomy_core/model/models/core.py +564 -0
  27. zoomy_core/model/models/coupled_constrained.py +60 -0
  28. zoomy_core/model/models/poisson.py +41 -0
  29. zoomy_core/model/models/shallow_moments.py +757 -0
  30. zoomy_core/model/models/shallow_moments_sediment.py +378 -0
  31. zoomy_core/model/models/shallow_moments_topo.py +423 -0
  32. zoomy_core/model/models/shallow_moments_variants.py +1509 -0
  33. zoomy_core/model/models/shallow_water.py +266 -0
  34. zoomy_core/model/models/shallow_water_topo.py +111 -0
  35. zoomy_core/model/models/shear_shallow_flow.py +594 -0
  36. zoomy_core/model/models/sme_turbulent.py +613 -0
  37. zoomy_core/model/models/vam.py +455 -0
  38. zoomy_core/postprocessing/postprocessing.py +72 -0
  39. zoomy_core/preprocessing/openfoam_moments.py +452 -0
  40. zoomy_core/transformation/helpers.py +25 -0
  41. zoomy_core/transformation/to_amrex.py +238 -0
  42. zoomy_core/transformation/to_c.py +181 -0
  43. zoomy_core/transformation/to_jax.py +14 -0
  44. zoomy_core/transformation/to_numpy.py +115 -0
  45. zoomy_core/transformation/to_openfoam.py +254 -0
  46. zoomy_core/transformation/to_ufl.py +67 -0
  47. {zoomy_core-0.1.1.dist-info → zoomy_core-0.1.3.dist-info}/METADATA +2 -1
  48. zoomy_core-0.1.3.dist-info/RECORD +51 -0
  49. zoomy_core-0.1.3.dist-info/top_level.txt +1 -0
  50. zoomy_core-0.1.1.dist-info/RECORD +0 -5
  51. zoomy_core-0.1.1.dist-info/top_level.txt +0 -1
  52. {zoomy_core-0.1.1.dist-info → zoomy_core-0.1.3.dist-info}/WHEEL +0 -0
  53. {zoomy_core-0.1.1.dist-info → zoomy_core-0.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1234 @@
1
+ import os
2
+
3
+ try:
4
+ import h5py
5
+
6
+ _HAVE_H5PY = True
7
+ except ImportError:
8
+ _HAVE_H5PY = False
9
+
10
+
11
+ try:
12
+ from petsc4py import PETSc
13
+
14
+ _HAVE_PETSC = True
15
+ except ImportError:
16
+ _HAVE_PETSC = False
17
+
18
+
19
+ try:
20
+ import meshio
21
+
22
+ _HAVE_MESHIO = True
23
+ except:
24
+ _HAVE_MESHIO = False
25
+ from copy import deepcopy
26
+ from itertools import product
27
+ from typing import Union
28
+
29
+ import numpy as np
30
+ from attr import define
31
+
32
+ import library.zoomy_core.mesh.mesh_extrude as extrude
33
+ import library.zoomy_core.mesh.mesh_util as mesh_util
34
+ from library.zoomy_core.mesh.mesh_util import compute_subvolume, get_extruded_mesh_type
35
+ from library.zoomy_core.misc.custom_types import CArray, FArray, IArray
36
+ from library.zoomy_core.misc.static_class import register_static_pytree
37
+ from library.zoomy_core.model.boundary_conditions import Periodic
38
+
39
+ # petsc4py.init(sys.argv)
40
+
41
+
42
+ def build_monomial_indices(degree: int, dim: int):
43
+ """
44
+ Build the full set of monomial multi-indices for a given degree and dimension.
45
+
46
+ Parameters:
47
+ - degree (int): Maximum total polynomial degree
48
+ - dim (int): Number of dimensions
49
+
50
+ Returns:
51
+ - List of tuples: each tuple is a multi-index (α₁, ..., α_dim)
52
+ """
53
+ mon_indices = []
54
+ for powers in product(range(degree + 1), repeat=dim):
55
+ if sum(powers) <= degree:
56
+ if sum(powers) == 0:
57
+ continue
58
+ mon_indices.append(powers)
59
+ return mon_indices
60
+
61
+
62
+ def scale_lsq_derivative(mon_indices):
63
+ from math import factorial
64
+
65
+ import numpy as np
66
+
67
+ scale_factors = np.array(
68
+ [np.prod([factorial(k) for k in mi]) for mi in mon_indices]
69
+ ) # shape (n_monomials,)
70
+ return scale_factors
71
+
72
+
73
+ def find_derivative_indices(full_monomials_arr, requested_derivs_arr):
74
+ """
75
+ Args:
76
+ full_monomials_arr: shape (N, dim)
77
+ requested_derivs_arr: shape (M, dim)
78
+
79
+ Returns:
80
+ indices: shape (M,), where indices[i] gives the index of requested_derivs_arr[i]
81
+ in full_monomials_arr, or -1 if not found
82
+ """
83
+ # Ensure arrays
84
+ full_monomials_arr = np.array(full_monomials_arr)
85
+ requested_derivs_arr = np.array(requested_derivs_arr)
86
+
87
+ # Compare all requested derivatives with all monomials
88
+ matches = np.all(
89
+ full_monomials_arr[:, None, :] == requested_derivs_arr[None, :, :], axis=-1
90
+ ) # (N, M)
91
+
92
+ # Which monomials match each requested derivative?
93
+ found = np.any(matches, axis=0) # (M,)
94
+ indices = np.argmax(matches, axis=0) # (M,) — returns 0 even if not found
95
+
96
+ # Replace unfound indices with -1
97
+ indices = np.where(found, indices, -1)
98
+ return indices
99
+
100
+
101
+ def compute_derivatives(u, mesh, derivatives_multi_index=None):
102
+ A_glob = mesh.lsq_gradQ # shape (n_cells, n_monomials, n_neighbors)
103
+ neighbors = mesh.lsq_neighbors # list of neighbors per cell
104
+ mon_indices = mesh.lsq_monomial_multi_index # shape (n_monomials, dim)
105
+ # scale_factors = scale_lsq_derivative(mon_indices)
106
+ scale_factors = mesh.lsq_scale_factors
107
+
108
+ if derivatives_multi_index is None:
109
+ derivatives_multi_index = mon_indices
110
+ indices = find_derivative_indices(mon_indices, derivatives_multi_index)
111
+
112
+ def reconstruct_cell(A_loc, neighbor_idx, u_i):
113
+ u_neighbors = u[neighbor_idx]
114
+ delta_u = u_neighbors - u_i
115
+ return (scale_factors * (A_loc.T @ delta_u)).T # shape (n_monomials,)
116
+
117
+ out = np.zeros((A_glob.shape[0], len(
118
+ derivatives_multi_index)), dtype=float)
119
+ for i in range(A_glob.shape[0]):
120
+ A_loc = A_glob[i]
121
+ neighbor_idx = neighbors[i]
122
+ u_i = u[i]
123
+ out[i, :] = reconstruct_cell(A_loc, neighbor_idx, u_i)
124
+
125
+ return out
126
+
127
+
128
+ def get_polynomial_degree(mon_indices):
129
+ return max(sum(m) for m in mon_indices)
130
+
131
+
132
+ def get_required_monomials_count(degree, dim):
133
+ from math import comb
134
+
135
+ return comb(int(degree + dim), int(dim))
136
+
137
+
138
+ def build_vandermonde(cell_diffs, mon_indices):
139
+ n_neighbors, dim = cell_diffs.shape
140
+ n_monomials = len(mon_indices)
141
+ V = np.zeros((n_neighbors, n_monomials))
142
+ for i, powers in enumerate(mon_indices):
143
+ V[:, i] = np.prod(cell_diffs**powers, axis=1)
144
+ return V
145
+
146
+
147
+ def expand_neighbors(neighbors_list, initial_neighbors):
148
+ # Expand to the next ring: union of neighbors of neighbors
149
+ expanded = set(initial_neighbors)
150
+ for n in initial_neighbors:
151
+ expanded.update(neighbors_list[n])
152
+ return list(expanded)
153
+
154
+
155
+ def compute_gaussian_weights(dX, sigma=1.0):
156
+ distances = np.linalg.norm(dX, axis=1)
157
+ weights = np.exp(-((distances / sigma) ** 2))
158
+ return weights
159
+
160
+
161
+ def least_squares_reconstruction_local(
162
+ n_cells, dim, neighbors_list, cell_centers, lsq_degree
163
+ ):
164
+ """
165
+ Build the full set of monomial multi-indices for a given degree and dimension.
166
+
167
+ Parameters:
168
+ - degree (int): Maximum total polynomial degree
169
+ - dim (int): Number of dimensions
170
+
171
+ Returns:
172
+ - List of tuples: each tuple is a multi-index (α₁, ..., α_dim)
173
+ """
174
+ mon_indices = build_monomial_indices(lsq_degree, dim)
175
+ A_glob = []
176
+ neighbors_all = [] # to store expanded neighbors per cell
177
+ degree = get_polynomial_degree(mon_indices)
178
+ required_neighbors = get_required_monomials_count(degree, dim)
179
+
180
+ # First, expand neighborhoods and store them
181
+ for i_c in range(n_cells):
182
+ current_neighbors = list(neighbors_list[i_c])
183
+
184
+ # Expand neighbor rings until we have enough
185
+ while len(current_neighbors) < required_neighbors:
186
+ new_neighbors = expand_neighbors(neighbors_list, current_neighbors)
187
+ current_neighbors = list(set(new_neighbors) - {i_c})
188
+
189
+ neighbors_all.append(current_neighbors)
190
+
191
+ # Compute max neighbors after expansion
192
+ max_neighbors = max(len(nbrs) for nbrs in neighbors_all)
193
+
194
+ A_glob = []
195
+ neighbors_array = np.empty((n_cells, max_neighbors), dtype=int)
196
+
197
+ for i_c in range(n_cells):
198
+ current_neighbors = list(neighbors_all[i_c]) # previously expanded
199
+ n_nbr = len(current_neighbors)
200
+
201
+ # Expand further if still under-resolved
202
+ while n_nbr < max_neighbors:
203
+ extended_neighbors = expand_neighbors(
204
+ neighbors_list, current_neighbors)
205
+ extended_neighbors = list(
206
+ set(extended_neighbors) - {i_c}) # Remove self
207
+ # Keep only new neighbors not already present
208
+ new_neighbors = [
209
+ n for n in extended_neighbors if n not in current_neighbors
210
+ ]
211
+ current_neighbors.extend(new_neighbors)
212
+ n_nbr = len(current_neighbors)
213
+
214
+ # Stop if enough neighbors collected
215
+ if n_nbr >= max_neighbors:
216
+ break
217
+
218
+ # Trim or pad neighbor list to exact max_neighbors
219
+ if len(current_neighbors) >= max_neighbors:
220
+ trimmed_neighbors = current_neighbors[:max_neighbors]
221
+ else:
222
+ padding = [i_c] * (max_neighbors - len(current_neighbors))
223
+ trimmed_neighbors = current_neighbors + padding
224
+
225
+ neighbors_array[i_c, :] = trimmed_neighbors
226
+
227
+ # Build dX matrix for least squares
228
+ dX = np.zeros((max_neighbors, dim), dtype=float)
229
+ for j, neighbor in enumerate(trimmed_neighbors):
230
+ dX[j, :] = cell_centers[neighbor] - cell_centers[i_c]
231
+
232
+ # shape (max_neighbors, n_monomials)
233
+ V = build_vandermonde(dX, mon_indices)
234
+
235
+ # Compute weights
236
+ weights = compute_gaussian_weights(dX) # shape (n_neighbors,)
237
+ W = np.diag(weights) # shape (n_neighbors, n_neighbors)
238
+ VW = W @ V # shape (n_neighbors, n_monomials)
239
+ alpha = dX.min() * 10 ** (-8)
240
+ A_loc = (
241
+ np.linalg.pinv(VW.T @ VW + alpha * np.eye(VW.shape[1])) @ VW.T @ W
242
+ ) # shape (n_monomials, n_neighbors)
243
+ # A_loc = np.linalg.pinv(V).T # shape (n_monomials, max_neighbors)
244
+ A_glob.append(A_loc.T)
245
+
246
+ A_glob = np.array(A_glob) # shape (n_cells, n_monomials, max_neighbors)
247
+ return A_glob, neighbors_array, mon_indices
248
+
249
+
250
+ def get_physical_boundary_labels(filepath):
251
+ if not _HAVE_MESHIO:
252
+ raise RuntimeError(
253
+ "_write_to_vtk_from_vertices_edges requires meshio, which is not available."
254
+ )
255
+ mesh = meshio.read(filepath)
256
+ boundary_dict = {key: value[0] for key, value in mesh.field_data.items()}
257
+ return boundary_dict
258
+
259
+
260
+ def _compute_inradius_generic(cell_center, face_centers, face_normals):
261
+ """
262
+ strategy: find the shortest path from the center to each side (defined by face center and normal)
263
+ use the minimum of all these shortest paths
264
+ For a distorted element, the inradius might be zero. In this case, use minimal distance of center to face_centers
265
+ """
266
+ inradius = np.inf
267
+ for center, normal in zip(face_centers, face_normals):
268
+ distance = np.abs(np.dot(center - cell_center, normal))
269
+ inradius = min(inradius, distance)
270
+ if inradius <= 0:
271
+ inradius = np.array(face_centers - cell_center).min()
272
+ return inradius
273
+
274
+
275
+ def compute_cell_inradius(dm):
276
+ """
277
+ Error in petsc4py? dm.getMinRadius() always returns 0
278
+ strategy: find the shortest path from the center to each side (defined by face center and normal)
279
+ use the minimum of all these shortest paths
280
+ """
281
+ (cStart, cEnd) = dm.getHeightStratum(0)
282
+ (vStart, vEnd) = dm.getDepthStratum(0)
283
+ (eStart, eEnd) = dm.getDepthStratum(1)
284
+ inradius = []
285
+ for c in range(cStart, cEnd):
286
+ _, cell_center, _ = dm.computeCellGeometryFVM(c)
287
+ face_normals = []
288
+ face_centers = []
289
+ faces = dm.getCone(c)
290
+ for f in faces:
291
+ _, center, normal = dm.computeCellGeometryFVM(f)
292
+ face_normals.append(normal)
293
+ face_centers.append(center)
294
+ inradius.append(
295
+ _compute_inradius_generic(cell_center, face_centers, face_normals)
296
+ )
297
+ return np.array(inradius, dtype=float)
298
+
299
+
300
+ def get_mesh_type_from_dm(num_faces_per_cell, dim):
301
+ if dim == 1:
302
+ if num_faces_per_cell == 2:
303
+ return "line"
304
+ else:
305
+ assert False
306
+ elif dim == 2:
307
+ if num_faces_per_cell == 3:
308
+ return "triangle"
309
+ elif num_faces_per_cell == 4:
310
+ return "quad"
311
+ else:
312
+ assert False
313
+ elif dim == 3:
314
+ if num_faces_per_cell == 4:
315
+ return "tetra"
316
+ elif num_faces_per_cell == 6:
317
+ return "edge"
318
+ elif num_faces_per_cell == 8:
319
+ return "hexahedron"
320
+ else:
321
+ assert False
322
+ assert False
323
+
324
+
325
+ def _boundary_dict_to_list(d):
326
+ l = []
327
+ for i, vs in enumerate(d.values()):
328
+ l += vs
329
+ return l
330
+
331
+
332
+ def _boundary_dict_indices(d):
333
+ indices = []
334
+ index = 0
335
+ for values in d.values():
336
+ indices += [index for v in values]
337
+ index += 1
338
+ return np.array(indices, dtype=int)
339
+
340
+
341
+ def _get_neighberhood(dm, cell, cStart=0):
342
+ neighbors = (
343
+ np.array(
344
+ [dm.getSupport(f)[dm.getSupport(f) != cell][0]
345
+ for f in dm.getCone(cell)],
346
+ dtype=int,
347
+ )
348
+ - cStart
349
+ )
350
+ return neighbors
351
+
352
+
353
+ def _fill_neighborhood(dm, neighbors, max_neighbors, cStart=0):
354
+ new_potential_neighbors = []
355
+ for cell in neighbors:
356
+ new_potential_neighbors += list(_get_neighberhood(dm,
357
+ cell, cStart=cStart))
358
+ new_potential_neighbors = np.array(new_potential_neighbors)
359
+ new_neighbors = np.setdiff1d(new_potential_neighbors, neighbors)
360
+ neighbors = np.concatenate((neighbors, new_neighbors))[:max_neighbors]
361
+ if len(neighbors) == max_neighbors:
362
+ return neighbors
363
+ else:
364
+ return _fill_neighborhood(dm, neighbors, max_neighbors, cStart=cStart)
365
+
366
+
367
+ @register_static_pytree
368
+ @define(slots=True, frozen=True)
369
+ class Mesh:
370
+ dimension: int
371
+ type: str
372
+ n_cells: int
373
+ n_inner_cells: int
374
+ n_faces: int
375
+ n_vertices: int
376
+ n_boundary_faces: int
377
+ n_faces_per_cell: int
378
+ vertex_coordinates: FArray
379
+ cell_vertices: IArray
380
+ cell_faces: IArray
381
+ cell_volumes: FArray
382
+ cell_centers: FArray
383
+ cell_inradius: FArray
384
+ cell_neighbors: IArray
385
+ boundary_face_cells: IArray
386
+ boundary_face_ghosts: IArray
387
+ boundary_face_function_numbers: IArray
388
+ boundary_face_physical_tags: IArray
389
+ boundary_face_face_indices: IArray
390
+ face_cells: IArray
391
+ # face_cell_face_index: IArray
392
+ face_normals: FArray
393
+ face_volumes: FArray
394
+ face_centers: FArray
395
+ face_subvolumes: FArray
396
+ face_neighbors: IArray
397
+ boundary_conditions_sorted_physical_tags: IArray
398
+ boundary_conditions_sorted_names: CArray
399
+ lsq_gradQ: FArray
400
+ lsq_neighbors: FArray
401
+ lsq_monomial_multi_index: int
402
+ lsq_scale_factors: FArray
403
+ z_ordering: IArray
404
+
405
+ def resolve_periodic_bcs(self, bcs):
406
+ """
407
+ Goal: if 'apply_boundary_condition' is called, the ghost cell value is computed, given an input cell value funtion.
408
+ In case of a periodic BC, this is NOT the adjacent cell. So I need to change the 'boundary_face_cell' for the periodic
409
+ cells to point to the right data location!
410
+ This is why we only overwrite the 'mesh.boundary_face_cell' in the end.
411
+ Furthermore, I CANNOT alter any ordering! However, I need to sort the two boundaries such that the e.g.
412
+ left and right border are mapped correctly, as the boundary cells are not ordered.
413
+ As ghost/inner cells is confusing here, I rather like to use 'from' and 'to', as 'from' data from which bc is copied and 'to' stands for the boundary where it is copied to. As we only change the cells where the data is taken from (from the adjacent to the periodic cell), we only alter the 'boundary_face_cell' in the end.
414
+ """
415
+ dict_physical_name_to_index = {
416
+ v: i for i, v in enumerate(self.boundary_conditions_sorted_names)
417
+ }
418
+ dict_function_index_to_physical_tag = {
419
+ i: v for i, v in enumerate(self.boundary_conditions_sorted_physical_tags)
420
+ }
421
+
422
+ boundary_face_cells_copy = deepcopy(self.boundary_face_cells)
423
+
424
+ for i_bc, bc in enumerate(bcs._boundary_conditions):
425
+ if type(bc) == Periodic:
426
+ from_physical_tag = dict_function_index_to_physical_tag[
427
+ dict_physical_name_to_index[bc.periodic_to_physical_tag]
428
+ ]
429
+ to_physical_tag = dict_function_index_to_physical_tag[
430
+ dict_physical_name_to_index[bc.tag]
431
+ ]
432
+
433
+ mask_face_from = self.boundary_face_physical_tags == from_physical_tag
434
+ mask_face_to = self.boundary_face_physical_tags == to_physical_tag
435
+
436
+ # I want to copy from boundary_face_cells to boundary_face_ghosts!
437
+ from_cells = self.boundary_face_cells[mask_face_from]
438
+ to_cells = self.boundary_face_ghosts[mask_face_to]
439
+
440
+ from_coords = self.cell_centers[:, from_cells]
441
+ to_coords = self.cell_centers[:, to_cells]
442
+
443
+ # sort not dimension by dimension, but most significant dimension to least significant dimension
444
+ # determine significance by max difference
445
+ significance_per_dimension = [
446
+ from_coords[d, :].max() - from_coords[d, :].min()
447
+ for d in range(self.dimension)
448
+ ]
449
+ _significance_per_dimension = [
450
+ to_coords[d, :].max() - to_coords[d, :].min()
451
+ for d in range(self.dimension)
452
+ ]
453
+ # reverse the order of lexsort such that the most important is first IS NOT NEEDED, since lexsort starts sorting by the last entry in the list
454
+ sort_order_significance = np.lexsort(
455
+ [significance_per_dimension])
456
+
457
+ from_cells_sort_order = np.lexsort(
458
+ [from_coords[d, :] for d in sort_order_significance]
459
+ )
460
+ to_cells_sort_order = np.lexsort(
461
+ [to_coords[d, :] for d in sort_order_significance]
462
+ )
463
+
464
+ # generates indices from 0 to number of ghost_cells (total)
465
+ indices = np.array(list(range(mask_face_to.shape[0])))
466
+ # masks away all cells that do not belong to this tag
467
+ indices_to = indices[mask_face_to]
468
+ # sort the indices
469
+ indices_to_sort = indices_to[to_cells_sort_order]
470
+
471
+ indices_from = indices[mask_face_from]
472
+ indices_from_sort = indices_from[from_cells_sort_order]
473
+
474
+ self.boundary_face_cells[indices_to_sort] = boundary_face_cells_copy[
475
+ indices_from_sort
476
+ ]
477
+
478
+ @classmethod
479
+ def create_1d(cls, domain: tuple[float, float], n_inner_cells: int, lsq_degree=1):
480
+ xL = domain[0]
481
+ xR = domain[1]
482
+
483
+ n_cells = n_inner_cells + 2
484
+ n_vertices = n_inner_cells + 1
485
+ dimension = 1
486
+ n_faces_per_cell = 2
487
+ n_faces = n_inner_cells + 1
488
+ n_boundary_faces = 2
489
+ dx = (xR - xL) / n_inner_cells
490
+ vertex_coordinates = np.zeros((n_vertices, 1))
491
+ vertex_coordinates[:, 0] = np.linspace(xL, xR, n_vertices, dtype=float)
492
+ cell_vertices = np.zeros((n_inner_cells, n_faces_per_cell), dtype=int)
493
+ cell_vertices[:, 0] = list(range(0, n_vertices - 1))
494
+ cell_vertices[:, 1] = list(range(1, n_vertices))
495
+ cell_volumes = dx * np.ones(n_cells, dtype=float)
496
+ cell_inradius = dx / 2 * np.ones(n_cells, dtype=float)
497
+
498
+ face_normals = np.zeros((n_faces, dimension), dtype=float)
499
+ face_volumes = np.ones((n_faces), dtype=float)
500
+
501
+ cell_centers = np.zeros((n_cells, 3), dtype=float)
502
+ cell_centers[:n_inner_cells, 0] = np.arange(xL + dx / 2, xR, dx)
503
+ cell_centers[n_inner_cells, 0] = xL - dx / 2
504
+ cell_centers[n_inner_cells + 1, 0] = xR + dx / 2
505
+ cell_neighbors = (n_cells + 1) * \
506
+ np.ones((n_cells, n_faces_per_cell), dtype=int)
507
+
508
+ cell_faces = np.empty((n_inner_cells, n_faces_per_cell), dtype=int)
509
+ cell_faces[:, 0] = list(range(0, n_faces - 1))
510
+ cell_faces[:, 1] = list(range(1, n_faces))
511
+
512
+ # inner cells
513
+ for i_cell in range(0, n_cells):
514
+ cell_neighbors[i_cell, :] = [i_cell - 1, i_cell + 1]
515
+ # left neighbor of 0is the first ghost
516
+ cell_neighbors[0, 0] = n_inner_cells
517
+ # right neighbor of n_inner_cell is the second ghost
518
+ cell_neighbors[n_inner_cells - 1, 1] = n_inner_cells + 1
519
+ # left neighbor of first ghost is empty, but we add the neighbor of the neighbor
520
+ cell_neighbors[n_inner_cells, 0] = 1
521
+ # right neighbor of first ghost is first cell
522
+ cell_neighbors[n_inner_cells, 1] = 0
523
+ # left neighbor of second ghost is last inner
524
+ cell_neighbors[n_inner_cells + 1, 0] = n_inner_cells - 1
525
+ # right neighbor of second ghost is empty, but we add the neighbor of the neighbor
526
+ cell_neighbors[n_inner_cells + 1, 1] = n_inner_cells - 2
527
+
528
+ for i_face in range(0, n_faces):
529
+ face_normals[i_face, 0] = 1.0
530
+
531
+ boundary_face_cells = np.array([0, n_inner_cells - 1], dtype=int)
532
+ boundary_face_ghosts = np.array(
533
+ [n_inner_cells, n_inner_cells + 1], dtype=int)
534
+ boundary_face_function_numbers = np.empty(
535
+ (n_boundary_faces), dtype=int)
536
+ boundary_face_function_numbers[0] = 0
537
+ boundary_face_function_numbers[1] = 1
538
+ boundary_face_physical_tags = np.array([0, 1], dtype=int)
539
+ boundary_face_face_indices = np.array([0, n_faces - 1], dtype=int)
540
+
541
+ face_cells = np.empty((n_faces, 2), dtype=int)
542
+ # face_cell_face_index = (n_faces + 1)*np.ones((n_faces, 2), dtype=int)
543
+ face_cells[1: n_faces - 1, 0] = list(range(0, n_inner_cells - 1))
544
+ face_cells[1: n_faces - 1, 1] = list(range(1, n_inner_cells))
545
+ # face_cell_face_index[1:n_faces-1, 0] = 1
546
+ # face_cell_face_index[1:n_faces-1, 1] = 0
547
+ face_cells[0, 0] = n_inner_cells
548
+ # face_cell_face_index[0, 0] = 0
549
+ face_cells[0, 1] = 0
550
+ # face_cell_face_index[0, 1] = 0
551
+ face_cells[-1, 0] = n_inner_cells - 1
552
+ # face_cell_face_index[-1, 0] = 1
553
+ face_cells[-1, 1] = n_inner_cells + 1
554
+ # face_cell_face_index[-1, 1] = 0
555
+ face_centers = 0.5 * (
556
+ cell_centers[face_cells[:, 0]] + cell_centers[face_cells[:, 1]]
557
+ )
558
+
559
+ face_subvolumes = np.empty((n_faces, 2), dtype=float)
560
+ face_subvolumes[:, 0] = dx / 2
561
+ face_subvolumes[:, 1] = dx / 2
562
+
563
+ boundary_conditions_sorted_physical_tags = np.array([0, 1], dtype=int)
564
+ boundary_conditions_sorted_names = np.array(["left", "right"])
565
+
566
+ lsq_gradQ = np.zeros((n_cells, dimension, n_cells), dtype=float)
567
+ deltaQ = np.zeros((n_cells, n_faces_per_cell, n_cells), dtype=float)
568
+ cell_centers = cell_centers[:, :dimension]
569
+
570
+ polynomial_degree = 1
571
+ n_neighbors = n_faces_per_cell * polynomial_degree
572
+ dim = 1
573
+ lsq_gradQ, lsq_neighbors, lsq_monomial_multi_index = (
574
+ least_squares_reconstruction_local(
575
+ n_cells, dim, cell_neighbors, cell_centers, lsq_degree
576
+ )
577
+ )
578
+ lsq_scale_factors = scale_lsq_derivative(lsq_monomial_multi_index)
579
+
580
+ n_face_neighbors = 2
581
+ face_neighbors = (n_cells + 1) * \
582
+ np.ones((n_faces, n_face_neighbors), dtype=int)
583
+
584
+ for i_f, neighbors in enumerate(face_cells):
585
+ face_neighbors[i_f] = neighbors
586
+
587
+ z_ordering = np.array([-1], dtype=float)
588
+
589
+ # return cls(dimension, 'line', n_cells, n_cells + 1, 2, n_faces_per_element, vertex_coordinates, element_vertices, element_face_areas, element_centers, element_volume, element_inradius, element_face_normals, element_n_neighbors, element_neighbors, element_neighbors_face_index, boundary_face_vertices, boundary_face_corresponding_element, boundary_face_element_face_index, boundary_face_tag, boundary_tag_names)
590
+ return cls(
591
+ dimension,
592
+ "line",
593
+ n_cells,
594
+ n_inner_cells,
595
+ n_faces,
596
+ n_vertices,
597
+ n_boundary_faces,
598
+ n_faces_per_cell,
599
+ vertex_coordinates.T,
600
+ cell_vertices.T,
601
+ cell_faces.T,
602
+ cell_volumes,
603
+ cell_centers.T,
604
+ cell_inradius,
605
+ cell_neighbors,
606
+ boundary_face_cells.T,
607
+ boundary_face_ghosts.T,
608
+ boundary_face_function_numbers,
609
+ boundary_face_physical_tags,
610
+ boundary_face_face_indices.T,
611
+ face_cells.T,
612
+ face_normals.T,
613
+ face_volumes,
614
+ face_centers,
615
+ face_subvolumes,
616
+ face_neighbors,
617
+ boundary_conditions_sorted_physical_tags,
618
+ boundary_conditions_sorted_names,
619
+ lsq_gradQ,
620
+ lsq_neighbors,
621
+ lsq_monomial_multi_index,
622
+ lsq_scale_factors,
623
+ z_ordering,
624
+ )
625
+
626
+ def _compute_ascending_order_structured_axis(self):
627
+ cell_centers = self.cell_centers.T
628
+ dimension = self.dimension
629
+ if dimension == 1:
630
+ order = np.lexsort((cell_centers[:, 0],))
631
+ elif dimension == 2:
632
+ order = np.lexsort(
633
+ (
634
+ cell_centers[:, 0],
635
+ cell_centers[:, 1],
636
+ )
637
+ )
638
+ elif dimension == 3:
639
+ order = np.lexsort(
640
+ (
641
+ cell_centers[:, 0],
642
+ cell_centers[:, 1],
643
+ cell_centers[:, 2],
644
+ )
645
+ )
646
+ else:
647
+ assert False
648
+
649
+ # find number of cells in z (Nz) from first column
650
+ Nz = 1
651
+ while cell_centers[order[Nz], 2] > cell_centers[order[Nz - 1], 2]:
652
+ Nz += 1
653
+ Nz += 1
654
+
655
+ # partition order into [(Nx x Ny) , (Nz)]
656
+ N = int(cell_centers.shape[0] / Nz)
657
+ assert cell_centers.shape[0] == N * Nz
658
+ order = order.reshape((N, Nz))
659
+
660
+ coords_z = order[0]
661
+ for o in order:
662
+ assert o == coords_z
663
+
664
+ return order
665
+
666
+ @classmethod
667
+ def from_gmsh(cls, filepath, allow_z_integration=False, lsq_degree=1):
668
+ if not _HAVE_PETSC:
669
+ raise RuntimeError(
670
+ "Mesh.from_gmsh() requires petsc4py, which is not available."
671
+ )
672
+ dm = PETSc.DMPlex().createFromFile(filepath, comm=PETSc.COMM_WORLD)
673
+ boundary_dict = get_physical_boundary_labels(filepath)
674
+ (cStart, cEnd) = dm.getHeightStratum(0)
675
+ (vStart, vEnd) = dm.getDepthStratum(0)
676
+ # (eStart, eEnd) = dm.getDepthStratum(1)
677
+ (eStart, eEnd) = dm.getHeightStratum(1)
678
+ gdm = dm.clone()
679
+ gdm.constructGhostCells()
680
+ (cgStart, cgEnd) = gdm.getHeightStratum(0)
681
+ (vgStart, vgEnd) = gdm.getDepthStratum(0)
682
+ # (egStart, egEnd) = gdm.getDepthStratum(1)
683
+ (egStart, egEnd) = gdm.getHeightStratum(1)
684
+
685
+ dim = dm.getDimension()
686
+ n_faces_per_cell = len(gdm.getCone(cgStart))
687
+ n_vertices_per_cell = n_faces_per_cell
688
+ if dim > 2:
689
+ transitive_closure_points, transitive_closure_orientation = (
690
+ dm.getTransitiveClosure(cStart, useCone=True)
691
+ )
692
+ n_vertices_per_cell = transitive_closure_points[
693
+ np.logical_and(
694
+ transitive_closure_points >= vStart,
695
+ transitive_closure_points < vEnd,
696
+ )
697
+ ].shape[0]
698
+ n_cells = cgEnd - cgStart
699
+ n_inner_cells = cEnd - cStart
700
+ n_faces = egEnd - egStart
701
+ n_vertices = vEnd - vStart
702
+ cell_vertices = np.zeros(
703
+ (n_inner_cells, n_vertices_per_cell), dtype=int)
704
+ cell_faces = np.zeros((n_inner_cells, n_faces_per_cell), dtype=int)
705
+ cell_centers = np.zeros((n_cells, 3), dtype=float)
706
+ # I create cell_volumes of size n_cells because then I can avoid an if clause in the numerical flux computation. The values will be delted after using apply_boundary_conditions anyways
707
+ cell_volumes = np.ones((n_cells), dtype=float)
708
+ cell_inradius = compute_cell_inradius(dm)
709
+ for i_c, c in enumerate(range(cStart, cEnd)):
710
+ cell_volume, cell_center, cell_normal = dm.computeCellGeometryFVM(
711
+ c)
712
+ transitive_closure_points, transitive_closure_orientation = (
713
+ dm.getTransitiveClosure(c, useCone=True)
714
+ )
715
+ _cell_vertices = transitive_closure_points[
716
+ np.logical_and(
717
+ transitive_closure_points >= vStart,
718
+ transitive_closure_points < vEnd,
719
+ )
720
+ ]
721
+ # _cell_vertices_orientation = transitive_closure_orientation[np.logical_and(transitive_closure_points >= vStart, transitive_closure_points <vEnd)]
722
+ assert _cell_vertices.shape[0] == cell_vertices.shape[1]
723
+ # assert (_cell_vertices_orientation == 0).all()
724
+ cell_vertices[i_c, :] = _cell_vertices - vStart
725
+ cell_centers[i_c, :dim] = cell_center[:dim]
726
+ cell_volumes[i_c] = cell_volume
727
+
728
+ vertex_coordinates = np.array(gdm.getCoordinates()).reshape((-1, dim))
729
+ boundary_face_cells = {k: [] for k in boundary_dict.values()}
730
+ boundary_face_ghosts = {k: [] for k in boundary_dict.values()}
731
+ boundary_face_face_indices = {k: [] for k in boundary_dict.values()}
732
+ boundary_face_physical_tags = {k: [] for k in boundary_dict.values()}
733
+ face_cells = []
734
+ # face_cell_face_index = np.zeros((n_faces, 2), dtype=int)
735
+ face_normals = []
736
+ face_volumes = []
737
+ face_centers = []
738
+ face_subvolumes = []
739
+ allowed_keys = []
740
+ vertex_coordinates = np.array(dm.getCoordinates()).reshape((-1, dim))
741
+
742
+ def get_face_vertices(dim, gdm, vgStart, e):
743
+ if dim == 2:
744
+ return gdm.getCone(e) - vgStart
745
+ elif dim == 3:
746
+ face_vertices = set()
747
+ face_edges = gdm.getCone(e)
748
+ for edge in face_edges:
749
+ face_vertices.update(gdm.getCone(edge))
750
+ return np.array(list(face_vertices), dtype=int) - vgStart
751
+ else:
752
+ assert False
753
+
754
+ for e in range(egStart, egEnd):
755
+ label = gdm.getLabelValue("Face Sets", e)
756
+ face_volume, face_center, face_normal = gdm.computeCellGeometryFVM(
757
+ e)
758
+ face_vertices = get_face_vertices(dim, gdm, vgStart, e)
759
+ face_vertices_coords = vertex_coordinates[face_vertices]
760
+ _face_cells = gdm.getSupport(e)
761
+
762
+ _, _cell_center, _ = gdm.computeCellGeometryFVM(_face_cells[0])
763
+ _face_subvolume = np.zeros(2, dtype=float)
764
+ _face_subvolume[0] = compute_subvolume(
765
+ face_vertices_coords, _cell_center, dim
766
+ )
767
+ if _face_cells[1] < n_inner_cells:
768
+ _, _cell_center, _ = gdm.computeCellGeometryFVM(_face_cells[1])
769
+ _face_subvolume[1] = compute_subvolume(
770
+ face_vertices_coords, _cell_center, dim
771
+ )
772
+
773
+ # 2 cells support an face. Ghost cell is the one with the higher number
774
+ if label > -1:
775
+ allowed_keys.append(label)
776
+ boundary_cell = gdm.getSupport(e).min() - cStart
777
+ boundary_ghost = gdm.getSupport(e).max() - cStart
778
+ boundary_face_cells[label].append(boundary_cell)
779
+ boundary_face_ghosts[label].append(boundary_ghost)
780
+ boundary_face_face_indices[label].append(e - egStart)
781
+ boundary_face_physical_tags[label].append(label)
782
+ # boundary_face_vertices[label].append(gdm.getCone(e)-vStart)
783
+ # for periodic boudnary conditions, I need the ghost cell to have a cell_center. I copy the one from the related inner cell.
784
+ _face_cell = gdm.getSupport(e).min()
785
+ _face_ghost = gdm.getSupport(e).max()
786
+ cell_centers[_face_ghost, :dim] = (
787
+ cell_centers[_face_cell, :dim]
788
+ + 2
789
+ * ((face_center - cell_centers[_face_cell, :dim]) @ face_normal)
790
+ * face_normal
791
+ )
792
+ # subvolumes of the ghost cell are computes wrongly. In this case, copy the value from the inner cell.
793
+ if _face_cells[0] > _face_cells[1]:
794
+ _face_subvolume[0] = _face_subvolume[1]
795
+ else:
796
+ _face_subvolume[1] = _face_subvolume[0]
797
+
798
+ face_centers.append(face_center)
799
+ _face_cells = gdm.getSupport(e) - cStart
800
+ face_volumes.append(face_volume)
801
+ face_normals.append(face_normal)
802
+ face_cells.append(_face_cells)
803
+ face_subvolumes.append(_face_subvolume)
804
+
805
+ # I only want to iterate over the inner cells, but in the ghosted mesh
806
+ for i_c, c in enumerate(range(cgStart, cgStart + n_inner_cells)):
807
+ faces = gdm.getCone(c) - egStart
808
+ cell_faces[i_c, :] = faces
809
+
810
+ # least squares liner reconstruction
811
+ # Consider 2d, three points, scalar field, then the reconstruction is for a quad mesh
812
+ # DU = [u_{i+1, j} - u_{i,j}, u_{i-1, j} - u_{i,j}, u_{i, j+1} - u_{i,j}, u_{i, j-1} - u_{i,j}] \in \mathbb{R}^{4}
813
+ # DX = [[x_{i+1, j} - x_{i,j}, x_{i-1, j} - x_{i,j}, x_{i, j+1} - x_{i,j}, x_{i, j-1} - x_{i,j}],
814
+ # [y_{i+1, j} - y_{i,j}, y_{i-1, j} - y_{i,j}, y_{i, j+1} - y_{i,j}, y_{i, j-1} - y_{i,j}]] \in \mathbb{R}^{4x2}
815
+ # S = [S_x, S_y] \mathbb{R}^{2}
816
+ # solve DU = DX \dot S via normal equation
817
+ # A = (DX.T \dot DX)^{-1} DX.T \mathbb{R}^{2x4}
818
+ # so the solution is given by S = A \dot DU
819
+ # the vectorized version is more complicated. I have the vectorization (...) in the first dimension of size n_cells
820
+ # However, I do not want to apply the matrix vector product on DU (such that I can get the reconstruction with a single matrix vector product in the solver), but rather on a scalar field q \in \mathbb{R}^{n_cells}. This requires the need of a discretization matrix D \in \mathbb{R}^{n_cells x 4 x n_cells}, such that Dq = DU \in \mathbb{n_cells x 4}, where the first dimension is the dimension of vectorization
821
+
822
+ # cell_neighbors = np.zeros(1)
823
+ lsq_gradQ = np.zeros(1)
824
+ # NON_VECTORIZED CASE
825
+ polynomial_degree = 1
826
+ n_neighbors = n_faces_per_cell * polynomial_degree
827
+ cell_neighbors = (n_cells + 1) * \
828
+ np.ones((n_cells, n_neighbors), dtype=int)
829
+
830
+ for i_c, c in enumerate(range(cgStart, cgEnd)):
831
+ # GET NEIGHBORHOOD
832
+ neighbors = _get_neighberhood(gdm, c, cStart=cgStart)
833
+ assert not (i_c == neighbors).any()
834
+ _n_neighbors = neighbors.shape[0]
835
+ if _n_neighbors == 1:
836
+ neighbors_of_neighbor = _get_neighberhood(
837
+ gdm, neighbors[0] + cgStart, cStart=cgStart
838
+ )
839
+ assert len(neighbors_of_neighbor) == n_faces_per_cell
840
+ neighbors = np.setdiff1d(
841
+ np.union1d(neighbors_of_neighbor, neighbors), [c]
842
+ )
843
+ cell_neighbors[i_c, :] = neighbors
844
+
845
+ lsq_gradQ, lsq_neighbors, lsq_monomial_multi_index = (
846
+ least_squares_reconstruction_local(
847
+ n_cells, dim, cell_neighbors, cell_centers[:, :dim], lsq_degree
848
+ )
849
+ )
850
+ lsq_scale_factors = scale_lsq_derivative(lsq_monomial_multi_index)
851
+
852
+ n_face_neighbors = (2 * (n_faces_per_cell + 1) - 2) * polynomial_degree
853
+ face_neighbors = (n_cells + 1) * \
854
+ np.ones((n_faces, n_face_neighbors), dtype=int)
855
+
856
+ for i_f, f in enumerate(range(egStart, egEnd)):
857
+ # GET NEIGHBORHOOD
858
+ neighbors = _fill_neighborhood(
859
+ gdm, gdm.getSupport(f), n_face_neighbors, cgStart
860
+ )
861
+ face_neighbors[i_f, :] = neighbors
862
+
863
+ face_volumes = np.array(face_volumes, dtype=float)
864
+ _face_centers = np.array(face_centers, dtype=float)
865
+ face_centers = np.zeros((n_faces, 3), dtype=float)
866
+ face_centers[:, :dim] = _face_centers[:, :dim]
867
+ face_normals = np.array(face_normals, dtype=float)
868
+ face_subvolumes = np.array(face_subvolumes, dtype=float)
869
+
870
+ face_cells = np.array(face_cells, dtype=int)
871
+ # face_cell_face_index = np.array(face_cell_face_index, dtype=int)
872
+ boundary_face_function_numbers = _boundary_dict_indices(
873
+ boundary_face_cells)
874
+
875
+ # get rid of empty keys in the boundary_dict (e.g. no surface values in 2d)
876
+ boundary_dict_inverted = {v: k for k, v in boundary_dict.items()}
877
+ boundary_dict_reduced = {
878
+ k: boundary_dict_inverted[k] for k in allowed_keys}
879
+
880
+ # sort the dict by the values
881
+ sorted_keys = np.array(list(boundary_dict_reduced.keys()), dtype=int)
882
+ sorted_keys.sort()
883
+ boundary_dict = {k: boundary_dict_reduced[k] for k in sorted_keys}
884
+ boundary_face_cells = {k: boundary_face_cells[k] for k in sorted_keys}
885
+ boundary_face_ghosts = {
886
+ k: boundary_face_ghosts[k] for k in sorted_keys}
887
+ boundary_face_face_indices = {
888
+ k: boundary_face_face_indices[k] for k in sorted_keys
889
+ }
890
+
891
+ boundary_conditions_sorted_physical_tags = np.array(
892
+ list(boundary_dict.keys()), dtype=int
893
+ )
894
+ boundary_conditions_sorted_names = np.array(
895
+ list(boundary_dict.values()), dtype="str"
896
+ )
897
+ boundary_face_cells = np.array(
898
+ _boundary_dict_to_list(boundary_face_cells), dtype=int
899
+ )
900
+ boundary_face_ghosts = np.array(
901
+ _boundary_dict_to_list(boundary_face_ghosts), dtype=int
902
+ )
903
+ boundary_face_physical_tags = np.array(
904
+ _boundary_dict_to_list(boundary_face_physical_tags), dtype=int
905
+ )
906
+ boundary_face_face_indices = np.array(
907
+ _boundary_dict_to_list(boundary_face_face_indices), dtype=int
908
+ )
909
+ n_boundary_faces = boundary_face_cells.shape[0]
910
+
911
+ mesh_type = get_mesh_type_from_dm(n_faces_per_cell, dim)
912
+
913
+ z_ordering = np.array([-1], dtype=float)
914
+
915
+ obj = cls(
916
+ dim,
917
+ mesh_type,
918
+ n_cells,
919
+ n_inner_cells,
920
+ n_faces,
921
+ n_vertices,
922
+ n_boundary_faces,
923
+ n_faces_per_cell,
924
+ vertex_coordinates.T,
925
+ cell_vertices.T,
926
+ cell_faces.T,
927
+ cell_volumes,
928
+ cell_centers.T,
929
+ cell_inradius,
930
+ cell_neighbors,
931
+ boundary_face_cells.T,
932
+ boundary_face_ghosts.T,
933
+ boundary_face_function_numbers,
934
+ boundary_face_physical_tags,
935
+ boundary_face_face_indices.T,
936
+ face_cells.T,
937
+ face_normals.T,
938
+ face_volumes,
939
+ face_centers,
940
+ face_subvolumes,
941
+ face_neighbors,
942
+ boundary_conditions_sorted_physical_tags,
943
+ boundary_conditions_sorted_names,
944
+ lsq_gradQ,
945
+ lsq_neighbors,
946
+ lsq_monomial_multi_index,
947
+ lsq_scale_factors,
948
+ z_ordering,
949
+ )
950
+
951
+ if allow_z_integration:
952
+ obj._compute_ascending_order_structured_axis()
953
+
954
+ return obj
955
+
956
+ @classmethod
957
+ def extrude_mesh(cls, msh, n_layers=10):
958
+ Z = np.linspace(0, 1, n_layers + 1)
959
+ dimension = msh.dimension + 1
960
+ mesh_type = get_extruded_mesh_type(msh.type)
961
+ n_cells = msh.n_cells * n_layers
962
+ n_inner_cells = msh.n_inner_cells * n_layers
963
+ n_vertices = msh.n_cells * (n_layers + 1)
964
+ n_boundary_faces = msh.n_boundary_faces * n_layers + 2 * msh.n_cells
965
+ n_faces_per_cell = mesh_util._get_faces_per_element(mesh_type)
966
+ n_faces = n_inner_cells * n_faces_per_cell
967
+ vertex_coordinates = extrude.extrude_points(
968
+ msh.vertex_coordinates.T, Z).T
969
+ cell_vertices = extrude.extrude_element_vertices(
970
+ msh.cell_vertices.T, msh.n_vertices, n_layers
971
+ ).T
972
+
973
+ cell_centers = np.empty((n_cells, 3), dtype=float)
974
+ cell_volumes = np.empty((n_cells), dtype=float)
975
+ cell_inradius = np.empty((n_cells), dtype=float)
976
+ cell_face_areas = np.empty((n_cells, n_faces_per_cell), dtype=float)
977
+ cell_face_normals = np.zeros(
978
+ (n_cells, n_faces_per_cell, 3), dtype=float)
979
+ cell_n_neighbors = np.empty((n_cells), dtype=int)
980
+ cell_neighbors = np.empty((n_cells, n_faces_per_cell), dtype=int)
981
+ cell_neighbors_face_index = np.empty(
982
+ (n_cells, n_faces_per_cell), dtype=int)
983
+ for i_elem, elem in enumerate(cell_vertices.T):
984
+ cell_centers[i_elem, :dimension] = mesh_util.center(
985
+ vertex_coordinates.T, elem
986
+ )
987
+
988
+ # truncate normals and positions from 3d to dimendion-d
989
+ vertex_coordinates = vertex_coordinates.T[:, :dimension].T
990
+ cell_centers = cell_centers[:, :]
991
+ # cell_face_normals = cell_face_normals[:, :, :dimension]
992
+
993
+ # TODO
994
+ # empty fields
995
+ cell_faces = np.empty((n_inner_cells, n_faces_per_cell), dtype=int)
996
+ boundary_face_cells = np.array([0, n_inner_cells - 1], dtype=int)
997
+ boundary_face_ghosts = np.array(
998
+ [n_inner_cells, n_inner_cells + 1], dtype=int)
999
+ boundary_face_function_numbers = np.empty(
1000
+ (n_boundary_faces), dtype=int)
1001
+ boundary_face_physical_tags = np.array([0, 1], dtype=int)
1002
+ boundary_face_face_indices = np.array([0, n_faces - 1], dtype=int)
1003
+ face_cells = np.empty((n_faces, 2), dtype=int)
1004
+ face_subvolumes = np.empty((n_faces, 2), dtype=float)
1005
+ boundary_conditions_sorted_physical_tags = np.array([0, 1], dtype=int)
1006
+ boundary_conditions_sorted_names = np.array(["left", "right"])
1007
+ face_normals = np.zeros((n_faces, 3), dtype=float)
1008
+ face_volumes = np.zeros((n_faces), dtype=float)
1009
+ face_centers = np.zeros((n_faces, 3), dtype=float)
1010
+
1011
+ # hard coded guess
1012
+ n_face_neighbors = 0
1013
+ face_neighbors = (n_cells + 1) * \
1014
+ np.ones((n_faces, n_face_neighbors), dtype=int)
1015
+ lsq_gradQ = np.zeros((n_cells, dimension, 0), dtype=float)
1016
+ lsq_neighbors = np.zeros(1)
1017
+ lsq_monomial_multi_index = np.zeros(1)
1018
+ lsq_scale_factors = np.zeros(1)
1019
+ z_ordering = np.array([-1], dtype=float)
1020
+
1021
+ return cls(
1022
+ msh.dimension + 1,
1023
+ mesh_type,
1024
+ n_cells,
1025
+ n_inner_cells,
1026
+ n_faces,
1027
+ n_vertices,
1028
+ n_boundary_faces,
1029
+ n_faces_per_cell,
1030
+ vertex_coordinates,
1031
+ cell_vertices,
1032
+ cell_faces.T,
1033
+ cell_volumes,
1034
+ cell_centers.T,
1035
+ cell_inradius,
1036
+ cell_neighbors,
1037
+ boundary_face_cells.T,
1038
+ boundary_face_ghosts.T,
1039
+ boundary_face_function_numbers,
1040
+ boundary_face_physical_tags,
1041
+ boundary_face_face_indices.T,
1042
+ face_cells.T,
1043
+ face_normals.T,
1044
+ face_volumes,
1045
+ face_centers,
1046
+ face_subvolumes,
1047
+ face_neighbors,
1048
+ boundary_conditions_sorted_physical_tags,
1049
+ boundary_conditions_sorted_names,
1050
+ lsq_gradQ,
1051
+ lsq_neighbors,
1052
+ lsq_monomial_multi_index,
1053
+ lsq_scale_factors,
1054
+ z_ordering,
1055
+ )
1056
+
1057
+ def write_to_hdf5(self, filepath: str):
1058
+ if not _HAVE_H5PY:
1059
+ raise RuntimeError(
1060
+ "Mesh.write_to_hdf5() requires h5py, which is not available."
1061
+ )
1062
+ main_dir = os.getenv("ZOOMY_DIR")
1063
+ with h5py.File(os.path.join(main_dir, filepath), "w") as f:
1064
+ mesh = f.create_group("mesh")
1065
+ mesh.create_dataset("dimension", data=self.dimension)
1066
+ mesh.create_dataset("type", data=self.type)
1067
+ mesh.create_dataset("n_cells", data=self.n_cells)
1068
+ mesh.create_dataset("n_inner_cells", data=self.n_inner_cells)
1069
+ mesh.create_dataset("n_faces", data=self.n_faces)
1070
+ mesh.create_dataset("n_vertices", data=self.n_vertices)
1071
+ mesh.create_dataset("n_boundary_faces", data=self.n_boundary_faces)
1072
+ mesh.create_dataset("n_faces_per_cell", data=self.n_faces_per_cell)
1073
+ mesh.create_dataset("vertex_coordinates",
1074
+ data=self.vertex_coordinates)
1075
+ mesh.create_dataset("cell_vertices", data=self.cell_vertices)
1076
+ mesh.create_dataset("cell_faces", data=self.cell_faces)
1077
+ mesh.create_dataset("cell_volumes", data=self.cell_volumes)
1078
+ mesh.create_dataset("cell_centers", data=self.cell_centers)
1079
+ mesh.create_dataset("cell_inradius", data=self.cell_inradius)
1080
+ mesh.create_dataset("cell_neighbors", data=self.cell_neighbors)
1081
+ mesh.create_dataset("boundary_face_cells",
1082
+ data=self.boundary_face_cells)
1083
+ mesh.create_dataset("boundary_face_ghosts",
1084
+ data=self.boundary_face_ghosts)
1085
+ mesh.create_dataset(
1086
+ "boundary_face_function_numbers",
1087
+ data=self.boundary_face_function_numbers,
1088
+ )
1089
+ mesh.create_dataset(
1090
+ "boundary_face_physical_tags", data=self.boundary_face_physical_tags
1091
+ )
1092
+ mesh.create_dataset(
1093
+ "boundary_face_face_indices", data=self.boundary_face_face_indices
1094
+ )
1095
+ mesh.create_dataset("face_cells", data=self.face_cells)
1096
+ # mesh.create_dataset("face_cell_face_index", data=self.face_cell_face_index)
1097
+ mesh.create_dataset("face_normals", data=self.face_normals)
1098
+ mesh.create_dataset("face_volumes", data=self.face_volumes)
1099
+ mesh.create_dataset("face_centers", data=self.face_centers)
1100
+ mesh.create_dataset("face_subvolumes", data=self.face_subvolumes)
1101
+ mesh.create_dataset("face_neighbors", data=self.face_neighbors)
1102
+ mesh.create_dataset(
1103
+ "boundary_conditions_sorted_physical_tags",
1104
+ data=np.array(self.boundary_conditions_sorted_physical_tags),
1105
+ )
1106
+ mesh.create_dataset(
1107
+ "boundary_conditions_sorted_names",
1108
+ data=np.array(
1109
+ self.boundary_conditions_sorted_names, dtype="S"),
1110
+ )
1111
+ mesh.create_dataset("lsq_gradQ", data=np.array(self.lsq_gradQ))
1112
+ mesh.create_dataset(
1113
+ "lsq_neighbors", data=np.array(self.lsq_neighbors))
1114
+ mesh.create_dataset(
1115
+ "lsq_monomial_multi_index", data=(self.lsq_monomial_multi_index)
1116
+ )
1117
+ mesh.create_dataset("lsq_scale_factors",
1118
+ data=(self.lsq_scale_factors))
1119
+ mesh.create_dataset("z_ordering", data=np.array(self.z_ordering))
1120
+
1121
+ @classmethod
1122
+ def from_hdf5(cls, filepath: str):
1123
+ if not _HAVE_H5PY:
1124
+ raise RuntimeError(
1125
+ "Mesh.from_hdf5() requires h5py, which is not available."
1126
+ )
1127
+ with h5py.File(filepath, "r") as file:
1128
+ file = file
1129
+ mesh = cls(
1130
+ file["mesh"]["dimension"][()],
1131
+ (file["mesh"]["type"][()]).decode("utf-8"),
1132
+ file["mesh"]["n_cells"][()],
1133
+ file["mesh"]["n_inner_cells"][()],
1134
+ file["mesh"]["n_faces"][()],
1135
+ file["mesh"]["n_vertices"][()],
1136
+ file["mesh"]["n_boundary_faces"][()],
1137
+ file["mesh"]["n_faces_per_cell"][()],
1138
+ file["mesh"]["vertex_coordinates"][()],
1139
+ file["mesh"]["cell_vertices"][()],
1140
+ file["mesh"]["cell_faces"][()],
1141
+ file["mesh"]["cell_volumes"][()],
1142
+ file["mesh"]["cell_centers"][()],
1143
+ file["mesh"]["cell_inradius"][()],
1144
+ file["mesh"]["cell_neighbors"][()],
1145
+ file["mesh"]["boundary_face_cells"][()],
1146
+ file["mesh"]["boundary_face_ghosts"][()],
1147
+ file["mesh"]["boundary_face_function_numbers"][()],
1148
+ file["mesh"]["boundary_face_physical_tags"][()],
1149
+ file["mesh"]["boundary_face_face_indices"][()],
1150
+ file["mesh"]["face_cells"][()],
1151
+ # file["mesh"]["face_cell_face_index"][()],
1152
+ file["mesh"]["face_normals"][()],
1153
+ file["mesh"]["face_volumes"][()],
1154
+ file["mesh"]["face_centers"][()],
1155
+ file["mesh"]["face_subvolumes"][()],
1156
+ file["mesh"]["face_neighbors"][()],
1157
+ file["mesh"]["boundary_conditions_sorted_physical_tags"][()],
1158
+ np.array(
1159
+ file["mesh"]["boundary_conditions_sorted_names"][()
1160
+ ], dtype="str"
1161
+ ),
1162
+ file["mesh"]["lsq_gradQ"][()],
1163
+ file["mesh"]["lsq_neighbors"][()],
1164
+ file["mesh"]["lsq_monomial_multi_index"][()],
1165
+ file["mesh"]["lsq_scale_factors"][()],
1166
+ file["mesh"]["z_ordering"][()],
1167
+ )
1168
+ return mesh
1169
+
1170
+ def write_to_vtk(
1171
+ self,
1172
+ filepath: str,
1173
+ fields: Union[FArray, None] = None,
1174
+ field_names: Union[list[str], None] = None,
1175
+ point_data: dict = {},
1176
+ ):
1177
+ if not _HAVE_MESHIO:
1178
+ raise RuntimeError(
1179
+ "_write_to_vtk_from_vertices_edges requires meshio, which is not available."
1180
+ )
1181
+ d_fields = {}
1182
+ vertex_coords_3d = np.zeros((3, self.vertex_coordinates.shape[1]))
1183
+ vertex_coords_3d[: self.vertex_coordinates.shape[0], :] = (
1184
+ self.vertex_coordinates
1185
+ )
1186
+ if fields is not None:
1187
+ if field_names is None:
1188
+ field_names = [str(i) for i in range(fields.shape[0])]
1189
+ for i_fields, field in enumerate(fields):
1190
+ d_fields[field_names[i_fields]] = [field]
1191
+ # the brackets around the second argument (cells) indicate that I have only mesh type of mesh element of type self.type, with corresponding vertices.
1192
+ meshout = meshio.Mesh(
1193
+ vertex_coords_3d.T,
1194
+ [
1195
+ (
1196
+ mesh_util.convert_mesh_type_to_meshio_mesh_type(self.type),
1197
+ self.cell_vertices.T,
1198
+ )
1199
+ ],
1200
+ cell_data=d_fields,
1201
+ point_data=point_data,
1202
+ )
1203
+ path, _ = os.path.split(filepath)
1204
+ filepath, file_ext = os.path.splitext(filepath)
1205
+ if not os.path.exists(path) and path != "":
1206
+ os.mkdir(path)
1207
+ meshout.write(filepath + ".vtk", binary=False)
1208
+
1209
+
1210
+ if __name__ == "__main__":
1211
+ path = "/home/ingo/Git/sms/meshes/quad_2d/mesh_coarse.msh"
1212
+ path2 = "/home/ingo/Git/sms/meshes/quad_2d/mesh_fine.msh"
1213
+ path3 = "/home/ingo/Git/sms/meshes/quad_2d/mesh_finest.msh"
1214
+ path4 = "/home/ingo/Git/sms/meshes/triangle_2d/mesh_coarse.msh"
1215
+ labels = get_physical_boundary_labels(path)
1216
+
1217
+ # dm, boundary_dict, ghost_cells_dict = load_gmsh(path)
1218
+
1219
+ mesh = Mesh.from_gmsh(path)
1220
+ assert mesh.cell_faces.max() == mesh.n_faces - 1
1221
+ assert mesh.cell_faces.min() == 0
1222
+ assert mesh.face_cells.max() == mesh.n_cells - 1
1223
+ assert mesh.face_cells.min() == 0
1224
+ assert mesh.cell_vertices.max() == mesh.n_vertices - 1
1225
+ assert mesh.cell_vertices.min() == 0
1226
+
1227
+ mesh.write_to_hdf5("./test.h5")
1228
+ mesh = Mesh.from_hdf5("./test.h5")
1229
+ # mesh.write_to_vtk('./test.vtk')
1230
+ mesh.write_to_vtk(
1231
+ "./test.vtk",
1232
+ fields=np.ones((2, mesh.n_inner_cells), dtype=float),
1233
+ field_names=["A", "B"],
1234
+ )