11# # Electromagnetic scattering from a wire with PML
22#
3- # Copyright (C) 2022 Michele Castriotta, Igor Baratta, Jørgen S. Dokken
3+ # Copyright (C) 2022-2025 Michele Castriotta, Igor Baratta
4+ # and Jørgen S. Dokken
45#
56# ```{admonition} Download sources
67# :class: download
1213# - Use complex quantities in FEniCSx
1314# - Setup and solve Maxwell's equations
1415# - Implement (rectangular) perfectly matched layers (PMLs)
16+ # - Use custom integration entities for one-sided interior facet integrals
1517#
1618# First, we import the required modules
1719
@@ -549,7 +551,7 @@ def pml_coordinates(x: ufl.indexed.Indexed, alpha: float, k0: complex, l_dom: fl
549551#
550552# with $A^{-1}=\operatorname{det}(\mathbf{J})$.
551553#
552- # We use `ufl.grad` to calculate the Jacobian of our coordinate
554+ # We use {py:func} `ufl.grad` to calculate the Jacobian of our coordinate
553555# transformation for the different PML regions, and then we can
554556# implement this Jacobian for calculating
555557# $\boldsymbol{\varepsilon}_{pml}$ and $\boldsymbol{\mu}_{pml}$. The
@@ -710,9 +712,73 @@ def create_eps_mu(
710712)
711713
712714# We calculate the numerical efficiencies in the same way as done in
713- # `demo_scattering_boundary_conditions.py`, with the only difference
714- # that now the scattering efficiency needs to be calculated over an
715- # inner facet, and therefore it requires a slightly different approach:
715+ # [Electromagnetic scattering demo](./demo_scattering_boundary_conditions),
716+ # with the only difference that now the scattering efficiency needs to be
717+ # calculated over an inner facet, and therefore it requires a slightly
718+ # different approach:
719+
720+ # ### One-sided interior facet integrals
721+
722+ # An integration entity of an integral over a single facet in DOLFINx is
723+ # defined as a tuple, `(cell_idx, local_facet_idx)`, where `cell_idx` is
724+ # the index of a cell containing the facet, (local to process),
725+ # while `local_facet_idx` is the relative index of the facet in the cell.
726+ # For exterior facets, this is straightforward to obtain,
727+ # as a facet is then connected to only one cell.
728+ # However, for an interior facet a facet is connected to at lest two cells.
729+ # As we would like to compute the outwards facing flux through the wire,
730+ # we want to be able to integrate only from the side of the cell that has
731+ # a normal pointing outwards.
732+ # We start by identifying all cells that are interior to the scatter tag.
733+
734+ cell_map = mesh_data .mesh .topology .index_map (tdim )
735+ num_cells_local = cell_map .size_local + cell_map .num_ghosts
736+ midpoints = mesh .compute_midpoints (mesh_data .mesh , tdim , np .arange (num_cells_local , dtype = np .int32 ))
737+ is_inner_cell = (midpoints [:, 0 ] ** 2 + midpoints [:, 1 ] ** 2 ) < (radius_scatt ) ** 2
738+
739+ # Next, we compute the integration entity for the facets in question.
740+ # We start by finding all facets owned by the current process (to assure
741+ # that we only integrate over each facet once), and then for each facet,
742+ # we find the connected cells.
743+
744+ # +
745+ # Get connectivity between facets and cells
746+ mesh_data .mesh .topology .create_connectivity (tdim - 1 , tdim )
747+ mesh_data .mesh .topology .create_connectivity (tdim , tdim - 1 )
748+ f_to_c = mesh_data .mesh .topology .connectivity (tdim - 1 , tdim )
749+ c_to_f = mesh_data .mesh .topology .connectivity (tdim , tdim - 1 )
750+
751+ # Filter facets to only those owned by the current process
752+ num_facets_local = mesh_data .mesh .topology .index_map (tdim - 1 ).size_local
753+ scatt_facets = mesh_data .facet_tags .find (scatt_tag )
754+ scatt_facets = scatt_facets [scatt_facets < num_facets_local ]
755+
756+ # Pack integration data
757+ integration_entities = np .empty ((len (scatt_facets ), 2 ), dtype = np .int32 )
758+ for i , facet in enumerate (scatt_facets ):
759+ connected_cells = f_to_c .links (facet )
760+ inner_cell_idx = np .flatnonzero (is_inner_cell [connected_cells ])
761+ assert len (inner_cell_idx ) == 1 , "Expected exactly one inner cell per facet."
762+ inner_cell = connected_cells [inner_cell_idx [0 ]]
763+ local_facets = c_to_f .links (inner_cell )
764+ local_facet_idx = np .flatnonzero (local_facets == facet )[0 ]
765+ integration_entities [i , :] = (inner_cell , local_facet_idx )
766+ # -
767+
768+ # Now that we have computed the integration entities, we define
769+ # an integration measure for one-sided facet integrals `ufl.ds`:
770+
771+ ds_scatter = ufl .ds (
772+ domain = mesh_data .mesh ,
773+ subdomain_data = [(scatt_tag , integration_entities .flatten ())],
774+ subdomain_id = scatt_tag ,
775+ )
776+
777+
778+ scatt_facets = mesh_data .facet_tags .find (scatt_tag )
779+ incident_cells = mesh .compute_incident_entities (
780+ mesh_data .mesh .topology , scatt_facets , tdim - 1 , tdim
781+ )
716782
717783# +
718784Z0 = np .sqrt (mu_0 / epsilon_0 ) # Vacuum impedance
@@ -727,22 +793,8 @@ def create_eps_mu(
727793n = ufl .FacetNormal (mesh_data .mesh )
728794n_3d = ufl .as_vector ((n [0 ], n [1 ], 0 ))
729795
730- # Create a marker for the integration boundary for the scattering
731- # efficiency
732- marker = fem .Function (D )
733- scatt_facets = mesh_data .facet_tags .find (scatt_tag )
734- incident_cells = mesh .compute_incident_entities (
735- mesh_data .mesh .topology , scatt_facets , tdim - 1 , tdim
736- )
737-
738- mesh_data .mesh .topology .create_connectivity (tdim , tdim )
739- midpoints = mesh .compute_midpoints (mesh_data .mesh , tdim , incident_cells )
740- inner_cells = incident_cells [(midpoints [:, 0 ] ** 2 + midpoints [:, 1 ] ** 2 ) < (radius_scatt ) ** 2 ]
741-
742- marker .x .array [inner_cells ] = 1
743-
744796# Quantities for the calculation of efficiencies
745- P = 0.5 * ufl .inner (ufl .cross (Esh_3d , ufl .conj (Hsh_3d )), n_3d ) * marker
797+ P = 0.5 * ufl .inner (ufl .cross (Esh_3d , ufl .conj (Hsh_3d )), n_3d )
746798Q = 0.5 * eps_au .imag * k0 * (ufl .inner (E_3d , E_3d )) / (Z0 * n_bkg )
747799
748800# Normalized absorption efficiency
@@ -752,10 +804,7 @@ def create_eps_mu(
752804q_abs_fenics = mesh_data .mesh .comm .allreduce (q_abs_fenics_proc , op = MPI .SUM )
753805
754806# Normalized scattering efficiency
755- dS = ufl .Measure ("dS" , mesh_data .mesh , subdomain_data = mesh_data .facet_tags )
756- q_sca_fenics_proc = (
757- fem .assemble_scalar (fem .form ((P ("+" ) + P ("-" )) * dS (scatt_tag ))) / (gcs * I0 )
758- ).real
807+ q_sca_fenics_proc = (fem .assemble_scalar (fem .form (P * ds_scatter )) / (gcs * I0 )).real
759808
760809# Sum results from all MPI processes
761810q_sca_fenics = mesh_data .mesh .comm .allreduce (q_sca_fenics_proc , op = MPI .SUM )
0 commit comments