phiFEM 0.7.2.dev1__py3-none-any.whl → 0.8.0.dev1__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.
- phifem/mesh_scripts.py +336 -189
- {phifem-0.7.2.dev1.dist-info → phifem-0.8.0.dev1.dist-info}/METADATA +5 -5
- phifem-0.8.0.dev1.dist-info/RECORD +7 -0
- phifem-0.7.2.dev1.dist-info/RECORD +0 -7
- {phifem-0.7.2.dev1.dist-info → phifem-0.8.0.dev1.dist-info}/WHEEL +0 -0
- {phifem-0.7.2.dev1.dist-info → phifem-0.8.0.dev1.dist-info}/licenses/COPYING +0 -0
- {phifem-0.7.2.dev1.dist-info → phifem-0.8.0.dev1.dist-info}/licenses/COPYING.lesser +0 -0
phifem/mesh_scripts.py
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
4
|
+
import typing
|
|
2
5
|
import warnings
|
|
3
6
|
from collections.abc import Callable
|
|
4
7
|
from os import PathLike
|
|
5
|
-
from typing import Any, Tuple
|
|
6
8
|
|
|
7
9
|
import dolfinx as dfx
|
|
8
10
|
import numpy as np
|
|
9
11
|
import numpy.typing as npt
|
|
10
12
|
import ufl # type: ignore
|
|
11
|
-
from basix.ufl import element
|
|
13
|
+
from basix.ufl import _ElementBase, element
|
|
12
14
|
from dolfinx.cpp.graph import AdjacencyList_int32 # type: ignore
|
|
13
|
-
from dolfinx.fem import Function
|
|
15
|
+
from dolfinx.fem import Expression, Function, FunctionSpace
|
|
14
16
|
from dolfinx.fem.petsc import assemble_vector
|
|
15
17
|
from dolfinx.mesh import Mesh, MeshTags
|
|
16
18
|
from ufl import inner
|
|
@@ -20,9 +22,123 @@ PathStr = PathLike[str] | str
|
|
|
20
22
|
NDArrayFunction = Callable[[npt.NDArray[np.float64]], npt.NDArray[np.float64]]
|
|
21
23
|
|
|
22
24
|
debug_mode = False
|
|
23
|
-
if "MODE" in os.environ:
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
if "MODE" in os.environ and os.environ["MODE"] == "debug":
|
|
26
|
+
debug_mode = True
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Borrowed from: https://github.com/scientificcomputing/scifem/blob/main/src/scifem/mesh.py#L173
|
|
30
|
+
def _reverse_mark_entities(
|
|
31
|
+
entity_map: dfx.common.IndexMap, entities: npt.NDArray[np.int32]
|
|
32
|
+
) -> npt.NDArray[np.int32]:
|
|
33
|
+
"""Communicate entities marked on a single process to all processes that ghosts or owns this entity.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
entity_map: Index-map describing entity ownership
|
|
37
|
+
entities: Local indices of entities to communicate
|
|
38
|
+
Returns:
|
|
39
|
+
Local indices marked on any process sharing this entity
|
|
40
|
+
"""
|
|
41
|
+
comm_vec = dfx.la.vector(entity_map, dtype=np.int32)
|
|
42
|
+
comm_vec.array[:] = 0
|
|
43
|
+
comm_vec.array[entities] = 1
|
|
44
|
+
comm_vec.scatter_reverse(dfx.la.InsertMode.add)
|
|
45
|
+
comm_vec.scatter_forward()
|
|
46
|
+
return np.flatnonzero(comm_vec.array).astype(np.int32)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Borrowed from: https://github.com/scientificcomputing/scifem/blob/main/src/scifem/mesh.py#L57
|
|
50
|
+
def _get_entity_map(
|
|
51
|
+
entity_map: dfx.mesh.EntityMap | npt.NDArray[np.int32],
|
|
52
|
+
) -> npt.NDArray[np.int32]:
|
|
53
|
+
"""Get an entity map from the sub-topology to the topology.
|
|
54
|
+
|
|
55
|
+
This function handles both the deprecated construction of an entity map as a numpy array and the newer `EntityMap` class from `dolfinx.mesh`.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
entity_map: An `EntityMap` object or a numpy array representing the mapping.
|
|
59
|
+
Returns:
|
|
60
|
+
Mapped indices of entities.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
sub_top = entity_map.sub_topology
|
|
64
|
+
assert isinstance(sub_top, dfx.mesh.Topology)
|
|
65
|
+
sub_map = sub_top.index_map(entity_map.dim)
|
|
66
|
+
indices = np.arange(sub_map.size_local + sub_map.num_ghosts, dtype=np.int32)
|
|
67
|
+
return entity_map.sub_topology_to_topology(indices, inverse=False)
|
|
68
|
+
except AttributeError:
|
|
69
|
+
return entity_map
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Borrowed from: https://github.com/scientificcomputing/scifem/blob/main/src/scifem/mesh.py#L287
|
|
73
|
+
def _compute_subdomain_exterior_facets(
|
|
74
|
+
mesh: Mesh, ct: MeshTags, markers: typing.Sequence[int]
|
|
75
|
+
) -> npt.NDArray[np.int32]:
|
|
76
|
+
"""Find the the facets that are considered to be on the "exterior" boundary of a subdomain.
|
|
77
|
+
|
|
78
|
+
The subdomain is defined as the collection of cells in ``ct`` that is marked with any of the
|
|
79
|
+
``markers``. The exterior boundary of the subdomain is defined as the collection of facets
|
|
80
|
+
that are only connected to a single cell within the subdomain.
|
|
81
|
+
|
|
82
|
+
Note:
|
|
83
|
+
Ghosted facets are included in the resulting array.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
mesh: Mesh to extract subdomains from
|
|
87
|
+
ct: MeshTags object marking subdomains
|
|
88
|
+
markers: The tags making up the "new" mesh
|
|
89
|
+
Returns:
|
|
90
|
+
The exterior facets
|
|
91
|
+
"""
|
|
92
|
+
# Create submesh to find the exterior facet of subdomain
|
|
93
|
+
# Accumulate all entities, including ghosts, for the specfic set of tagged entities
|
|
94
|
+
edim = ct.dim
|
|
95
|
+
mesh.topology.create_connectivity(edim, mesh.topology.dim)
|
|
96
|
+
tags_as_arr = np.asarray(markers, dtype=ct.values.dtype)
|
|
97
|
+
all_tagged_indices = np.isin(ct.values, tags_as_arr)
|
|
98
|
+
entities = ct.indices[all_tagged_indices]
|
|
99
|
+
sub_mesh, cell_map = dfx.mesh.create_submesh(
|
|
100
|
+
mesh,
|
|
101
|
+
edim,
|
|
102
|
+
entities,
|
|
103
|
+
)[:2]
|
|
104
|
+
|
|
105
|
+
new_et = _transfer_tags(ct, sub_mesh, cell_map, mesh)
|
|
106
|
+
new_et.name = ct.name
|
|
107
|
+
|
|
108
|
+
sub_mesh.topology.create_connectivity(
|
|
109
|
+
sub_mesh.topology.dim - 1, sub_mesh.topology.dim
|
|
110
|
+
)
|
|
111
|
+
sub_facets = dfx.mesh.exterior_facet_indices(sub_mesh.topology)
|
|
112
|
+
|
|
113
|
+
# Map exterior facet to (submesh_cell, local_facet_index) tuples
|
|
114
|
+
try:
|
|
115
|
+
integration_entities = dfx.fem.compute_integration_domains(
|
|
116
|
+
dfx.fem.IntegralType.exterior_facet, sub_mesh.topology, sub_facets
|
|
117
|
+
)
|
|
118
|
+
except TypeError:
|
|
119
|
+
integration_entities = dfx.fem.compute_integration_domains(
|
|
120
|
+
dfx.fem.IntegralType.exterior_facet,
|
|
121
|
+
sub_mesh.topology,
|
|
122
|
+
sub_facets,
|
|
123
|
+
sub_mesh.topology.dim - 1,
|
|
124
|
+
)
|
|
125
|
+
integration_entities = integration_entities.reshape(-1, 2)
|
|
126
|
+
submap_array = _get_entity_map(cell_map)
|
|
127
|
+
integration_entities[:, 0] = submap_array[integration_entities[:, 0]]
|
|
128
|
+
|
|
129
|
+
# Get cell to facet connectivity (parent mesh)
|
|
130
|
+
mesh.topology.create_connectivity(mesh.topology.dim, mesh.topology.dim - 1)
|
|
131
|
+
num_facets_per_cell = dfx.cpp.mesh.cell_num_entities(
|
|
132
|
+
mesh.topology.cell_type, mesh.topology.dim - 1
|
|
133
|
+
)
|
|
134
|
+
c_to_f = mesh.topology.connectivity(
|
|
135
|
+
mesh.topology.dim, mesh.topology.dim - 1
|
|
136
|
+
).array.reshape(-1, num_facets_per_cell)
|
|
137
|
+
# Map (parent_cell, local_facet_index) to facet index (local to process)
|
|
138
|
+
parent_facets = c_to_f[integration_entities[:, 0], integration_entities[:, 1]]
|
|
139
|
+
facet_map = mesh.topology.index_map(mesh.topology.dim - 1)
|
|
140
|
+
# Accumulate ghost facets
|
|
141
|
+
return _reverse_mark_entities(facet_map, parent_facets)
|
|
26
142
|
|
|
27
143
|
|
|
28
144
|
def _reference_segment_points(N: int) -> npt.NDArray[np.float64]:
|
|
@@ -135,14 +251,15 @@ def _compute_detection_vector(
|
|
|
135
251
|
|
|
136
252
|
|
|
137
253
|
def _compute_integration_entities(
|
|
138
|
-
mesh: Mesh, integration_cells: list[int], integration_facets: list[int]
|
|
139
|
-
) ->
|
|
254
|
+
mesh: Mesh, integration_cells: list[int], integration_facets: list[int], ind: int
|
|
255
|
+
) -> ufl.Measure:
|
|
140
256
|
"""Compute the integration entities in order to build a one-sided integral over a set of given edges. This script is inspired from https://github.com/jorgensd/dolfinx-tutorial/issues/158.
|
|
141
257
|
|
|
142
258
|
Args:
|
|
143
259
|
mesh: the mesh on which we compute the measure.
|
|
144
260
|
integration_cells: list of cells indices from which the integral is computed.
|
|
145
261
|
integration_facets: list of facets indices on which the integral is computed.
|
|
262
|
+
ind: index used in the measure.
|
|
146
263
|
Returns: the integration entities.
|
|
147
264
|
"""
|
|
148
265
|
cdim = mesh.topology.dim
|
|
@@ -188,7 +305,7 @@ def _compute_integration_entities(
|
|
|
188
305
|
np.column_stack((right_side_cells_rep, local_indices))
|
|
189
306
|
).astype(np.int32)
|
|
190
307
|
|
|
191
|
-
return integration_entities
|
|
308
|
+
return [(ind, integration_entities)]
|
|
192
309
|
|
|
193
310
|
|
|
194
311
|
def _reshape_map(connect: AdjacencyList_int32) -> npt.NDArray[np.int32]:
|
|
@@ -216,7 +333,7 @@ def _reshape_map(connect: AdjacencyList_int32) -> npt.NDArray[np.int32]:
|
|
|
216
333
|
def _transfer_tags(
|
|
217
334
|
source_mesh_tags: MeshTags,
|
|
218
335
|
dest_mesh: Mesh,
|
|
219
|
-
cmap: npt.NDArray
|
|
336
|
+
cmap: npt.NDArray,
|
|
220
337
|
source_mesh: Mesh = None,
|
|
221
338
|
) -> MeshTags:
|
|
222
339
|
"""Given entities tags (cells or facets) from a source mesh, a destination mesh and the source mesh-destination mesh cells mapping, transfers the entities tags to the destination mesh.
|
|
@@ -255,7 +372,7 @@ def _transfer_tags(
|
|
|
255
372
|
dest_c2f_map = dest_c2f_map.reshape(
|
|
256
373
|
-1,
|
|
257
374
|
)
|
|
258
|
-
|
|
375
|
+
sorted_indices = np.unique(dest_c2f_map, return_index=True)[1]
|
|
259
376
|
emap = source_c2f_dest_map[sorted_indices]
|
|
260
377
|
else:
|
|
261
378
|
raise ValueError("The source_mesh_tags can only be cells tags or facets tags.")
|
|
@@ -282,8 +399,7 @@ def _transfer_tags(
|
|
|
282
399
|
|
|
283
400
|
def _tag_cells(
|
|
284
401
|
mesh: Mesh,
|
|
285
|
-
|
|
286
|
-
detection_degree: int,
|
|
402
|
+
levelset_expression: Expression,
|
|
287
403
|
single_layer_cut: bool = False,
|
|
288
404
|
) -> MeshTags:
|
|
289
405
|
"""Tag the mesh cells by computing detection = Σ f(dof)/Σ|f(dof)| where 'dof' are coming from a custom quadrature rule with points on the boundary of the cell only.
|
|
@@ -300,8 +416,21 @@ def _tag_cells(
|
|
|
300
416
|
Returns:
|
|
301
417
|
The cells tags as a MeshTags object.
|
|
302
418
|
"""
|
|
419
|
+
cdim = mesh.topology.dim
|
|
420
|
+
all_cells = dfx.mesh.locate_entities(
|
|
421
|
+
mesh, cdim, lambda x: np.ones_like(x[0]).astype(bool)
|
|
422
|
+
)
|
|
423
|
+
levelset_eval = levelset_expression.eval(mesh, all_cells)
|
|
424
|
+
|
|
425
|
+
exterior = np.min(levelset_eval, axis=1) > 0.0
|
|
426
|
+
interior = np.max(levelset_eval, axis=1) < 0.0
|
|
427
|
+
cut = np.logical_not(np.logical_or(exterior, interior))
|
|
428
|
+
|
|
429
|
+
exterior_indices = np.where(exterior)[0]
|
|
430
|
+
interior_indices = np.where(interior)[0]
|
|
431
|
+
cut_indices = np.where(cut)[0]
|
|
432
|
+
|
|
303
433
|
if single_layer_cut:
|
|
304
|
-
cdim = mesh.topology.dim
|
|
305
434
|
vdim = 0
|
|
306
435
|
# Create the cell to facet connectivity and reshape it into an array s.t. c2f_map[cell_index] = [facets of this cell index]
|
|
307
436
|
mesh.topology.create_connectivity(cdim, vdim)
|
|
@@ -313,39 +442,6 @@ def _tag_cells(
|
|
|
313
442
|
v2c_connect = mesh.topology.connectivity(vdim, cdim)
|
|
314
443
|
v2c_map, max_offset = _reshape_map(v2c_connect)
|
|
315
444
|
|
|
316
|
-
# Create the custom quadrature rule.
|
|
317
|
-
# The quadrature points are evenly spaced on the boundary of the reference cell.
|
|
318
|
-
# The weights are 1.
|
|
319
|
-
cell_type = mesh.topology.cell_type.name
|
|
320
|
-
|
|
321
|
-
if cell_type == "triangle":
|
|
322
|
-
points = _reference_triangle_boundary_points(detection_degree)
|
|
323
|
-
elif cell_type == "quadrilateral":
|
|
324
|
-
points = _reference_square_boundary_points(detection_degree)
|
|
325
|
-
else:
|
|
326
|
-
raise NotImplementedError(
|
|
327
|
-
"Mesh tags computation does not support other cell types than 'triangle' or 'quadrilateral'"
|
|
328
|
-
)
|
|
329
|
-
weights = np.ones_like(points[:, 0])
|
|
330
|
-
|
|
331
|
-
detection_quadrature = {
|
|
332
|
-
"quadrature_rule": "custom",
|
|
333
|
-
"quadrature_points": points,
|
|
334
|
-
"quadrature_weights": weights,
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
detection_measure = ufl.Measure("dx", domain=mesh, metadata=detection_quadrature)
|
|
338
|
-
|
|
339
|
-
detection_vector = _compute_detection_vector(
|
|
340
|
-
mesh, discrete_levelset, detection_measure
|
|
341
|
-
)
|
|
342
|
-
cut_indices = np.where(
|
|
343
|
-
np.logical_and(detection_vector > -1.0, detection_vector < 1.0)
|
|
344
|
-
)[0]
|
|
345
|
-
exterior_indices = np.where(detection_vector == 1.0)[0]
|
|
346
|
-
interior_indices = np.where(detection_vector == -1.0)[0]
|
|
347
|
-
|
|
348
|
-
if single_layer_cut:
|
|
349
445
|
neighbor_cells = np.reshape(
|
|
350
446
|
v2c_map[c2v_map[cut_indices]], (-1, num_vertices_per_cell * max_offset)
|
|
351
447
|
)
|
|
@@ -381,7 +477,6 @@ def _tag_cells(
|
|
|
381
477
|
cut_marker = np.full_like(cut_indices, 2).astype(np.int32)
|
|
382
478
|
markers = np.hstack([exterior_marker, interior_marker, cut_marker]).astype(np.int32)
|
|
383
479
|
sorted_indices = np.argsort(indices)
|
|
384
|
-
|
|
385
480
|
cells_tags = dfx.mesh.meshtags(
|
|
386
481
|
mesh, mesh.topology.dim, indices[sorted_indices], markers[sorted_indices]
|
|
387
482
|
)
|
|
@@ -389,11 +484,31 @@ def _tag_cells(
|
|
|
389
484
|
return cells_tags
|
|
390
485
|
|
|
391
486
|
|
|
487
|
+
def _cells_facets_pairs(f2c_map, c2f_map, facets):
|
|
488
|
+
"""Get integration entities cells-facets pairs for the corresponding facets indices.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
f2c_map: the facet to cell connectivity mapping.
|
|
492
|
+
c2f_map: the cell to facet connectivity mapping.
|
|
493
|
+
facets: the facets indices to get the pairs from.
|
|
494
|
+
|
|
495
|
+
Returns: the local indices of facets in their corresponding cells ordered as [cell_1 local_facet cell_2 local_facet cell_3 local_facet ...]
|
|
496
|
+
"""
|
|
497
|
+
connected_cells = f2c_map[facets][:, 0]
|
|
498
|
+
facets_connected_cells = c2f_map[connected_cells]
|
|
499
|
+
facets_tiled = np.tile(facets[..., None], facets_connected_cells.shape[1])
|
|
500
|
+
mask = facets_tiled == facets_connected_cells
|
|
501
|
+
local_indices = np.where(mask)[1]
|
|
502
|
+
pairs = np.ravel([connected_cells.T, local_indices.T], "F")
|
|
503
|
+
return pairs
|
|
504
|
+
|
|
505
|
+
|
|
392
506
|
def _tag_facets(
|
|
393
507
|
mesh: Mesh,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
508
|
+
levelset_expression_facets: Expression,
|
|
509
|
+
cells_tags: MeshTags | None = None,
|
|
510
|
+
levelset_expression_cells: Expression | None = None,
|
|
511
|
+
single_layer_cut: bool = False,
|
|
397
512
|
) -> MeshTags:
|
|
398
513
|
"""Tag the mesh facets.
|
|
399
514
|
Strictly interior facets => tag 1
|
|
@@ -412,104 +527,83 @@ def _tag_facets(
|
|
|
412
527
|
Returns:
|
|
413
528
|
The facets tags as a MeshTags object.
|
|
414
529
|
"""
|
|
530
|
+
if cells_tags is None:
|
|
531
|
+
assert levelset_expression_cells is not None, (
|
|
532
|
+
"You must either pass cells_tags or a levelset expression over the cells of the domain."
|
|
533
|
+
)
|
|
534
|
+
cells_tags = _tag_cells(
|
|
535
|
+
mesh, levelset_expression_cells, single_layer_cut=single_layer_cut
|
|
536
|
+
)
|
|
537
|
+
|
|
415
538
|
cdim = mesh.topology.dim
|
|
416
539
|
fdim = cdim - 1
|
|
417
|
-
|
|
418
|
-
mesh.topology.
|
|
540
|
+
mesh.topology.create_connectivity(fdim, cdim)
|
|
541
|
+
f2c_connect = mesh.topology.connectivity(fdim, cdim)
|
|
419
542
|
c2f_connect = mesh.topology.connectivity(cdim, fdim)
|
|
543
|
+
f2c_map = _reshape_map(f2c_connect)[0]
|
|
420
544
|
num_facets_per_cell = len(c2f_connect.links(0))
|
|
421
545
|
c2f_map = np.reshape(c2f_connect.array, (-1, num_facets_per_cell))
|
|
422
546
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
)
|
|
466
|
-
|
|
467
|
-
# If there is no exterior_cells, the boundary facets are just the facets on the boundary of Ω_h
|
|
468
|
-
if len(exterior_cells) == 0:
|
|
469
|
-
boundary_facets = background_mesh_boundary_facets
|
|
470
|
-
else:
|
|
471
|
-
# Facets shared by an exterior cell and a cut cell
|
|
472
|
-
boundary_facets = np.intersect1d(c2f_map[exterior_cells], c2f_map[cut_cells])
|
|
473
|
-
boundary_facets = np.union1d(boundary_facets, uncut_boundary_facets)
|
|
474
|
-
|
|
475
|
-
direct_interface_facets = np.intersect1d(
|
|
476
|
-
c2f_map[exterior_cells], c2f_map[interior_cells]
|
|
477
|
-
)
|
|
478
|
-
# Cut facets F_h^Γ
|
|
479
|
-
facets_to_remove = np.union1d(boundary_facets, interior_boundary_facets)
|
|
480
|
-
facets_to_remove = np.union1d(facets_to_remove, direct_interface_facets)
|
|
481
|
-
facets_to_remove = np.union1d(facets_to_remove, uncut_boundary_facets)
|
|
482
|
-
cut_facets = np.setdiff1d(c2f_map[cut_cells], facets_to_remove)
|
|
483
|
-
cut_facets = np.union1d(cut_facets, cut_boundary_facets)
|
|
484
|
-
|
|
485
|
-
# Interior facets
|
|
486
|
-
facets_to_remove = np.union1d(interior_boundary_facets, boundary_facets)
|
|
487
|
-
facets_to_remove = np.union1d(facets_to_remove, direct_interface_facets)
|
|
488
|
-
interior_facets = np.setdiff1d(c2f_map[interior_cells], facets_to_remove)
|
|
489
|
-
|
|
490
|
-
# Exterior facets
|
|
491
|
-
facets_to_remove = np.union1d(interior_boundary_facets, boundary_facets)
|
|
492
|
-
facets_to_remove = np.union1d(facets_to_remove, direct_interface_facets)
|
|
493
|
-
exterior_facets = np.setdiff1d(c2f_map[exterior_cells], facets_to_remove)
|
|
494
|
-
|
|
495
|
-
boundary_facets = np.setdiff1d(boundary_facets, cut_facets)
|
|
547
|
+
marker = lambda x: np.ones_like(x[0]).astype(bool)
|
|
548
|
+
all_facets = dfx.mesh.locate_entities(mesh, fdim, marker)
|
|
549
|
+
boundary_facets = dfx.mesh.locate_entities_boundary(mesh, fdim, marker)
|
|
550
|
+
interior_facets = np.setdiff1d(all_facets, boundary_facets)
|
|
551
|
+
|
|
552
|
+
cell_facet_pairs = _cells_facets_pairs(f2c_map, c2f_map, interior_facets)
|
|
553
|
+
levelset_eval_int = levelset_expression_facets.eval(mesh, cell_facet_pairs)
|
|
554
|
+
|
|
555
|
+
exterior_int = np.min(levelset_eval_int, axis=1) > 0.0
|
|
556
|
+
interior_int = np.max(levelset_eval_int, axis=1) < 0.0
|
|
557
|
+
direct_int = np.isclose(np.sum(np.abs(levelset_eval_int), axis=1), 0.0)
|
|
558
|
+
|
|
559
|
+
connected_cells = f2c_map[boundary_facets][:, 0]
|
|
560
|
+
|
|
561
|
+
cell_facet_pairs = _compute_integration_entities(
|
|
562
|
+
mesh, connected_cells, boundary_facets, 0
|
|
563
|
+
)[0][1]
|
|
564
|
+
levelset_eval_bdy = levelset_expression_facets.eval(mesh, cell_facet_pairs)
|
|
565
|
+
exterior_bdy = np.min(levelset_eval_bdy, axis=1) > 0.0
|
|
566
|
+
interior_bdy = np.max(levelset_eval_bdy, axis=1) < 0.0
|
|
567
|
+
direct_bdy = np.isclose(np.sum(np.abs(levelset_eval_bdy), axis=1), 0.0)
|
|
568
|
+
|
|
569
|
+
facets_indices = np.hstack([interior_facets, boundary_facets])
|
|
570
|
+
exterior = np.hstack([exterior_int, exterior_bdy])
|
|
571
|
+
interior = np.hstack([interior_int, interior_bdy])
|
|
572
|
+
direct = np.hstack([direct_int, direct_bdy])
|
|
573
|
+
exterior_indices = facets_indices[exterior]
|
|
574
|
+
interior_indices = facets_indices[interior]
|
|
575
|
+
direct_indices = facets_indices[direct]
|
|
576
|
+
|
|
577
|
+
cut = np.logical_not(np.logical_or(exterior, interior))
|
|
578
|
+
cut_indices = facets_indices[cut]
|
|
579
|
+
cut_indices = np.setdiff1d(cut_indices, direct_indices)
|
|
580
|
+
|
|
581
|
+
# Compute the list of facets on the boundary of the union of cut cells
|
|
582
|
+
boundary_cut_indices = _compute_subdomain_exterior_facets(mesh, cells_tags, [2])
|
|
583
|
+
boundary_exterior_indices = np.intersect1d(boundary_cut_indices, interior_indices)
|
|
584
|
+
boundary_exterior_indices = np.setdiff1d(boundary_exterior_indices, direct_indices)
|
|
585
|
+
interior_indices = np.setdiff1d(interior_indices, boundary_exterior_indices)
|
|
586
|
+
|
|
587
|
+
boundary_interior_indices = np.intersect1d(boundary_cut_indices, exterior_indices)
|
|
588
|
+
boundary_interior_indices = np.setdiff1d(boundary_interior_indices, direct_indices)
|
|
589
|
+
exterior_indices = np.setdiff1d(exterior_indices, boundary_interior_indices)
|
|
496
590
|
|
|
497
591
|
# Only exterior_facets might be empty
|
|
498
592
|
if debug_mode:
|
|
499
|
-
if len(
|
|
593
|
+
if len(interior_indices) == 0:
|
|
500
594
|
raise ValueError("No interior facets (1)!")
|
|
501
|
-
if len(
|
|
595
|
+
if len(cut_indices) == 0:
|
|
502
596
|
print("WARNING: no cut facet computed in the partition.")
|
|
503
|
-
if len(
|
|
597
|
+
if len(boundary_interior_indices) == 0:
|
|
504
598
|
raise ValueError("No boundary facets (4)!")
|
|
505
599
|
|
|
506
600
|
# The lists must not intersect
|
|
507
601
|
names = ["interior facets (1)", "cut facets (2)", "boundary facets (4)"]
|
|
508
602
|
for i, facets_list_1 in enumerate(
|
|
509
|
-
[
|
|
603
|
+
[interior_indices, cut_indices, boundary_interior_indices]
|
|
510
604
|
):
|
|
511
605
|
for j, facets_list_2 in enumerate(
|
|
512
|
-
[
|
|
606
|
+
[interior_indices, cut_indices, boundary_interior_indices]
|
|
513
607
|
):
|
|
514
608
|
if i != j and len(np.intersect1d(facets_list_1, facets_list_2)) > 0:
|
|
515
609
|
raise ValueError(
|
|
@@ -518,26 +612,25 @@ def _tag_facets(
|
|
|
518
612
|
+ names[j]
|
|
519
613
|
+ " have a non-empty intersection!"
|
|
520
614
|
)
|
|
521
|
-
|
|
522
615
|
# Create the meshtags from the indices.
|
|
523
616
|
indices = np.hstack(
|
|
524
617
|
[
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
618
|
+
exterior_indices,
|
|
619
|
+
interior_indices,
|
|
620
|
+
boundary_exterior_indices,
|
|
621
|
+
cut_indices,
|
|
622
|
+
boundary_interior_indices,
|
|
623
|
+
direct_indices,
|
|
531
624
|
]
|
|
532
625
|
).astype(np.int32)
|
|
533
|
-
interior_marker = np.full_like(
|
|
534
|
-
cut_marker = np.full_like(
|
|
535
|
-
interior_boundary_marker = np.full_like(
|
|
626
|
+
interior_marker = np.full_like(interior_indices, 1).astype(np.int32)
|
|
627
|
+
cut_marker = np.full_like(cut_indices, 2).astype(np.int32)
|
|
628
|
+
interior_boundary_marker = np.full_like(boundary_exterior_indices, 3).astype(
|
|
536
629
|
np.int32
|
|
537
630
|
)
|
|
538
|
-
boundary_marker = np.full_like(
|
|
539
|
-
exterior_marker = np.full_like(
|
|
540
|
-
direct_interface_marker = np.full_like(
|
|
631
|
+
boundary_marker = np.full_like(boundary_interior_indices, 4).astype(np.int32)
|
|
632
|
+
exterior_marker = np.full_like(exterior_indices, 5).astype(np.int32)
|
|
633
|
+
direct_interface_marker = np.full_like(direct_indices, 6).astype(np.int32)
|
|
541
634
|
markers = np.hstack(
|
|
542
635
|
[
|
|
543
636
|
exterior_marker,
|
|
@@ -563,18 +656,74 @@ def _overwrite_tags(mesh, tags_to_overwrite, new_tags):
|
|
|
563
656
|
overwritten_indices, ind = np.unique(stack_indices, return_index=True)
|
|
564
657
|
overwritten_values = stack_values[ind]
|
|
565
658
|
|
|
566
|
-
overwritten_tags = dfx.mesh.meshtags(
|
|
659
|
+
overwritten_tags = dfx.mesh.meshtags(
|
|
660
|
+
mesh, tags_to_overwrite.dim, overwritten_indices, overwritten_values
|
|
661
|
+
)
|
|
567
662
|
return overwritten_tags
|
|
568
663
|
|
|
569
664
|
|
|
665
|
+
def _compute_codim_interpolation_points(
|
|
666
|
+
space: dfx.fem.FunctionSpace,
|
|
667
|
+
) -> np.ndarray[np.float64]:
|
|
668
|
+
"""Compute the same FunctionSpace but defined over a submesh of codim 1.
|
|
669
|
+
|
|
670
|
+
Args:
|
|
671
|
+
space: the original space.
|
|
672
|
+
|
|
673
|
+
Return: a new space defined on a mesh of codim 1.
|
|
674
|
+
"""
|
|
675
|
+
family_name = space.ufl_element().basix_element.family.name
|
|
676
|
+
degree = space.ufl_element().basix_element.degree
|
|
677
|
+
tdim = space.mesh.topology.dim
|
|
678
|
+
codim_mesh = dfx.mesh.create_submesh(space.mesh, tdim - 1, np.array([0.0]))[0]
|
|
679
|
+
codim_cell_name = codim_mesh.topology.cell_name()
|
|
680
|
+
new_elmt = element(family_name, codim_cell_name, degree)
|
|
681
|
+
new_space = dfx.fem.functionspace(codim_mesh, new_elmt)
|
|
682
|
+
return new_space.element.interpolation_points()
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def _levelset_expression(
|
|
686
|
+
mesh: dfx.mesh.Mesh,
|
|
687
|
+
levelset: Function | Callable,
|
|
688
|
+
interpolation_points: np.NDarray[np.float64],
|
|
689
|
+
) -> Expression:
|
|
690
|
+
"""Sanitize the levelset input by turning it into an Expression.
|
|
691
|
+
|
|
692
|
+
Args:
|
|
693
|
+
levelset: the levelset function.
|
|
694
|
+
detection_space: the detection space.
|
|
695
|
+
|
|
696
|
+
Return: the levelset as an Expression object.
|
|
697
|
+
"""
|
|
698
|
+
|
|
699
|
+
# Sanitize levelset input
|
|
700
|
+
try:
|
|
701
|
+
# Test if levelset is a dolfinx.fem.Function
|
|
702
|
+
_ = levelset.function_space
|
|
703
|
+
levelset_expression = dfx.fem.Expression(
|
|
704
|
+
levelset, interpolation_points, comm=mesh.comm
|
|
705
|
+
)
|
|
706
|
+
except AttributeError:
|
|
707
|
+
try:
|
|
708
|
+
x = ufl.SpatialCoordinate(mesh)
|
|
709
|
+
levelset_expression = dfx.fem.Expression(
|
|
710
|
+
levelset(x), interpolation_points, comm=mesh.comm
|
|
711
|
+
)
|
|
712
|
+
except TypeError:
|
|
713
|
+
print(
|
|
714
|
+
"Invalid levelset type: must be either a dolfinx.fem.Function or a UFL based Callable."
|
|
715
|
+
)
|
|
716
|
+
return levelset_expression
|
|
717
|
+
|
|
718
|
+
|
|
570
719
|
def compute_tags_measures(
|
|
571
720
|
mesh: Mesh,
|
|
572
|
-
|
|
573
|
-
|
|
721
|
+
levelset: Function | Callable,
|
|
722
|
+
detection_space: FunctionSpace,
|
|
574
723
|
box_mode: bool = False,
|
|
575
724
|
single_layer_cut: bool = False,
|
|
576
|
-
overwrite_tags: dict[str,MeshTags] |
|
|
577
|
-
) ->
|
|
725
|
+
overwrite_tags: dict[str, MeshTags] | None = None,
|
|
726
|
+
) -> tuple[
|
|
578
727
|
MeshTags,
|
|
579
728
|
MeshTags,
|
|
580
729
|
Mesh | None,
|
|
@@ -597,55 +746,53 @@ def compute_tags_measures(
|
|
|
597
746
|
The boundaries measure.
|
|
598
747
|
Submesh c-map, v-map and n-map.
|
|
599
748
|
"""
|
|
749
|
+
interpolation_points = detection_space.element.interpolation_points()
|
|
750
|
+
levelset_expression = _levelset_expression(mesh, levelset, interpolation_points)
|
|
751
|
+
|
|
600
752
|
cells_tags = _tag_cells(
|
|
601
|
-
mesh,
|
|
753
|
+
mesh, levelset_expression, single_layer_cut=single_layer_cut
|
|
602
754
|
)
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
755
|
+
|
|
756
|
+
codim_interpolation_points = _compute_codim_interpolation_points(detection_space)
|
|
757
|
+
levelset_expression_facets = _levelset_expression(
|
|
758
|
+
mesh, levelset, codim_interpolation_points
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
facets_tags = _tag_facets(
|
|
762
|
+
mesh,
|
|
763
|
+
levelset_expression_facets,
|
|
764
|
+
cells_tags,
|
|
765
|
+
levelset_expression,
|
|
766
|
+
single_layer_cut=single_layer_cut,
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
if overwrite_tags is not None:
|
|
770
|
+
if "cells" in overwrite_tags:
|
|
771
|
+
ow_cells_tags = overwrite_tags["cells"]
|
|
772
|
+
if np.any(np.isin([1, 2, 3], ow_cells_tags.values)):
|
|
773
|
+
raise ValueError("Cannot overwrite cells tags with values 1, 2 or 3.")
|
|
774
|
+
cells_tags = _overwrite_tags(mesh, cells_tags, ow_cells_tags)
|
|
775
|
+
if "facets" in overwrite_tags:
|
|
776
|
+
ow_facets_tags = overwrite_tags["facets"]
|
|
777
|
+
if np.any(np.isin([1, 2, 3, 4, 5, 6, 100, 101], ow_facets_tags.values)):
|
|
778
|
+
raise ValueError(
|
|
779
|
+
"Cannot overwrite facets tags with values 1, 2, 3, 4, 5, 6, 100 or 101."
|
|
780
|
+
)
|
|
781
|
+
facets_tags = _overwrite_tags(mesh, facets_tags, ow_facets_tags)
|
|
615
782
|
|
|
616
783
|
if box_mode:
|
|
617
784
|
submesh = None
|
|
618
785
|
integration_cells = np.union1d(cells_tags.find(2), cells_tags.find(1))
|
|
619
786
|
integration_entities_outside = _compute_integration_entities(
|
|
620
|
-
mesh, integration_cells, facets_tags.find(4)
|
|
621
|
-
|
|
787
|
+
mesh, integration_cells, facets_tags.find(4), 100
|
|
788
|
+
)
|
|
622
789
|
integration_cells = np.union1d(cells_tags.find(2), cells_tags.find(3))
|
|
623
790
|
integration_entities_inside = _compute_integration_entities(
|
|
624
|
-
mesh, integration_cells, facets_tags.find(3)
|
|
625
|
-
|
|
626
|
-
combined_integration_entities =
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
fdim = facets_tags.dim
|
|
630
|
-
boundary_facets = dfx.mesh.locate_entities_boundary(mesh, fdim, lambda x: np.ones_like(x[0]).astype(bool))
|
|
631
|
-
non_phifem_facets_bdy = np.isin(facets_tags.indices, boundary_facets)
|
|
632
|
-
non_phifem_mask *= non_phifem_facets_bdy
|
|
633
|
-
non_phifem_facets_ind = np.arange(len(facets_tags.indices))[non_phifem_mask]
|
|
634
|
-
non_phifem_facets = facets_tags.indices[non_phifem_facets_ind]
|
|
635
|
-
non_phifem_facets_tags = np.unique(facets_tags.values[non_phifem_facets_ind])
|
|
636
|
-
|
|
637
|
-
for tag in non_phifem_facets_tags:
|
|
638
|
-
cdim = mesh.topology.dim
|
|
639
|
-
fdim = cdim - 1
|
|
640
|
-
mesh.topology.create_connectivity(fdim, cdim)
|
|
641
|
-
f2c_connect = mesh.topology.connectivity(fdim, cdim)
|
|
642
|
-
f2c_map = _reshape_map(f2c_connect)[0]
|
|
643
|
-
|
|
644
|
-
non_phifem_facets_cells = f2c_map[non_phifem_facets]
|
|
645
|
-
|
|
646
|
-
assert len(non_phifem_facets_cells) == len(non_phifem_facets)
|
|
647
|
-
|
|
648
|
-
combined_integration_entities += [(tag, _compute_integration_entities(mesh, non_phifem_facets_cells, facets_tags.find(tag)))]
|
|
791
|
+
mesh, integration_cells, facets_tags.find(3), 101
|
|
792
|
+
)
|
|
793
|
+
combined_integration_entities = (
|
|
794
|
+
integration_entities_outside + integration_entities_inside
|
|
795
|
+
)
|
|
649
796
|
|
|
650
797
|
boundaries_measure = ufl.Measure(
|
|
651
798
|
"ds", domain=mesh, subdomain_data=combined_integration_entities
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: phiFEM
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0.dev1
|
|
4
4
|
Summary: phiFEM: convenience tools to use the phiFEM immersed boundary finite element method with dolfinx.
|
|
5
5
|
Project-URL: Homepage, https://github.com/PhiFEM/phiFEM
|
|
6
6
|
Project-URL: Issues, https://github.com/PhiFEM/phiFEM/issues
|
|
@@ -11,8 +11,8 @@ License-File: COPYING
|
|
|
11
11
|
License-File: COPYING.lesser
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Requires-Python: >=3.
|
|
15
|
-
Requires-Dist: fenics-dolfinx
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Requires-Dist: fenics-dolfinx==0.9.0
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
18
|
<p align="center" width="100%">
|
|
@@ -118,7 +118,7 @@ To run the demos first clone the repository.
|
|
|
118
118
|
The author Raphaël Bulle guarantees that his contributions to this project are generative-ai-free.
|
|
119
119
|
|
|
120
120
|
<p align="center" width="50%">
|
|
121
|
-
<img width="30%" src="https://github.com/PhiFEM/phiFEM/raw/main/logo/
|
|
121
|
+
<img width="30%" src="https://github.com/PhiFEM/phiFEM/raw/main/logo/image-572.png">
|
|
122
122
|
</p>
|
|
123
123
|
|
|
124
124
|
For more information, see [here (in french)](https://doi.org/10.58079/1591s).
|
|
@@ -133,4 +133,4 @@ You should have received a copy of the GNU Lesser General Public License along w
|
|
|
133
133
|
|
|
134
134
|
## Logo credit
|
|
135
135
|
|
|
136
|
-
Frédérique Lecourtier ([https://mimesis.inria.fr/members/frederique-lecourtier/](https://mimesis.inria.fr/members/frederique-lecourtier/))
|
|
136
|
+
Frédérique Lecourtier ([https://mimesis.inria.fr/members/frederique-lecourtier/](https://mimesis.inria.fr/members/frederique-lecourtier/))
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
phifem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
phifem/mesh_scripts.py,sha256=g9G_QjgERcNNdo26tCGymidLYaCRzSvMYwVaCOmiDIs,32508
|
|
3
|
+
phifem-0.8.0.dev1.dist-info/METADATA,sha256=jFmzNbqBlHFois1398voBC6m7rJyKoUpT_a6s_seKGI,5606
|
|
4
|
+
phifem-0.8.0.dev1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
5
|
+
phifem-0.8.0.dev1.dist-info/licenses/COPYING,sha256=xTplwv1WHIfqq_EHLvXcq4ZTBCvBUwhGX1JBNYXrYnE,35146
|
|
6
|
+
phifem-0.8.0.dev1.dist-info/licenses/COPYING.lesser,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
|
|
7
|
+
phifem-0.8.0.dev1.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
phifem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
phifem/mesh_scripts.py,sha256=NSciUpSeABNaZAHdihEI4yUqYZGSFk-NvjuRkSUqrhk,26963
|
|
3
|
-
phifem-0.7.2.dev1.dist-info/METADATA,sha256=M_YPSSUAxI-OOC8n4YM2Ld7AnHfnkx3QtlK9qBJ-K4g,5603
|
|
4
|
-
phifem-0.7.2.dev1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
5
|
-
phifem-0.7.2.dev1.dist-info/licenses/COPYING,sha256=xTplwv1WHIfqq_EHLvXcq4ZTBCvBUwhGX1JBNYXrYnE,35146
|
|
6
|
-
phifem-0.7.2.dev1.dist-info/licenses/COPYING.lesser,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
|
|
7
|
-
phifem-0.7.2.dev1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|