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