phiFEM 0.7.2.dev1__py3-none-any.whl → 0.7.2.dev2__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 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
- if os.environ["MODE"] == "debug":
25
- debug_mode = True
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
- ) -> list[int]:
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[Any],
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
- unique_indices, sorted_indices = np.unique(dest_c2f_map, return_index=True)
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
- discrete_levelset: Function,
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
- cells_tags: MeshTags,
395
- discrete_levelset: Function,
396
- detection_degree: int,
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
- # Create the cell to facet connectivity and reshape it into an array s.t. c2f_map[cell_index] = [facets of this cell index]
418
- mesh.topology.create_connectivity(cdim, fdim)
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
- # Get tagged cells
424
- interior_cells = cells_tags.find(1)
425
- cut_cells = cells_tags.find(2)
426
- exterior_cells = cells_tags.find(3)
427
-
428
- # Check which background mesh boundary facets are cut by the interface
429
- background_mesh_boundary_facets = dfx.mesh.locate_entities_boundary(
430
- mesh, fdim, lambda x: np.ones_like(x[0]).astype(bool)
431
- )
432
-
433
- points = _reference_segment_points(detection_degree)
434
- weights = np.ones_like(points[:, 0])
435
-
436
- detection_quadrature = {
437
- "quadrature_rule": "custom",
438
- "quadrature_points": points,
439
- "quadrature_weights": weights,
440
- }
441
-
442
- detection_measure = ufl.Measure("ds", domain=mesh, metadata=detection_quadrature)
443
-
444
- detection_vector = _compute_detection_vector(
445
- mesh, discrete_levelset, detection_measure
446
- )
447
- mask_cut_indices_cells = np.logical_and(
448
- detection_vector > -1.0, detection_vector < 1.0
449
- )
450
- cut_indices_cells = np.where(mask_cut_indices_cells)[0]
451
- comp_indices_cells = np.where(np.logical_not(mask_cut_indices_cells))[0]
452
-
453
- cut_boundary_facets = np.intersect1d(
454
- c2f_map[cut_indices_cells], background_mesh_boundary_facets
455
- )
456
- uncut_boundary_facets = np.intersect1d(
457
- c2f_map[comp_indices_cells], background_mesh_boundary_facets
458
- )
459
- uncut_boundary_facets = np.setdiff1d(uncut_boundary_facets, c2f_map[exterior_cells])
460
- uncut_boundary_facets = np.setdiff1d(uncut_boundary_facets, c2f_map[interior_cells])
461
-
462
- # Facets shared by an interior cell and a cut cell
463
- interior_boundary_facets = np.intersect1d(
464
- c2f_map[interior_cells], c2f_map[cut_cells]
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(interior_facets) == 0:
593
+ if len(interior_indices) == 0:
500
594
  raise ValueError("No interior facets (1)!")
501
- if len(cut_facets) == 0:
595
+ if len(cut_indices) == 0:
502
596
  print("WARNING: no cut facet computed in the partition.")
503
- if len(boundary_facets) == 0:
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
- [interior_facets, cut_facets, boundary_facets]
603
+ [interior_indices, cut_indices, boundary_interior_indices]
510
604
  ):
511
605
  for j, facets_list_2 in enumerate(
512
- [interior_facets, cut_facets, boundary_facets]
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
- exterior_facets,
526
- interior_facets,
527
- interior_boundary_facets,
528
- cut_facets,
529
- boundary_facets,
530
- direct_interface_facets,
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(interior_facets, 1).astype(np.int32)
534
- cut_marker = np.full_like(cut_facets, 2).astype(np.int32)
535
- interior_boundary_marker = np.full_like(interior_boundary_facets, 3).astype(
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(boundary_facets, 4).astype(np.int32)
539
- exterior_marker = np.full_like(exterior_facets, 5).astype(np.int32)
540
- direct_interface_marker = np.full_like(direct_interface_facets, 6).astype(np.int32)
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,20 @@ 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(mesh, tags_to_overwrite.dim, overwritten_indices, overwritten_values)
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
 
570
665
  def compute_tags_measures(
571
666
  mesh: Mesh,
572
- discrete_levelset: Function,
573
- detection_degree: int,
667
+ levelset: Function | Callable,
668
+ detection_space: FunctionSpace,
574
669
  box_mode: bool = False,
575
670
  single_layer_cut: bool = False,
576
- overwrite_tags: dict[str,MeshTags] | dict = {},
577
- ) -> Tuple[
671
+ overwrite_tags: dict[str, MeshTags] | None = None,
672
+ ) -> tuple[
578
673
  MeshTags,
579
674
  MeshTags,
580
675
  Mesh | None,
@@ -597,55 +692,55 @@ def compute_tags_measures(
597
692
  The boundaries measure.
598
693
  Submesh c-map, v-map and n-map.
599
694
  """
695
+
696
+ # Sanitize levelset input
697
+ try:
698
+ levelset_expression = dfx.fem.Expression(
699
+ levelset, detection_space.element.interpolation_points()
700
+ )
701
+ except AttributeError:
702
+ try:
703
+ x = mesh.SpatialCoordinate(mesh)
704
+ levelset_expression = dfx.fem.Expression(
705
+ levelset(x), detection_space.element.interpolation_points()
706
+ )
707
+ except TypeError:
708
+ print(
709
+ "Invalid levelset type: must be either a dolfinx.fem.Function or a UFL based Callable."
710
+ )
711
+
600
712
  cells_tags = _tag_cells(
601
- mesh, discrete_levelset, detection_degree, single_layer_cut=single_layer_cut
713
+ mesh, levelset_expression, single_layer_cut=single_layer_cut
602
714
  )
603
715
  facets_tags = _tag_facets(mesh, cells_tags, discrete_levelset, detection_degree)
604
716
 
605
- if "cells" in overwrite_tags.keys():
606
- ow_cells_tags = overwrite_tags["cells"]
607
- if np.any(np.isin([1, 2, 3], ow_cells_tags.values)):
608
- raise ValueError("Cannot overwrite cells tags with values 1, 2 or 3.")
609
- cells_tags = _overwrite_tags(mesh, cells_tags, ow_cells_tags)
610
- if "facets" in overwrite_tags.keys():
611
- ow_facets_tags = overwrite_tags["facets"]
612
- if np.any(np.isin([1, 2, 3, 4, 5, 6], ow_facets_tags.values)):
613
- raise ValueError("Cannot overwrite facets tags with values 1, 2, 3, 4, 5, 6, 100 or 101.")
614
- facets_tags = _overwrite_tags(mesh, facets_tags, ow_facets_tags)
717
+ if overwrite_tags is not None:
718
+ if "cells" in overwrite_tags:
719
+ ow_cells_tags = overwrite_tags["cells"]
720
+ if np.any(np.isin([1, 2, 3], ow_cells_tags.values)):
721
+ raise ValueError("Cannot overwrite cells tags with values 1, 2 or 3.")
722
+ cells_tags = _overwrite_tags(mesh, cells_tags, ow_cells_tags)
723
+ if "facets" in overwrite_tags:
724
+ ow_facets_tags = overwrite_tags["facets"]
725
+ if np.any(np.isin([1, 2, 3, 4, 5, 6, 100, 101], ow_facets_tags.values)):
726
+ raise ValueError(
727
+ "Cannot overwrite facets tags with values 1, 2, 3, 4, 5, 6, 100 or 101."
728
+ )
729
+ facets_tags = _overwrite_tags(mesh, facets_tags, ow_facets_tags)
615
730
 
616
731
  if box_mode:
617
732
  submesh = None
618
733
  integration_cells = np.union1d(cells_tags.find(2), cells_tags.find(1))
619
734
  integration_entities_outside = _compute_integration_entities(
620
- mesh, integration_cells, facets_tags.find(4))
621
- integration_entities_outside = [(100, integration_entities_outside)]
735
+ mesh, integration_cells, facets_tags.find(4), 100
736
+ )
622
737
  integration_cells = np.union1d(cells_tags.find(2), cells_tags.find(3))
623
738
  integration_entities_inside = _compute_integration_entities(
624
- mesh, integration_cells, facets_tags.find(3))
625
- integration_entities_inside = [(101, integration_entities_inside)]
626
- combined_integration_entities = integration_entities_outside + integration_entities_inside
627
-
628
- non_phifem_mask = ~np.isin(facets_tags.values, [1, 2, 3, 4, 5, 6])
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)))]
739
+ mesh, integration_cells, facets_tags.find(3), 101
740
+ )
741
+ combined_integration_entities = (
742
+ integration_entities_outside + integration_entities_inside
743
+ )
649
744
 
650
745
  boundaries_measure = ufl.Measure(
651
746
  "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.7.2.dev1
3
+ Version: 0.7.2.dev2
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.11
15
- Requires-Dist: fenics-dolfinx>=0.9.0
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/sw_wbg.png">
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=Im8vPg3NxA44qiynUmVkCvyu6C6tYcQLKhJpDY0FJbA,30820
3
+ phifem-0.7.2.dev2.dist-info/METADATA,sha256=pggXcQxyYtwUxScyjZtHvFlmkiakNl8kLtbr9qCdj4s,5606
4
+ phifem-0.7.2.dev2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
5
+ phifem-0.7.2.dev2.dist-info/licenses/COPYING,sha256=xTplwv1WHIfqq_EHLvXcq4ZTBCvBUwhGX1JBNYXrYnE,35146
6
+ phifem-0.7.2.dev2.dist-info/licenses/COPYING.lesser,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
7
+ phifem-0.7.2.dev2.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,,