phiFEM 0.7.0.dev1__py3-none-any.whl → 0.7.1.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 CHANGED
@@ -118,22 +118,19 @@ def _compute_detection_vector(
118
118
  detection_denom_form = dfx.fem.form(detection_denom)
119
119
  detection_denom_vec = assemble_vector(detection_denom_form)
120
120
 
121
- imap = dg_0_space.dofmap.index_map
122
- detection_vector = dfx.la.vector(imap)
123
121
  # detection_denom_vec is not supposed to be zero, this would mean that the levelset is zero at all dofs in a cell.
124
122
  # However, in practice it can happen that for a very small cut triangle, detection_denom_vec is of the order of the machine precision.
125
123
  # In this case, we set the value of detection_vector to 0.5, meaning we consider the cell as cut.
124
+ mask = np.where(detection_denom_vec.array > 0.0)
125
+ detection_vector = np.full_like(detection_num_vec.array, 0.5)
126
+ detection_vector[mask] = (
127
+ detection_num_vec.array[mask] / detection_denom_vec.array[mask]
128
+ )
126
129
  if np.any(np.isclose(detection_denom_vec.array, 0.0)):
127
130
  warnings.warn(
128
- "The levelset function is zero everywhere on a cell. We mark it as 'cut' but this can be incorrect and should be carefully checked.",
131
+ "The detection function is zero everywhere on a cell. We mark it as 'cut' but this can be incorrect and should be carefully checked.",
129
132
  RuntimeWarning,
130
133
  )
131
- mask = np.where(detection_denom_vec.array > 0.0)
132
- detection_vector.array[:] = 0.5
133
- detection_vector.array[mask] = (
134
- detection_num_vec.array[mask] / detection_denom_vec.array[mask]
135
- )
136
- detection_vector.scatter_forward()
137
134
  return detection_vector
138
135
 
139
136
 
@@ -217,31 +214,71 @@ def _reshape_map(connect: AdjacencyList_int32) -> npt.NDArray[np.int32]:
217
214
  return emap, max_offset
218
215
 
219
216
 
220
- def _transfer_markers(
221
- source_markers: dfx.la.Vector,
217
+ def _transfer_tags(
218
+ source_mesh_tags: MeshTags,
219
+ dest_mesh: Mesh,
222
220
  cmap: npt.NDArray[Any],
223
- edim: int,
224
- ) -> dfx.la.Vector:
221
+ source_mesh: Mesh = None,
222
+ ) -> MeshTags:
225
223
  """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.
226
224
 
227
225
  Args:
228
- source_markers: the markers on the source mesh.
226
+ source_mesh_tags: the tags on the source mesh.
229
227
  dest_mesh: the destination mesh.
230
228
  cmap: the source mesh-destination mesh cells mapping.
229
+ source_mesh: the source mesh mandatory to transfer facets tags.
231
230
 
232
231
  Returns:
233
232
  Cells tags on the destination mesh.
234
233
  """
235
- sub_index_map = cmap.sub_topology.index_map(edim)
236
- local_entities = np.arange(sub_index_map.size_local + sub_index_map.num_ghosts)
237
- emap = cmap.sub_topology_to_topology(
238
- local_entities,
239
- False,
234
+ cdim = dest_mesh.topology.dim
235
+ fdim = cdim - 1
236
+ edim = source_mesh_tags.dim
237
+
238
+ if edim == cdim:
239
+ emap = cmap
240
+ elif edim == fdim:
241
+ if source_mesh is None:
242
+ raise ValueError("You must pass a source_mesh to transfer facets tags.")
243
+
244
+ source_mesh.topology.create_connectivity(cdim, fdim)
245
+ c2f_connect = source_mesh.topology.connectivity(cdim, fdim)
246
+ num_facets_per_cell = len(c2f_connect.links(0))
247
+ source_c2f_map = np.reshape(c2f_connect.array, (-1, num_facets_per_cell))
248
+ dest_mesh.topology.create_connectivity(cdim, fdim)
249
+ c2f_connect = dest_mesh.topology.connectivity(cdim, fdim)
250
+ num_facets_per_cell = len(c2f_connect.links(0))
251
+ dest_c2f_map = np.reshape(c2f_connect.array, (-1, num_facets_per_cell))
252
+ source_c2f_dest_map = source_c2f_map[cmap]
253
+ source_c2f_dest_map = source_c2f_dest_map.reshape(
254
+ -1,
255
+ )
256
+ dest_c2f_map = dest_c2f_map.reshape(
257
+ -1,
258
+ )
259
+ unique_indices, sorted_indices = np.unique(dest_c2f_map, return_index=True)
260
+ emap = source_c2f_dest_map[sorted_indices]
261
+ else:
262
+ raise ValueError("The source_mesh_tags can only be cells tags or facets tags.")
263
+
264
+ # TODO: change this line to allow parallel computing
265
+ source_tags = source_mesh_tags.values
266
+
267
+ dest_entities = np.arange(len(emap))
268
+ dest_tags = source_tags[emap]
269
+
270
+ dest_entities_indices = np.hstack(dest_entities).astype(np.int32)
271
+ dest_entities_markers = np.hstack(dest_tags).astype(np.int32)
272
+ sorted_indices = np.argsort(dest_entities_indices)
273
+
274
+ dest_entities_tags = dfx.mesh.meshtags(
275
+ dest_mesh,
276
+ edim,
277
+ dest_entities_indices[sorted_indices],
278
+ dest_entities_markers[sorted_indices],
240
279
  )
241
- dest_markers = dfx.la.vector(sub_index_map)
242
- dest_markers.array[:] = source_markers.array[emap]
243
280
 
244
- return dest_markers
281
+ return dest_entities_tags
245
282
 
246
283
 
247
284
  def _tag_cells(
@@ -249,7 +286,7 @@ def _tag_cells(
249
286
  discrete_levelset: Function,
250
287
  detection_degree: int,
251
288
  single_layer_cut: bool = False,
252
- ) -> dfx.la.Vector:
289
+ ) -> MeshTags:
253
290
  """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.
254
291
  Strictly inside cell => tag 1
255
292
  Cut cell => tag 2
@@ -262,7 +299,7 @@ def _tag_cells(
262
299
  single_layer_cut: boolean, if True force a single layer of cut cells.
263
300
 
264
301
  Returns:
265
- The cells markers as a dfx.la.Vector object.
302
+ The cells tags as a MeshTags object.
266
303
  """
267
304
  if single_layer_cut:
268
305
  cdim = mesh.topology.dim
@@ -304,12 +341,10 @@ def _tag_cells(
304
341
  mesh, discrete_levelset, detection_measure
305
342
  )
306
343
  cut_indices = np.where(
307
- np.logical_and(
308
- detection_vector.array[:] > -1.0, detection_vector.array[:] < 1.0
309
- )
344
+ np.logical_and(detection_vector > -1.0, detection_vector < 1.0)
310
345
  )[0]
311
- exterior_indices = np.where(np.isclose(detection_vector.array[:], 1.0))[0]
312
- interior_indices = np.where(np.isclose(detection_vector.array[:], -1.0))[0]
346
+ exterior_indices = np.where(detection_vector == 1.0)[0]
347
+ interior_indices = np.where(detection_vector == -1.0)[0]
313
348
 
314
349
  if single_layer_cut:
315
350
  neighbor_cells = np.reshape(
@@ -337,21 +372,30 @@ def _tag_cells(
337
372
  assert np.logical_not(np.isin(exterior_indices, interior_indices).any()), (
338
373
  "The sets of outside cells and inside cells have a non-empty intersection"
339
374
  )
340
- cells_markers = dfx.la.vector(detection_vector.index_map)
341
- cells_markers.array[interior_indices] = 1
342
- cells_markers.array[cut_indices] = 2
343
- cells_markers.array[exterior_indices] = 3
344
- cells_markers.scatter_forward()
345
375
 
346
- return cells_markers
376
+ # Create the meshtags from the indices.
377
+ indices = np.hstack([exterior_indices, interior_indices, cut_indices]).astype(
378
+ np.int32
379
+ )
380
+ interior_marker = np.full_like(interior_indices, 1).astype(np.int32)
381
+ exterior_marker = np.full_like(exterior_indices, 3).astype(np.int32)
382
+ cut_marker = np.full_like(cut_indices, 2).astype(np.int32)
383
+ markers = np.hstack([exterior_marker, interior_marker, cut_marker]).astype(np.int32)
384
+ sorted_indices = np.argsort(indices)
385
+
386
+ cells_tags = dfx.mesh.meshtags(
387
+ mesh, mesh.topology.dim, indices[sorted_indices], markers[sorted_indices]
388
+ )
389
+
390
+ return cells_tags
347
391
 
348
392
 
349
393
  def _tag_facets(
350
394
  mesh: Mesh,
351
- cells_markers: dfx.la.Vector,
395
+ cells_tags: MeshTags,
352
396
  discrete_levelset: Function,
353
397
  detection_degree: int,
354
- ) -> dfx.la.Vector:
398
+ ) -> MeshTags:
355
399
  """Tag the mesh facets.
356
400
  Strictly interior facets => tag 1
357
401
  Cut facets => tag 2
@@ -362,13 +406,12 @@ def _tag_facets(
362
406
 
363
407
  Args:
364
408
  mesh: the background mesh.
365
- cells_markers: the vector storing the cells markers.
409
+ cells_tags: the MeshTags object containing cells tags.
366
410
  discrete_levelset: the discretization of the levelset.
367
411
  detection_degree: the degree of the custom quadrature rule used to detect cut entities.
368
412
 
369
413
  Returns:
370
- The facets markers as a dfx.la.Vector object.
371
- The wireframe mesh containing all facets.
414
+ The facets tags as a MeshTags object.
372
415
  """
373
416
  cdim = mesh.topology.dim
374
417
  fdim = cdim - 1
@@ -379,9 +422,9 @@ def _tag_facets(
379
422
  c2f_map = np.reshape(c2f_connect.array, (-1, num_facets_per_cell))
380
423
 
381
424
  # Get tagged cells
382
- interior_cells = np.where(cells_markers.array[:] == 1)
383
- cut_cells = np.where(cells_markers.array[:] == 2)
384
- exterior_cells = np.where(cells_markers.array[:] == 3)
425
+ interior_cells = cells_tags.find(1)
426
+ cut_cells = cells_tags.find(2)
427
+ exterior_cells = cells_tags.find(3)
385
428
 
386
429
  # Check which background mesh boundary facets are cut by the interface
387
430
  background_mesh_boundary_facets = dfx.mesh.locate_entities_boundary(
@@ -403,7 +446,7 @@ def _tag_facets(
403
446
  mesh, discrete_levelset, detection_measure
404
447
  )
405
448
  mask_cut_indices_cells = np.logical_and(
406
- detection_vector.array[:] > -1.0, detection_vector.array[:] < 1.0
449
+ detection_vector > -1.0, detection_vector < 1.0
407
450
  )
408
451
  cut_indices_cells = np.where(mask_cut_indices_cells)[0]
409
452
  comp_indices_cells = np.where(np.logical_not(mask_cut_indices_cells))[0]
@@ -477,26 +520,52 @@ def _tag_facets(
477
520
  + " have a non-empty intersection!"
478
521
  )
479
522
 
480
- # Allocate distributed vectors supporting facets markers.
481
- all_facets = dfx.mesh.locate_entities(
482
- mesh, 1, lambda x: np.ones_like(x[0]).astype(bool)
523
+ # Create the meshtags from the indices.
524
+ indices = np.hstack(
525
+ [
526
+ exterior_facets,
527
+ interior_facets,
528
+ interior_boundary_facets,
529
+ cut_facets,
530
+ boundary_facets,
531
+ direct_interface_facets,
532
+ ]
533
+ ).astype(np.int32)
534
+ interior_marker = np.full_like(interior_facets, 1).astype(np.int32)
535
+ cut_marker = np.full_like(cut_facets, 2).astype(np.int32)
536
+ interior_boundary_marker = np.full_like(interior_boundary_facets, 3).astype(
537
+ np.int32
483
538
  )
484
- wireframe = dfx.mesh.create_submesh(mesh, 1, all_facets)[0]
485
- cell_name_wf = wireframe.topology.cell_name()
486
- dg0_element_wf = element("DG", cell_name_wf, 0)
487
- dg0_space_wf = dfx.fem.functionspace(wireframe, dg0_element_wf)
488
- imap = dg0_space_wf.dofmap.index_map
489
- facets_markers = dfx.la.vector(imap)
539
+ boundary_marker = np.full_like(boundary_facets, 4).astype(np.int32)
540
+ exterior_marker = np.full_like(exterior_facets, 5).astype(np.int32)
541
+ direct_interface_marker = np.full_like(direct_interface_facets, 6).astype(np.int32)
542
+ markers = np.hstack(
543
+ [
544
+ exterior_marker,
545
+ interior_marker,
546
+ interior_boundary_marker,
547
+ cut_marker,
548
+ boundary_marker,
549
+ direct_interface_marker,
550
+ ]
551
+ ).astype(np.int32)
552
+ sorted_indices = np.argsort(indices)
490
553
 
491
- facets_markers.array[interior_facets] = 1
492
- facets_markers.array[cut_facets] = 2
493
- facets_markers.array[interior_boundary_facets] = 3
494
- facets_markers.array[boundary_facets] = 4
495
- facets_markers.array[exterior_facets] = 5
496
- facets_markers.array[direct_interface_facets] = 6
497
- facets_markers.scatter_forward()
554
+ facets_tags = dfx.mesh.meshtags(
555
+ mesh, fdim, indices[sorted_indices], markers[sorted_indices]
556
+ )
498
557
 
499
- return facets_markers
558
+ return facets_tags
559
+
560
+
561
+ def _overwrite_tags(mesh, tags_to_overwrite, new_tags):
562
+ stack_indices = np.hstack([new_tags.indices, tags_to_overwrite.indices])
563
+ stack_values = np.hstack([new_tags.values, tags_to_overwrite.values])
564
+ overwritten_indices, ind = np.unique(stack_indices, return_index=True)
565
+ overwritten_values = stack_values[ind]
566
+
567
+ overwritten_tags = dfx.mesh.meshtags(mesh, tags_to_overwrite.dim, overwritten_indices, overwritten_values)
568
+ return overwritten_tags
500
569
 
501
570
 
502
571
  def compute_tags_measures(
@@ -505,6 +574,7 @@ def compute_tags_measures(
505
574
  detection_degree: int,
506
575
  box_mode: bool = False,
507
576
  single_layer_cut: bool = False,
577
+ overwrite_tags: dict[str,MeshTags] | dict = {},
508
578
  ) -> Tuple[
509
579
  MeshTags,
510
580
  MeshTags,
@@ -528,34 +598,31 @@ def compute_tags_measures(
528
598
  The boundaries measure.
529
599
  Submesh c-map, v-map and n-map.
530
600
  """
531
- cells_markers = _tag_cells(
601
+ cells_tags = _tag_cells(
532
602
  mesh, discrete_levelset, detection_degree, single_layer_cut=single_layer_cut
533
603
  )
534
- facets_markers = _tag_facets(
535
- mesh, cells_markers, discrete_levelset, detection_degree
536
- )
604
+ facets_tags = _tag_facets(mesh, cells_tags, discrete_levelset, detection_degree)
605
+
606
+ if "cells" in overwrite_tags.keys():
607
+ ow_cells_tags = overwrite_tags["cells"]
608
+ if np.any(np.isin([1, 2, 3], ow_cells_tags.values)):
609
+ raise ValueError("Cannot overwrite cells tags with values 1, 2 or 3.")
610
+ cells_tags = _overwrite_tags(mesh, cells_tags, ow_cells_tags)
611
+ if "facets" in overwrite_tags.keys():
612
+ ow_facets_tags = overwrite_tags["facets"]
613
+ if np.any(np.isin([1, 2, 3, 4, 5, 6, 100, 101], ow_facets_tags.values)):
614
+ raise ValueError("Cannot overwrite facets tags with values 1, 2, 3, 4, 5, 6, 100 or 101.")
615
+ facets_tags = _overwrite_tags(mesh, facets_tags, ow_facets_tags)
537
616
 
538
617
  if box_mode:
539
618
  submesh = None
540
- integration_cells = np.union1d(
541
- np.where(np.isclose(cells_markers.array[:], 2)),
542
- np.where(np.isclose(cells_markers.array[:], 1)),
543
- )
619
+ integration_cells = np.union1d(cells_tags.find(2), cells_tags.find(1))
544
620
  integration_entities_outside = _compute_integration_entities(
545
- mesh,
546
- integration_cells,
547
- np.where(np.isclose(facets_markers.array[:], 4)),
548
- 100,
549
- )
550
- integration_cells = np.union1d(
551
- np.where(np.isclose(cells_markers.array[:], 2)),
552
- np.where(np.isclose(cells_markers.array[:], 3)),
621
+ mesh, integration_cells, facets_tags.find(4), 100
553
622
  )
623
+ integration_cells = np.union1d(cells_tags.find(2), cells_tags.find(3))
554
624
  integration_entities_inside = _compute_integration_entities(
555
- mesh,
556
- integration_cells,
557
- np.where(np.isclose(facets_markers.array[:], 3)),
558
- 101,
625
+ mesh, integration_cells, facets_tags.find(3), 101
559
626
  )
560
627
  combined_integration_entities = (
561
628
  integration_entities_outside + integration_entities_inside
@@ -565,61 +632,17 @@ def compute_tags_measures(
565
632
  "ds", domain=mesh, subdomain_data=combined_integration_entities
566
633
  )
567
634
  submesh_maps = None
568
- indices = np.arange(
569
- cells_markers.index_map.size_local + cells_markers.index_map.num_ghosts
570
- )
571
- cells_tags = dfx.mesh.meshtags(
572
- mesh, mesh.topology.dim, indices, cells_markers.array[:]
573
- )
574
- indices = np.arange(
575
- facets_markers.index_map.size_local + facets_markers.index_map.num_ghosts
576
- )
577
- facets_tags = dfx.mesh.meshtags(
578
- mesh, mesh.topology.dim - 1, indices, facets_markers.array[:]
579
- )
580
635
  else:
581
636
  # We create the submesh
582
- omega_h_cells = np.unique(
583
- np.hstack(
584
- [
585
- np.where(np.isclose(cells_markers.array[:], 1)),
586
- np.where(np.isclose(cells_markers.array[:], 2)),
587
- ]
588
- )
589
- )
637
+ omega_h_cells = np.unique(np.hstack([cells_tags.find(1), cells_tags.find(2)]))
590
638
  submesh, c_map, v_map, n_map = dfx.mesh.create_submesh(
591
639
  mesh, mesh.topology.dim, omega_h_cells
592
- )
593
- submesh_maps = [c_map, v_map, n_map]
640
+ ) # type: ignore
594
641
 
595
- cells_markers_submesh = _transfer_markers(
596
- cells_markers, c_map, submesh.topology.dim
597
- )
598
- submesh.topology.create_entities(submesh.topology.dim - 1)
599
- submesh_imap = submesh.topology.index_map(submesh.topology.dim - 1)
600
- submesh_facets = np.arange(submesh_imap.size_local + submesh_imap.num_ghosts)
601
- wf_submesh, wf_map = dfx.mesh.create_submesh(
602
- submesh, submesh.topology.dim - 1, submesh_facets
603
- )[:2]
604
- wf_submesh.topology.create_entities(wf_submesh.topology.dim)
605
- facets_markers_subwf = _transfer_markers(
606
- facets_markers, wf_map, submesh.topology.dim - 1
607
- )
642
+ cells_tags = _transfer_tags(cells_tags, submesh, c_map)
643
+ facets_tags = _transfer_tags(facets_tags, submesh, c_map, source_mesh=mesh)
608
644
  boundaries_measure = ufl.Measure("ds", domain=submesh)
609
-
610
- indices = np.arange(
611
- cells_markers_submesh.index_map.size_local
612
- + cells_markers_submesh.index_map.num_ghosts
613
- )
614
- cells_tags = dfx.mesh.meshtags(
615
- submesh, submesh.topology.dim, indices, cells_markers_submesh.array[:]
616
- )
617
- facets_tags = dfx.mesh.meshtags(
618
- submesh,
619
- submesh.topology.dim - 1,
620
- submesh_facets,
621
- facets_markers_subwf.array[submesh_facets],
622
- )
645
+ submesh_maps = [c_map, v_map, n_map]
623
646
 
624
647
  return (
625
648
  cells_tags,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phiFEM
3
- Version: 0.7.0.dev1
3
+ Version: 0.7.1.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
@@ -113,6 +113,16 @@ To run the demos first clone the repository.
113
113
  pytest
114
114
  ```
115
115
 
116
+ ## Disclaimer
117
+
118
+ The author Raphaël Bulle guarantees that his contributions to this project are generative-ai-free.
119
+
120
+ <p align="center" width="50%">
121
+ <img width="30%" src="https://github.com/PhiFEM/phiFEM/raw/main/logo/sw_wbg.png">
122
+ </p>
123
+
124
+ For more information, see [here (in french)](https://doi.org/10.58079/1591s).
125
+
116
126
  ## License
117
127
 
118
128
  `phiFEM` is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@@ -0,0 +1,7 @@
1
+ phifem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ phifem/mesh_scripts.py,sha256=mFbgpjs3tAmMOdMQYZCXICh91YIcwhKjE5lXmEUIcWw,25772
3
+ phifem-0.7.1.dev1.dist-info/METADATA,sha256=9-pTFrhjCVyvyK25xFdzNOS8jeD1IgYGpf6N2OI9GqU,5603
4
+ phifem-0.7.1.dev1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
5
+ phifem-0.7.1.dev1.dist-info/licenses/COPYING,sha256=xTplwv1WHIfqq_EHLvXcq4ZTBCvBUwhGX1JBNYXrYnE,35146
6
+ phifem-0.7.1.dev1.dist-info/licenses/COPYING.lesser,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
7
+ phifem-0.7.1.dev1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.30.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,7 +0,0 @@
1
- phifem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- phifem/mesh_scripts.py,sha256=jVRTsIQRmEffTfhWHyJrqiImqtWZoiCBnSpuhW4W-Ho,24614
3
- phifem-0.7.0.dev1.dist-info/METADATA,sha256=R0NitwqmCfRUTMiH-p907mLfKPWTX1RTEXJaKO-f0YY,5285
4
- phifem-0.7.0.dev1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
5
- phifem-0.7.0.dev1.dist-info/licenses/COPYING,sha256=xTplwv1WHIfqq_EHLvXcq4ZTBCvBUwhGX1JBNYXrYnE,35146
6
- phifem-0.7.0.dev1.dist-info/licenses/COPYING.lesser,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
7
- phifem-0.7.0.dev1.dist-info/RECORD,,