Time-Dependent boundary for "dead-alive" elements

Hello, I am trying to model 3D metal printing process with moving Heat source. I have Robin bcs in boundary facets of working area and base plate except for bottom facets where i add Dirichlet bcs. After evaluation of Heat source as a dolfinx.fem.Function at some point using Heat.interpolate(lambda x: self.HeatSource(current_point, x)) i update dolfinx.mesh.meshtags to make cells near heat source alive


MWE: (just algorithm due to complexity)


points = [[0.,0.,0.],[0.,0.5,0.],[0.,1.,0.]]
num_steps = len(points) 
cnt = 0
dt = fem.Constant(domain,0.1)

V = fem.functionspace(domain, ("Lagrange", 1))
facet_tag = get_meshtags_facet()
# to integrate specific boundaries ds(1) or ds(2)
ds = ufl.Measure("ds", domain=domain, subdomain_data=facet_tag)
# solution function
u_next = dolfinx.fem.Function(V)
u_next.name = "u_next"
# initial condition
u_n = dolfinx.fem.Function(V)
u_n.x.array[:] = 20
#Heat source
Heat = dolfinx.fem.Function(V)

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
F = ro * c * u * v * ufl.dx + dt * kappa * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx\
    - (ro * c * u_n + dt * Heat) * v * ufl.dx
boundary_conditions = [ BoundaryCondition("Robin"    , 1, (s, H_c)),  # our domain
                        BoundaryCondition("Dirichlet", 3, s) , #  # bottom of plate
                        BoundaryCondition("Robin"    , 5, (s, H_c))]  # base platform
bcs = []

for condition in boundary_conditions:
    if condition.type == "Dirichlet":
        bcs.append(condition.bc)
    else:
        F += condition.bc

bilinear_form = fem.form(ufl.lhs(F))
linear_form = fem.form(ufl.rhs(F))

A = assemble_matrix(bilinear_form, bcs)
A.assemble()

B = create_vector(linear_form)
ksp = PETSc.KSP().create(domain.comm)
ksp.setOperators(A)
ksp.setType(PETSc.KSP.Type.PREONLY)  
ksp.getPC().setType(PETSc.PC.Type.LU)  
ksp.getPC().setFactorSolverType("mumps")  

while cnt < num_steps:
    if cnt < len(points):
        current_point = point_at(cnt)
        Heat.interpolate(lambda x: HeatSource(current_point, x))
        # update alive cells
        DACells(current_point,HeatSource.size) 
        # kill cells outside visited area
        u_n.x.array[DACells.dead_tags] = 0
        u_next.x.array[DACells.dead_tags] = 0
    # Update the right hand side reusing the initial vector
    with B.localForm() as loc_b:
        loc_b.set(0)
    assemble_vector(B, linear_form)

    # Apply Dirichlet boundary condition to the vector
    apply_lifting(B, [bilinear_form], [bcs])
    B.ghostUpdate(
        addv=PETSc.InsertMode.ADD_VALUES,
        mode=PETSc.ScatterMode.REVERSE)
    set_bc(B, bcs)

    # Solve linear problem
    ksp.solve(B, u_next.x.petsc_vec)
    # to update values in different ranks
    if cnt < len(points):
        u_next.x.array[DACells.dead_tags] = 0
    u_next.x.scatter_forward()
    # Update solution at previous time step (u_n)
    u_n.x.array[:] = u_next.x.array
    cnt += 1

And thats how i update meshtags

num_cells = domain.topology.index_map(domain.topology.dim).size_local
midpoints = mesh.compute_midpoints(domain, domain.topology.dim, np.arange(num_cells, dtype=np.int32))
tags = np.zeros(len(domain.geometry.x),dtype = int)
def __call__(self, point,sizes):
    def in_cube(x):
        return np.array((((abs(x.T[0] - point[0]) <= sizes[0] / 2) 
                        & (abs(x.T[1] - point[1]) <= sizes[1] / 2) 
                        & (abs(x.T[2] - point[2]) <= 0.003 / 2))), dtype=np.int32)
    cell_tags = dolfinx.mesh.meshtags(domain, domain.topology.dim
        , np.arange(num_cells),in_cube(midpoints) )
    temp = dolfinx.fem.locate_dofs_topological(V,3, cell_tags.find(1))
    tags[temp] = 1
def dead_tags(self):
    return np.logical_not(self.tags)


My question is how can i add time dependent boundary condition for facets of alive cells only?
How can i make sure that dead cells won’t affect my computational area? Because now i can’t replicate results of other models due to strong heat losses. Are there any other suggestions to better approach for dead-alive elements method in dolfinx?

That’s quite a challenging problem. It would help if you could create a truly minimal example (i.e., a 2D dummy PDE with the same concept), so people can help you more targetedly.

Are you trying to effectively remove the ‘dead’ cells from the entire computation? That is indeed quite tricky. You could look into creating submeshes, but I imagine doing that each timestep is too costly for your usecase.

Alternatively, you could create a masking function in a DG0 space, that is 1 or zero depending on whether cells are alive. Each timestep, you’d have to re-specify the masking function. That could be done by assembling 1 on all your identified meshtags. This would enable you to operate on a single mesh, and you wouldn’t have to re-create your forms. However, once assembled the matrix would be singular, so you’d manually have to contrain/remove all dofs that have no support in the alive domain.

If I misunderstood your goal, and instead all you want to do is perform integrals on only parts of the domain boundary, then I’d again introduce a DG0 masking function mask. The integrals would look like \int mask\, f\, v \, d\Omega for the forcing or \int mask\, u\, v\, dS for the Robin condition.

As Stein said, if this changes for every time-step using submesh i probably not wise.

I would use a “time-dependent” DirichletBC to deactivate the degrees of freedom that are within deactivated cells (and not on the boundary between dead and alive cells.
In turn, I would use manual integration entities to specify where to use ds integrals.

However, as Stein said, i would love to see a minimal example here, of what you want to achieve, before i embark on coding up a “complete” example.

1 Like

Thanks for your answers, my goal is to assign Robin boundary condition for alive cells facets.
Here is 2D MWE with 2 passes and i want to achieve similar heat conduction for each of them.
I will try to use custom measures. I would assume there is a way to keep only interior facets between dead and alive cells and add boundary facets of alive cells to obtain boundary facets of alive cells in some bounding box. If it sounds confusing, i am happy to answer your questions.

import numpy as np
import gmsh
from dolfinx import mesh,fem
import ufl
import pyvista
import matplotlib as mpl
import matplotlib.pyplot as plt
import dolfinx.fem.petsc
from mpi4py import MPI
from petsc4py import PETSc
from dolfinx.fem.petsc import assemble_vector, assemble_matrix, create_vector, apply_lifting, set_bc
class MovingHeatSource:
    """ Goldak's Heat source for 2D """
    def __init__(self, Q=0.8*185*20):
        self.a=0.1
        self.cf=0.15
        self.cr=0.2
        self.ff = 0.6
        self.fr = 1.4 
        self.Q = Q
        self.r_min = 1e-6  # Small offset to prevent singularity

        assert self.ff + self.fr == 2, "ff and fr must sum to 2"
    def __call__(self, point1,point2, x):
        rel_x = x[0] - point1[0]
        rel_y = x[1] - point1[1]

        # Determine which ellipsoid to use (front or rear)
        front_mask = rel_y >= 0
        rear_mask = ~front_mask  # Opposite of front_mask
        heat_flux = np.zeros_like(rel_x)

        q_front = (6 * np.sqrt(3) * self.ff * self.Q) / (np.pi * self.a  * self.cf * np.sqrt(np.pi))
        r_front = (rel_x[front_mask] / self.a) ** 2 + \
                            (rel_y[front_mask] / self.cf) ** 2 + self.r_min
        heat_flux[front_mask] += q_front * np.exp(-3 * r_front)

        q_rear = (6 * np.sqrt(3) * self.fr * self.Q) / (np.pi * self.a  * self.cr * np.sqrt(np.pi))
        r_rear = np.sqrt((rel_x[rear_mask] / self.a) ** 2 + 
                         (rel_y[rear_mask] / self.cr) ** 2) + self.r_min
        heat_flux[rear_mask]+=q_rear * np.exp(-3 * r_rear**2)
        
        rel_x = x[0] - point2[0]
        rel_y = x[1] - point2[1]

        # Determine which ellipsoid to use (front or rear)
        front_mask = rel_y >= 0
        rear_mask = ~front_mask  # Opposite of front_mask

        q_front = (6 * np.sqrt(3) * self.ff * self.Q) / (np.pi * self.a  * self.cf * np.sqrt(np.pi))
        r_front = (rel_x[front_mask] / self.a) ** 2 + \
                            (rel_y[front_mask] / self.cf) ** 2 + self.r_min
        heat_flux[front_mask] += q_front * np.exp(-3 * r_front)

        q_rear = (6 * np.sqrt(3) * self.fr * self.Q) / (np.pi * self.a  * self.cr * np.sqrt(np.pi))
        r_rear = np.sqrt((rel_x[rear_mask] / self.a) ** 2 + 
                         (rel_y[rear_mask] / self.cr) ** 2) + self.r_min
        heat_flux[rear_mask]+=q_rear * np.exp(-3 * r_rear**2)
        return heat_flux
HeatSource = MovingHeatSource()
domain = mesh.create_rectangle(
    comm=MPI.COMM_WORLD,
    points=((0.0, 0.0), (2.0, 1.0)),
    n=(320, 160),
    cell_type=mesh.CellType.triangle,
)

num_cells = domain.topology.index_map(domain.topology.dim).size_local
midpoints = mesh.compute_midpoints(domain, domain.topology.dim, np.arange(num_cells, dtype=np.int32))
tags = np.zeros(len(domain.geometry.x),dtype = int)
def update_dead_cells(point):
    def in_cube(x):
        return np.array((((abs(x.T[0] - point[0]) <= 0.2 / 2) 
                        & (abs(x.T[1] - point[1]) <= 0.2 / 2))), dtype=np.int32)
    cell_tags = dolfinx.mesh.meshtags(domain, domain.topology.dim
        , np.arange(num_cells),in_cube(midpoints) )
    temp = dolfinx.fem.locate_dofs_topological(V,2, cell_tags.find(1))
    tags[temp] = 1
def dead_tags(tags):
    return np.logical_not(tags)

points = [[0.1,0.],[0.1,0.2],[0.1,0.4],[0.1,0.6],[0.1,0.8],[0.1,1.],[0.5,0.],[0.5,0.2],[0.5,0.4],[0.5,0.6],[0.5,0.8],[0.5,1.]]
num_steps = len(points) +5
cnt = 0
dt = fem.Constant(domain,0.1)
V = fem.functionspace(domain, ("Lagrange", 1))
# solution function
u_next = dolfinx.fem.Function(V)
u_next.name = "u_next"
# initial condition
u_n = dolfinx.fem.Function(V)
u_n.x.array[:] = 26
#Heat source
Heat = dolfinx.fem.Function(V)

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
ro = fem.Constant(domain, dolfinx.default_scalar_type(7.250e-3))
c = fem.Constant(domain, dolfinx.default_scalar_type(0.571))
kappa = fem.Constant(domain, dolfinx.default_scalar_type(0.0261))
        
F = ro * c * u * v * ufl.dx + dt * kappa * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx\
    - (ro * c * u_n + dt * Heat) * v * ufl.dx
    
class BoundaryCondition():
    def __init__(self, type, marker, values):
        self._type = type
        if type == "Dirichlet":
            facets = mesh.locate_entities_boundary(
                        domain,
                        dim=(domain.topology.dim - 1),
                        marker=lambda x:np.isclose(x[1], 0) | np.isclose(x[0], 2.0))
            dofs = fem.locate_dofs_topological(V=V, entity_dim=1, entities=facets)
            self._bc = dolfinx.fem.dirichletbc(values, dofs, V)
        elif type == "Neumann":
            self._bc = ufl.inner(values, v) * ufl.ds
        elif type == "Robin":
            self._bc = (values[1] * \
                ufl.inner(u - values[0], v)) * ufl.ds
        else:
            raise TypeError(
                "Unknown boundary condition: {0:s}".format(type))

    @property
    def bc(self):
        return self._bc

    @property
    def type(self):
        return self._type
r = dolfinx.fem.Constant(domain, dolfinx.default_scalar_type(.018))
s = dolfinx.fem.Constant(domain, dolfinx.default_scalar_type(26))
    
boundary_conditions = [ BoundaryCondition("Robin"    , 0, (s,r))]  # base platform
bcs = []

for condition in boundary_conditions:
    if condition.type == "Dirichlet":
        bcs.append(condition.bc)
    else:
        F += condition.bc

bilinear_form = fem.form(ufl.lhs(F))
linear_form = fem.form(ufl.rhs(F))

A = assemble_matrix(bilinear_form, bcs)
A.assemble()

B = create_vector(linear_form)
ksp = PETSc.KSP().create(domain.comm)
ksp.setOperators(A)
ksp.setType(PETSc.KSP.Type.PREONLY)  
ksp.getPC().setType(PETSc.PC.Type.LU)  
ksp.getPC().setFactorSolverType("mumps")  
grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(V))

plotter = pyvista.Plotter()
plotter.open_gif("u_time_diff.gif")
plotter.view_xy()
plotter.camera_position= [(0.5745519626987698, 0.49990690578879415, 2.143588810000001),
                            (0.5745519626987698, 0.49990690578879415, 0.0),
                            (0.0, 1.0, 0.0)]
grid.point_data["Temperature[*C]"] = u_next.x.array
grid.set_active_scalars("Temperature[*C]")
viridis = mpl.colormaps.get_cmap("viridis").resampled(25)
sargs = dict(title_font_size=25, label_font_size=20, fmt="%.2e", color="black",
             position_x=0.1, position_y=0.8, width=0.8, height=0.1)
Heat.interpolate(lambda x: HeatSource(points[0],points[6], x))
renderer = plotter.add_mesh(grid, show_edges=True, lighting=False,
                            cmap=viridis, scalar_bar_args=sargs, clim = [0,max(Heat.x.array)])
while cnt < num_steps:
    if cnt < len(points)/2:
        current_point = points[cnt]
        Heat.interpolate(lambda x: HeatSource(current_point,points[cnt+6], x))
        update_dead_cells(current_point)
        update_dead_cells(points[cnt+6]) 
         
        # kill cells outside visited area
        u_n.x.array[dead_tags(tags)] = 0
        u_next.x.array[dead_tags(tags)] = 0
    else: Heat.x.array[:] = 0
    # Update the right hand side reusing the initial vector
    with B.localForm() as loc_b:
        loc_b.set(0)
    assemble_vector(B, linear_form)

    # Apply Dirichlet boundary condition to the vector
    apply_lifting(B, [bilinear_form], [bcs])
    B.ghostUpdate(
        addv=PETSc.InsertMode.ADD_VALUES,
        mode=PETSc.ScatterMode.REVERSE)
    set_bc(B, bcs)

    # Solve linear problem
    ksp.solve(B, u_next.x.petsc_vec)
    u_next.x.array[dead_tags(tags)] = 0
    # to update values in different ranks
    u_next.x.scatter_forward()
    # Update solution at previous time step (u_n)
    u_n.x.array[:] = u_next.x.array
    cnt += 1
    plotter.update_scalars(u_next.x.array, render=False)
    plotter.write_frame()  
    print(cnt)
plotter.close()

u_time_diff

Main difference is the shape of first pass close to left edge. However, my main concern is that this approach doesn’t show heat accumulation in the base plate so i assumed it’s because of my dead cells with 0 value. Here is comparison with model performed on MSC. MARC (2021, MSC software,
Irvine, CA, USA)
My :


MARC:

Thank You!

I have an implementation of dead-alive elements that relies on multiphenicsx.DofMapRestriction to deactivate the DOFs of dead elements. For the time-dependent boundary condition you have to keep track of the boundary of the active cells. So in this C++ snippet I loop over all the facets, check which ones belong to a single active cell and mark it as a boundary facet:

template <std::floating_point T>                                                                                                                                                                                                                                                
std::vector<int> locate_active_boundary(const dolfinx::mesh::Mesh<T> &domain,                                                                                                                                                                                                   
                                  const dolfinx::fem::Function<T> &active_els_func) {                                                                                                                                                                                           
  std::vector<int> bfacets_indices;                                                                                                                                                                                                                                             
  // Values of function                                                                                                                                                                                                                                                         
  std::span<const T> func_vals = active_els_func.x()->array();                                                                                                                                                                                                                  
  auto topology = domain.topology();                                                                                                                                                                                                                                            
  int cdim = topology->dim();                                                                                                                                                                                                                                                   
  domain.topology_mutable()->create_connectivity(cdim-1,cdim);                                                                                                                                                                                                                  
  auto con_facet_cell = topology->connectivity(cdim-1,cdim);                                                                                                                                                                                                                    
  assert(con_facet_cell);                                                                                                                                                                                                                                                       
  auto facet_map = topology->index_map(cdim-1);                                                                                                                                                                                                                                 
  std::int32_t num_facets = facet_map->size_local();                                                                                                                                                                                                                            
  la::Vector<int> bfacet_marker = la::Vector<int>(facet_map, 1);                                                                                                                                                                                                                
  auto bfacet_marker_vals = bfacet_marker.mutable_array();                                                                                                                                                                                                                      
  float num_incident_active_els;                                                                                                                                                                                                                                                
  int num_bfacets = 0;                                                                                                                                                                                                                                                          
  for (int ifacet = 0; ifacet < num_facets; ++ifacet) {                                                                                                                                                                                                                         
    num_incident_active_els = 0.0;                                                                                                                                                                                                                                              
    auto local_con = con_facet_cell->links(ifacet);                                                                                                                                                                                                                             
    for (auto &el : local_con) {                                                                                                                                                                                                                                                
      num_incident_active_els += func_vals[el];                                                                                                                                                                                                                                 
    }                                                                                                                                                                                                                                                                           
    if (std::abs(num_incident_active_els - 1.0) < 1e-7) {                                                                                                                                                                                                                       
      bfacet_marker_vals[ifacet] = 1;                                                                                                                                                                                                                                           
      ++num_bfacets;                                                                                                                                                                                                                                                            
    }                                                                                                                                                                                                                                                                           
  }                                                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                                                                
  bfacet_marker.scatter_fwd();                                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                                
  bfacets_indices.reserve(num_bfacets);                                                                                                                                                                                                                                         
                                                                                                                                                                                                                                                                                
  for (int i = 0; i < bfacet_marker_vals.size(); ++i) {                                                                                                                                                                                                                         
    if (bfacet_marker_vals[i]==1)                                                                                                                                                                                                                                               
      bfacets_indices.push_back(i);                                                                                                                                                                                                                                             
  }                                                                                                                                                                                                                                                                             
  return bfacets_indices;                                                                                                                                                                                                                                                       
}   

You can certainly do something similar in Python and numba.

For the boundary condition itself, you should use fem.compile_form and fem.create_form for efficiency. To specify the subdomain data of the form, you need to pass the facets in the format [..., owner_cell_of_facet_i, local_index_of_facet_i, ...]. You could rely on fem.compute_integration_domains and discard the cells that are inactive or write a search like this one in c++ :

template <std::floating_point T>                                                                                                                                                                                                                                                
std::vector<int32_t> get_facet_integration_entities(const dolfinx::mesh::Mesh<T> &domain,                                                                                                                                                                                       
    std::span<const std::int32_t> facets,                                                                                                                                                                                                                                       
    const dolfinx::fem::Function<T> &active_els_func)                                                                                                                                                                                                                           
{                                                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                
  std::span<const T> active_els_vals = active_els_func.x()->array();                                                                                                                                                                                                            
  int cdim = domain.topology()->dim();                                                                                                                                                                                                                                          
  auto con_facet_cell = domain.topology()->connectivity(cdim-1,cdim);                                                                                                                                                                                                           
  auto con_cell_facet = domain.topology()->connectivity(cdim,cdim-1);                                                                                                                                                                                                           
  size_t num_local_cells = domain.topology()->index_map(cdim)->size_local();                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                
  std::vector<int32_t> integration_entities;                                                                                                                                                                                                                                    
  for (const int &ifacet : facets) {                                                                                                                                                                                                                                            
    // Find local active cell that owns the facet                                                                                                                                                                                                                               
    auto incident_cells = con_facet_cell->links(ifacet);                                                                                                                                                                                                                        
    int owner_el = -1;                                                                                                                                                                                                                                                          
    for (auto &ielem : incident_cells) {                                                                                                                                                                                                                                        
      if ((active_els_vals[ielem]) and (ielem < num_local_cells)) {                                                                                                                                                                                                             
        owner_el = ielem;                                                                                                                                                                                                                                                       
        break;                                                                                                                                                                                                                                                                  
      }                                                                                                                                                                                                                                                                         
    }                                                                                                                                                                                                                                                                           
    if (owner_el<=-1)                                                                                                                                                                                                                                                           
      continue;                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                
    //Get local index of facet                                                                                                                                                                                                                                                  
    auto incident_facets = con_cell_facet->links(owner_el);                                                                                                                                                                                                                     
    int local_index = -1;                                                                                                                                                                                                                                                       
    for (int i = 0; i < incident_facets.size(); ++i) {                                                                                                                                                                                                                          
      if (ifacet==incident_facets[i]) {                                                                                                                                                                                                                                         
        local_index = i;                                                                                                                                                                                                                                                        
        break;                                                                                                                                                                                                                                                                  
      }                                                                                                                                                                                                                                                                         
    }                                                                                                                                                                                                                                                                           
    assert(local_index>-1);                                                                                                                                                                                                                                                     
    integration_entities.push_back(owner_el);                                                                                                                                                                                                                                   
    integration_entities.push_back(local_index);                                                                                                                                                                                                                                
  }                                                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                                                                
  return integration_entities;                                                                                                                                                                                                                                                  
}         

The workflow for the form compilation/instantiation might look like this:

tag = 1
a_ufl = convection_coeff * u * v * ufl.ds(tag) # where u, v are the trial and test functions over `function_space`
a_compiled = fem.compile_form(domain.comm, a_ufl,
                              form_compiler_options={"scalar_type": np.float64})                                                                                                   
form_subdomain_data = {fem.IntegralType.exterior_facet : [(tag, [..., owner_cell_facet_i, local_index_facet_i, ...])]}                                                                                                                                                          
a_instance = fem.create_form(a_compiled,                                                                                                                                                                                                                                        
                             [function_space, function_space],                                                                                                                                                                                                                  
                             msh=domain,                                                                                                                                                                                                                                        
                             subdomains=form_subdomain_data,                                                                                                                                                                                                                    
                             coefficient_map={coeff:coeff for coeff in a_ufl.coefficients()}                                                                                                                                                                                    
                             constant_map={const:const for const in a_ufl.constants()})                

You should do something like this in order to avoid recompiling your form each time your active cells change.

As you said i managed to mark exterior facets, however i might get it wrong cause after ksp.solve(B, u_next.x.petsc_vec) on first iteration i get nan values if i try to visualize
Here is my attempt:

import numpy as np
from dolfinx import mesh,fem
import ufl
import pyvista
import matplotlib as mpl
import matplotlib.pyplot as plt
import dolfinx.fem.petsc
from mpi4py import MPI
from petsc4py import PETSc
from dolfinx.fem.petsc import assemble_vector, assemble_matrix, create_vector, apply_lifting, set_bc

class MovingHeatSource:
    """ Goldak's Heat source for 2D """
    def __init__(self, Q=0.8*185*20):
        self.a=0.1
        self.cf=0.15
        self.cr=0.2
        self.ff = 0.6
        self.fr = 1.4 
        self.Q = Q
        self.r_min = 1e-6  # Small offset to prevent singularity

        assert self.ff + self.fr == 2, "ff and fr must sum to 2"
    def __call__(self, point1,point2, x):
        rel_x = x[0] - point1[0]
        rel_y = x[1] - point1[1]

        # Determine which ellipsoid to use (front or rear)
        front_mask = rel_y >= 0
        rear_mask = ~front_mask  # Opposite of front_mask
        heat_flux = np.zeros_like(rel_x)

        q_front = (6 * np.sqrt(3) * self.ff * self.Q) / (np.pi * self.a  * self.cf * np.sqrt(np.pi))
        r_front = (rel_x[front_mask] / self.a) ** 2 + \
                            (rel_y[front_mask] / self.cf) ** 2 + self.r_min
        heat_flux[front_mask] += q_front * np.exp(-3 * r_front)

        q_rear = (6 * np.sqrt(3) * self.fr * self.Q) / (np.pi * self.a  * self.cr * np.sqrt(np.pi))
        r_rear = np.sqrt((rel_x[rear_mask] / self.a) ** 2 + 
                         (rel_y[rear_mask] / self.cr) ** 2) + self.r_min
        heat_flux[rear_mask]+=q_rear * np.exp(-3 * r_rear**2)
        
        rel_x = x[0] - point2[0]
        rel_y = x[1] - point2[1]

        # Determine which ellipsoid to use (front or rear)
        front_mask = rel_y >= 0
        rear_mask = ~front_mask  # Opposite of front_mask

        q_front = (6 * np.sqrt(3) * self.ff * self.Q) / (np.pi * self.a  * self.cf * np.sqrt(np.pi))
        r_front = (rel_x[front_mask] / self.a) ** 2 + \
                            (rel_y[front_mask] / self.cf) ** 2 + self.r_min
        heat_flux[front_mask] += q_front * np.exp(-3 * r_front)

        q_rear = (6 * np.sqrt(3) * self.fr * self.Q) / (np.pi * self.a  * self.cr * np.sqrt(np.pi))
        r_rear = np.sqrt((rel_x[rear_mask] / self.a) ** 2 + 
                         (rel_y[rear_mask] / self.cr) ** 2) + self.r_min
        heat_flux[rear_mask]+=q_rear * np.exp(-3 * r_rear**2)
        return heat_flux

HeatSource = MovingHeatSource()
domain = mesh.create_rectangle(
    comm=MPI.COMM_WORLD,
    points=((0.0, 0.0), (2.0, 1.0)),
    n=(320, 160),
    cell_type=mesh.CellType.triangle,
)
tdim = domain.topology.dim
num_cells = domain.topology.index_map(domain.topology.dim).size_local + domain.topology.index_map(2).num_ghosts
cell_map = domain.topology.index_map(tdim)
cell_indices = np.arange(0, num_cells)
cell_values = np.zeros_like(cell_indices, dtype=np.intc)
    
midpoints_c = mesh.compute_midpoints(domain, domain.topology.dim, cell_indices)
ctags = np.zeros(num_cells,dtype=int) # to update alive cells

domain.topology.create_entities(tdim - 1)
facet_map = domain.topology.index_map(tdim - 1)
num_facets = facet_map.size_local + facet_map.num_ghosts
midpoints_f = mesh.compute_midpoints(domain, domain.topology.dim-1, np.arange(num_facets, dtype=np.int32))
ftags = np.zeros(num_facets,dtype=int) # to update alive exterior facets

def update_dead_cells(point):
    def in_cube(x):
        return np.array((((abs(x.T[0] - point[0]) <= 0.2 / 2) 
                        & (abs(x.T[1] - point[1]) <= 0.2 / 2))), dtype=np.int32)
    cell_tags = dolfinx.mesh.meshtags(domain, domain.topology.dim
        , np.arange(0,num_cells),in_cube(midpoints_c) )
    ctags[cell_tags.find(1)] = 1
   
    cell_domains = [
            (domain_id, cell_indices[(ctags == domain_id) & (cell_indices < cell_map.size_local)])
            for domain_id in [1, 0]
        ]
    return cell_tags,cell_domains

ext_facet_domain = []
ftags_func = np.zeros(len(domain.geometry.x),dtype = int)

def mark_facets_from_cells(ct):
    marked_facets = set()
    for c in ct.find(1):
        # Get facets of each cell
        cell_facets = domain.topology.connectivity(domain.topology.dim, domain.topology.dim - 1).links(c)
        for f in cell_facets:
            # Get neighboring cells for facet
            facet_cells = domain.topology.connectivity(domain.topology.dim - 1, domain.topology.dim).links(f)
            # If facet is on boundary of tagged region (1 cell in region, 1 not)
            if any(ct.values[i] == 0 for i in facet_cells):
                marked_facets.add(f)
    facet_ids = np.array(list(marked_facets), dtype=np.int32)
    facet_tags = dolfinx.mesh.meshtags(domain, domain.topology.dim - 1, facet_ids, np.ones(len(facet_ids), dtype=np.int32))
    ftags[facet_tags.find(1)] = 1
    temp = dolfinx.fem.locate_dofs_topological(V,1, facet_tags.find(1))
    ftags_func[temp] = 1
    domain.topology.create_connectivity(tdim, tdim - 1)
    domain.topology.create_connectivity(tdim - 1, tdim)
    c_to_f = domain.topology.connectivity(tdim, tdim - 1)
    f_to_c = domain.topology.connectivity(tdim - 1, tdim)
    for f in facet_tags.find(1):
        if f < facet_map.size_local:
            c = f_to_c.links(f)[0]
            local_f = np.where(c_to_f.links(c) == f)[0][0]
            ext_facet_domain.append(c)
            ext_facet_domain.append(local_f)
    ext_facet_domains = [(1, ext_facet_domain)]

    return facet_tags,ext_facet_domains

points = [[0.1,0.0],[0.1,0.2],[0.1,0.4],[0.1,0.6],[0.1,0.8],[0.1,1.],[.5,0.],[0.5,0.2],[0.5,0.4],[0.5,0.6],[0.5,0.8],[0.5,1.]]
num_steps = len(points) +5
cnt = 0
dt = fem.Constant(domain,0.1)
V = fem.functionspace(domain, ("Lagrange", 1))
# solution function
u_next = dolfinx.fem.Function(V)
u_next.name = "u_next"
# initial condition
u_n = dolfinx.fem.Function(V)
u_n.x.array[:] = 26
#Heat source
Heat = dolfinx.fem.Function(V)
f = fem.Constant(domain, 0.)
       
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
ro = fem.Constant(domain, dolfinx.default_scalar_type(7.250e-3))
c = fem.Constant(domain, dolfinx.default_scalar_type(0.571))
kappa = fem.Constant(domain, dolfinx.default_scalar_type(0.0261))

#Activate cells for first points
cell_tags,cell_domains = update_dead_cells(points[0])
_,ext_facet_domains = mark_facets_from_cells(cell_tags) 
cell_tags,cell_domains = update_dead_cells(points[cnt+6])
_,ext_facet_domains = mark_facets_from_cells(cell_tags)


# Create measures
dx_mt = ufl.Measure("dx", subdomain_data=cell_domains, domain=domain)
ds_mt = ufl.Measure("ds", subdomain_data=ext_facet_domains, domain=domain)

# F = ro * c * u * v * dx_mt(1) + dt * kappa * ufl.dot(ufl.grad(u), ufl.grad(v)) * dx_mt(1)\
#     - (ro * c * u_n + dt * Heat) * v * dx_mt(1)
a = ro * c * u * v * dx_mt(1) + dt * kappa * ufl.dot(ufl.grad(u), ufl.grad(v)) * dx_mt(1)
L = (ro * c * u_n + dt * Heat) * v * dx_mt(1)

class BoundaryCondition():
    def __init__(self, type, marker, values):
        self._type = type
        if type == "Dirichlet":
                dofs = np.where(ftags_func == 1)[0]
                self._bc = dolfinx.fem.dirichletbc(values, dofs, V)
        elif type == "Neumann":
            self._bc = ufl.inner(values, v) * ufl.ds
        elif type == "Robin":
            self._bc = [(values[1] * \
                ufl.inner(u , v)) * ds_mt(marker),(values[1] * \
                ufl.inner(values[0], v)) * ds_mt(marker)]
        else:
            raise TypeError(
                "Unknown boundary condition: {0:s}".format(type))

    @property
    def bc(self):
        return self._bc

    @property
    def type(self):
        return self._type
r = dolfinx.fem.Constant(domain, dolfinx.default_scalar_type(.018))
s = dolfinx.fem.Constant(domain, dolfinx.default_scalar_type(26))
    
boundary_conditions = [ BoundaryCondition("Robin"    , 1, (s,r))]  # base platform
bcs = []
# boundary_conditions =[]
for condition in boundary_conditions:
    if condition.type == "Dirichlet":
        bcs.append(condition.bc)
    else:
        a+=condition.bc[0]
        L+=condition.bc[1]

bilinear_form = fem.form(a)
linear_form = fem.form(L)

A = assemble_matrix(bilinear_form, bcs)
A.assemble()

B = create_vector(linear_form)
ksp = PETSc.KSP().create(domain.comm)
ksp.setOperators(A)
ksp.setType(PETSc.KSP.Type.PREONLY)  
ksp.getPC().setType(PETSc.PC.Type.LU)  
ksp.getPC().setFactorSolverType("mumps")  

while cnt < num_steps:
    if cnt < len(points)/2:
        current_point = points[cnt]
        Heat.interpolate(lambda x: HeatSource(current_point,points[cnt+6], x))
    else: Heat.x.array[:] = 0
    # Update the right hand side reusing the initial vector
    with B.localForm() as loc_b:
        loc_b.set(0)
    assemble_vector(B, linear_form)

    # Apply Dirichlet boundary condition to the vector
    apply_lifting(B, [bilinear_form], [bcs])
    B.ghostUpdate(
        addv=PETSc.InsertMode.ADD_VALUES,
        mode=PETSc.ScatterMode.REVERSE)
    set_bc(B, bcs)

    # Solve linear problem
    ksp.solve(B, u_next.x.petsc_vec)
    
    u_next.x.scatter_forward()
    # Update solution at previous time step (u_n)
    u_n.x.array[:] = u_next.x.array
    cnt += 1
    # plotter.update_scalars(u_next.x.array, render=False)
    # plotter.write_frame()  
    print(cnt)
    cell_tags,cell_domains = update_dead_cells(points[0])
    _,ext_facet_domains = mark_facets_from_cells(cell_tags)
    cell_tags,cell_domains = update_dead_cells(points[cnt+6])
    _,ext_facet_domains = mark_facets_from_cells(cell_tags)

Maybe i will switch to @ordinary-slim method because now i keep boundary facets of previous iterations


And is it fine that actual boundary facets are not marked yellow as in pictures?

You are right. This solved issue with boundary facets that i made. It seems like integration over time dependent ds works. But i get empty list when trying to do time-dependent dx assembly

A = fem.assemble_matrix(bilinear_form)
print(A.data)

Which is not empty for ufl.dx. However, i get same results as in
def test_manual_integration_domains(): from github of

cell_domains = [
    (domain_id, cell_indices[(cell_values == domain_id) & (cell_indices < cell_map.size_local)])
    for domain_id in [7, 0]
]

Additionaly when i run this function i get assertion error of different sizes

Traceback (most recent call last):
  File "/home/arshidin/Documents/Fenicsx/test.py", line 122, in <module>
    assert np.allclose(A.data, A_mt.data)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arshidin/miniconda3/envs/fenicsx-env/lib/python3.12/site-packages/numpy/core/numeric.py", line 2241, in allclose
    res = all(isclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arshidin/miniconda3/envs/fenicsx-env/lib/python3.12/site-packages/numpy/core/numeric.py", line 2351, in isclose
    return within_tol(x, y, atol, rtol)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arshidin/miniconda3/envs/fenicsx-env/lib/python3.12/site-packages/numpy/core/numeric.py", line 2332, in within_tol
    return less_equal(abs(x-y), atol + rtol * abs(y))
                          ~^~
ValueError: operands could not be broadcast together with shapes (139,) (181,) 

The easiest way to use the latest dolfinx is through docker. You could first check that the method works for you by recompiling the form at every timestep with the right subdomain information. Then you can worry about efficiency afterwards.

Make sure you are looking at the version of that function that came with release v0.8.0 (test_manual_integration_domains v0.8.0).

u_time_diff

After updating my dolfinx version to 0.9.0 i got working ds integral but it required putting assembly and form creation inside loop, otherwise my ds wasn’t updating. This code part seems required to be in loop to make it work.

        ds_mt = ufl.Measure("ds", subdomain_data=ext_facet_domains, domain=domain)
        a = ro * c * u * v * ufl.dx + dt * kappa * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx
        L = (ro * c * u_n + dt * Heat) * v * ufl.dx
        boundary_conditions = [ BoundaryCondition("Robin"    , 1, (s,r))]  # base platform
        bcs = []
        for condition in boundary_conditions:
            if condition.type == "Dirichlet":
                bcs.append(condition.bc)
            else:
                a+=condition.bc[0]
                L+=condition.bc[1]

        bilinear_form = fem.form(a)
        linear_form = fem.form(L)

            
        A = assemble_matrix(bilinear_form, bcs)
        A.assemble()
        B = create_vector(linear_form)
        # ksp = PETSc.KSP().create(domain.comm)
        ksp.setOperators(A)

But i still have problems with cells integration on subdomain, this code gives same results

default_assembly = dolfinx.fem.assemble_scalar(dolfinx.fem.form(1 * ufl.dx(domain)))
new_assembly = dolfinx.fem.assemble_scalar(dolfinx.fem.form(1 * dx_mt))

but when i sum dx_mt(i) i get slight different results 1.9999999999984626 1.9999999999984945.
And even with no bcs and interpolate of heat values i get such results

So, i stick to u_next.x.array[dead_tags(ctags_func)] = 0 approach for now.

I couldn’t try fem.create_form due to type issue

Traceback (most recent call last):
  File "/workspace/Documents/Fenicsx/summary.py", line 367, in <module>
    a_instance = fem.create_form(a_compiled,                                                                                                                                                                                                                                        
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/dolfinx-real/lib/python3.12/dist-packages/dolfinx/fem/forms.py", line 504, in create_form
    f = ftype(
        ^^^^^^
TypeError: create_form_float64(): incompatible function arguments. The following argument types are supported:
    1. create_form_float64(form: int, spaces: collections.abc.Sequence[dolfinx.cpp.fem.FunctionSpace_float64], coefficients: collections.abc.Sequence[dolfinx.cpp.fem.Function_float64], constants: collections.abc.Sequence[dolfinx.cpp.fem.Constant_float64], subdomains: collections.abc.Mapping[dolfinx.cpp.fem.IntegralType, collections.abc.Sequence[tuple[int, ndarray[dtype=int32, order='C', writable=False]]]], mesh: dolfinx.cpp.mesh.Mesh_float64) -> dolfinx.cpp.fem.Form_float64
    2. create_form_float64(form: int, spaces: collections.abc.Sequence[dolfinx.cpp.fem.FunctionSpace_float64], coefficients: collections.abc.Mapping[str, dolfinx.cpp.fem.Function_float64], constants: collections.abc.Mapping[str, dolfinx.cpp.fem.Constant_float64], subdomains: collections.abc.Mapping[dolfinx.cpp.fem.IntegralType, collections.abc.Sequence[tuple[int, ndarray[dtype=int32, order='C', writable=False]]]], entity_maps: collections.abc.Mapping[dolfinx.cpp.mesh.Mesh_float64, ndarray[dtype=int32, order='C', writable=False]], mesh: dolfinx.cpp.mesh.Mesh_float64) -> dolfinx.cpp.fem.Form_float64

Invoked with types: _CDataBase, list, dict, dict, dict, dict, dolfinx.cpp.mesh.Mesh_float64

Here is output of interior_entities

[np.int32(46240), 0, np.int32(46240), 2, np.int32(46561), 0, np.int32(46560), 2, np.int32(46882), 0, np.int32(46880), 2, np.int32(47203), 0, np.int32(47200), 2, np.int32(47524), 0, np.int32(47520), 2, np.int32(47845), 0, np.int32(47840), 2, np.int32(48166), 0, np.int32(48160), 2, np.int32(48487), 0, np.int32(48480), 2, np.int32(48808), .......... , 0, np.int32(81084), 2, np.int32(81085), 0, np.int32(81375), 2, np.int32(81375), 0] 
a_ufl = u * v * ds_mt(1) # where u, v are the trial and test functions over `function_space`
a_compiled = fem.compile_form(domain.comm, a_ufl,
                              form_compiler_options={"scalar_type": np.float64})                                                                                                   
form_subdomain_data = {fem.IntegralType.exterior_facet : [(tag,interior_entities)]}                                                                                                                                                          
a_instance = fem.create_form(a_compiled,                                                                                                                                                                                                                                        
                             [V, V],                                                                                                                                                                                                                  
                             mesh=domain,                                                                                                                                                                                                                                        
                             subdomains=form_subdomain_data,                                                                                                                                                                                                                    
                             coefficient_map={coeff:coeff for coeff in a_ufl.coefficients()},                                                                                                                                                                                    
                             constant_map={const:const for const in a_ufl.constants()})

Also i have found my mistake in testing fuctions, it was this line(from 7,0 to 0,7):
302 for domain_id in [0, 7]

Hi,

I added Robin bc and simple cells activation to the “Diffusion of a Gaussian function” tutorial.

In the current code, the diffusion is computed across all cells, and the Robin boundary conditions are applied to outer facets of the entire domain.

What we want instead is to compute the diffusion only in the active cells, and apply Robin BCs only to external facets of the active region, at each time step.

What would be the most efficient way to implement this?

import ufl
import numpy as np
from mpi4py import MPI
from dolfinx import fem, mesh, io, default_scalar_type
from dolfinx.fem.petsc import LinearProblem

# Define temporal parameters
t = 0  # Start time
T = 1.0  # Final time
num_steps = 50
dt = T / num_steps  # time step size


nx, ny = 50, 50
domain = mesh.create_rectangle(
    MPI.COMM_WORLD,
    [np.array([-2, -2]), np.array([2, 2])],
    [nx, ny],
    mesh.CellType.quadrilateral,
)

V = fem.functionspace(domain, ("Lagrange", 1))
Q = fem.functionspace(domain, ("DG", 0))


def initial_condition(x, a=5):
    return np.exp(-a * (x[0] ** 2 + x[1] ** 2))


u_n = fem.Function(V)
u_n.name = "u_n"
u_n.interpolate(initial_condition)

uh = fem.Function(V)
uh.name = "uh"
uh.interpolate(initial_condition)

activation_position_x = fem.Constant(domain, default_scalar_type(0.0))


def active_cells_function(x):
    return x[0] <= activation_position_x.value + 1e-6


active_cells = fem.Function(Q)
active_cells.interpolate(active_cells_function)


xdmf = io.XDMFFile(domain.comm, "diffusion_gaussian_function_dead_alive.xdmf", "w")
xdmf.write_mesh(domain)
xdmf.write_function(uh, t)
xdmf.write_function(active_cells, t)

u, v = ufl.TrialFunction(V), ufl.TestFunction(V)
f = fem.Constant(domain, default_scalar_type(0.0))

F = (
    u * v * ufl.dx
    + dt * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx
    - (u_n + dt * f) * v * ufl.dx
)

F += dt * 0.001 * ufl.inner(u - 0, v) * ufl.ds


a, L = ufl.system(F)

a_compiled = fem.form(a)
L_compiled = fem.form(L)

problem = LinearProblem(
    a_compiled,
    L_compiled,
    u=uh,
    bcs=[],
    petsc_options={"ksp_type": "preonly", "pc_type": "lu"},
)

for i in range(num_steps):
    t += dt
    activation_position_x.value = i * 4 / 50
    active_cells.interpolate(active_cells_function)
    problem.solve()
    u_n.x.array[:] = uh.x.array
    xdmf.write_function(uh, t)
    xdmf.write_function(active_cells, t)

xdmf.close()

1 Like

Here is a method that covers the case where there is an inactive interface (it wouldn’t work for t=0.5, but should be quite easy to modify to make it work:

"""
Deactivate parts of the domain during assembly and let it update over time

Author: Jørgen S. Dokken
SPDX license identifier: MIT
"""

from mpi4py import MPI
from petsc4py import PETSc
import ufl

import dolfinx.fem.petsc
import numpy as np

mesh = dolfinx.mesh.create_unit_square(
    MPI.COMM_WORLD, 25, 25, ghost_mode=dolfinx.mesh.GhostMode.shared_facet
)

V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1))
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
x = ufl.SpatialCoordinate(mesh)


dx = ufl.Measure("dx", domain=mesh)
ds = ufl.Measure("ds", domain=mesh)
active_tag = 1
neumann_tag = 2


a = ufl.inner(ufl.grad(u), ufl.grad(v)) * dx(active_tag)
L = ufl.inner(10 * x[0] * ufl.sin(x[1]), v) * ds(neumann_tag)


compiled_a = dolfinx.fem.compile_form(mesh.comm, a)
compiled_L = dolfinx.fem.compile_form(mesh.comm, L)


cell_map = mesh.topology.index_map(mesh.topology.dim)
num_cells_local = cell_map.size_local + cell_map.num_ghosts

# Create sparsity pattern for whole domain
a_full = dolfinx.fem.create_form(
    compiled_a,
    [V, V],
    mesh,
    {
        dolfinx.fem.IntegralType.cell: [
            (active_tag, np.arange(num_cells_local, dtype=np.int32))
        ]
    },
    {},
    {},
    {},
)
pattern = dolfinx.fem.create_sparsity_pattern(a_full)
pattern.finalize()


def compute_active_cells(t):
    local_active = dolfinx.mesh.locate_entities(
        mesh, mesh.topology.dim, lambda x: x[0] < t
    )
    comm_vec = dolfinx.la.vector(cell_map, dtype=np.int32)
    comm_vec.array[:] = 0
    comm_vec.array[local_active] = 1
    comm_vec.scatter_reverse(dolfinx.la.InsertMode.add)
    comm_vec.scatter_forward()
    return np.flatnonzero(comm_vec.array).astype(np.int32)


def find_inactive_dofs(active_cells):
    num_dofs_local = V.dofmap.index_map.size_local
    num_dofs_ghost = V.dofmap.index_map.num_ghosts
    active_dofs = V.dofmap.list[active_cells].flatten()
    inactive_dofs_marker = np.ones(num_dofs_local + num_dofs_ghost, dtype=np.int32)
    inactive_dofs_marker[active_dofs] = 0
    return np.flatnonzero(inactive_dofs_marker).astype(np.int32)


def compute_active_interface_integration_entities(active_cells):
    # Find inactive cells to find interface
    inactive_cell_marker = np.ones(num_cells_local, dtype=np.bool_)
    inactive_cell_marker[active_cells] = 0
    inactive_cells = np.flatnonzero(inactive_cell_marker)
    active_cell_marker = np.invert(inactive_cell_marker)

    mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
    active_facets = dolfinx.mesh.compute_incident_entities(
        mesh.topology, active_cells, mesh.topology.dim, mesh.topology.dim - 1
    )
    inactive_facets = dolfinx.mesh.compute_incident_entities(
        mesh.topology, inactive_cells, mesh.topology.dim, mesh.topology.dim - 1
    )
    interface_facets = np.intersect1d(active_facets, inactive_facets)
    # Filter out ghost facets
    num_facets_local = mesh.topology.index_map(mesh.topology.dim - 1).size_local
    interface_facets = interface_facets[interface_facets < num_facets_local]

    integration_entities = dolfinx.fem.compute_integration_domains(
        dolfinx.fem.IntegralType.interior_facet,
        mesh.topology,
        interface_facets,
        mesh.topology.dim - 1,
    ).reshape(-1, 4)

    active_facet_integral = np.full((len(interface_facets), 2), -1, dtype=np.int32)
    first_cell_marker = active_cell_marker[integration_entities[:, 0]]
    second_cell_marker = active_cell_marker[integration_entities[:, 2]]
    active_facet_integral[first_cell_marker] = integration_entities[
        first_cell_marker, 0:2
    ]
    active_facet_integral[second_cell_marker] = integration_entities[
        second_cell_marker, 2:
    ]

    assert np.all(active_facet_integral >= 0)
    return active_facet_integral


uh = dolfinx.fem.Function(V)
vtx = dolfinx.io.VTXWriter(mesh.comm, "uh.bp", [uh], engine="BP5")
for t in [0.1, 0.5, 0.9]:
    active_cells = compute_active_cells(t)
    inactive_dofs = find_inactive_dofs(active_cells)
    active_facets = compute_active_interface_integration_entities(active_cells)

    bc_inactive = dolfinx.fem.dirichletbc(0.0, inactive_dofs, V)
    dofs_left = dolfinx.fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], 0.0))
    bc = dolfinx.fem.dirichletbc(1.0, dofs_left, V)
    bcs = [bc, bc_inactive]

    local_active_cells = active_cells[active_cells < cell_map.size_local]
    subdomains = {
        dolfinx.fem.IntegralType.cell: [(active_tag, local_active_cells)],
        dolfinx.fem.IntegralType.exterior_facet: [(neumann_tag, active_facets)],
    }

    specific_a = dolfinx.fem.create_form(
        compiled_a, [V, V], mesh, subdomains, {}, {}, {}
    )

    specific_L = dolfinx.fem.create_form(compiled_L, [V], mesh, subdomains, {}, {}, {})

    A = dolfinx.cpp.la.petsc.create_matrix(mesh.comm, pattern)
    A.zeroEntries()
    dolfinx.fem.petsc.assemble_matrix(A, specific_a, bcs=bcs)
    A.assemble()

    b = dolfinx.fem.petsc.assemble_vector(specific_L)
    dolfinx.fem.petsc.apply_lifting(b, [specific_a], [bcs])
    b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
    dolfinx.fem.petsc.set_bc(b, bcs)

    print(b.norm())
    ksp = PETSc.KSP().create(mesh.comm)
    ksp.setType("preonly")
    ksp.getPC().setType("lu")
    ksp.getPC().setFactorSolverType("mumps")
    ksp.setOperators(A)
    ksp.setErrorIfNotConverged(True)
    ksp.solve(b, uh.x.petsc_vec)
    ksp.destroy()
    b.destroy()
    uh.x.scatter_forward()
    vtx.write(t)

vtx.close()
1 Like

I slightly modified the example to also include boundary conditions on the exterior facets. It seems to work, but I’m unsure whether this is the proper approach.

Would it be better to follow the method described in this section of the tutorial, or is the current implementation acceptable?

"""
Deactivate parts of the domain during assembly and let it update over time

Author: Jørgen S. Dokken
SPDX license identifier: MIT
"""

from mpi4py import MPI
from petsc4py import PETSc
import ufl

import dolfinx.fem.petsc
import numpy as np

mesh = dolfinx.mesh.create_unit_square(
    MPI.COMM_WORLD, 25, 25, ghost_mode=dolfinx.mesh.GhostMode.shared_facet
)

V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1))
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
x = ufl.SpatialCoordinate(mesh)


dx = ufl.Measure("dx", domain=mesh)
ds = ufl.Measure("ds", domain=mesh)
active_tag = 1
neumann_tag = 2
neumann_tag_exterior = 3  # <---------------- new


a = ufl.inner(ufl.grad(u), ufl.grad(v)) * dx(active_tag)
L = ufl.inner(10 * x[0] * ufl.sin(x[1]), v) * ds(neumann_tag)
L += ufl.inner(1, v) * ds(neumann_tag_exterior)  # <---------------- new


compiled_a = dolfinx.fem.compile_form(mesh.comm, a)
compiled_L = dolfinx.fem.compile_form(mesh.comm, L)


cell_map = mesh.topology.index_map(mesh.topology.dim)
num_cells_local = cell_map.size_local + cell_map.num_ghosts

# Create sparsity pattern for whole domain
a_full = dolfinx.fem.create_form(
    compiled_a,
    [V, V],
    mesh,
    {
        dolfinx.fem.IntegralType.cell: [
            (active_tag, np.arange(num_cells_local, dtype=np.int32))
        ]
    },
    {},
    {},
    {},
)
pattern = dolfinx.fem.create_sparsity_pattern(a_full)
pattern.finalize()


def compute_exterior_facets_integration_entities():  # <---------------- new function
    boundary_facets = dolfinx.mesh.locate_entities_boundary(
        mesh,
        mesh.topology.dim - 1,
        lambda x: np.logical_or.reduce(
            [
                np.isclose(x[1], 0.0),
                np.isclose(x[1], 1.0),
                np.isclose(x[0], 1.0),
            ]
        ),
    )
    num_facets_local = mesh.topology.index_map(mesh.topology.dim - 1).size_local
    boundary_facets = boundary_facets[boundary_facets < num_facets_local]
    exterior_facets = dolfinx.fem.compute_integration_domains(
        dolfinx.fem.IntegralType.exterior_facet,
        mesh.topology,
        boundary_facets,
        mesh.topology.dim - 1,
    )
    return exterior_facets


exterior_facets = compute_exterior_facets_integration_entities()  # <---------------- new


def compute_active_cells(t):
    local_active = dolfinx.mesh.locate_entities(
        mesh, mesh.topology.dim, lambda x: x[0] < t
    )
    comm_vec = dolfinx.la.vector(cell_map, dtype=np.int32)
    comm_vec.array[:] = 0
    comm_vec.array[local_active] = 1
    comm_vec.scatter_reverse(dolfinx.la.InsertMode.add)
    comm_vec.scatter_forward()
    return np.flatnonzero(comm_vec.array).astype(np.int32)


def find_inactive_dofs(active_cells):
    num_dofs_local = V.dofmap.index_map.size_local
    num_dofs_ghost = V.dofmap.index_map.num_ghosts
    active_dofs = V.dofmap.list[active_cells].flatten()
    inactive_dofs_marker = np.ones(num_dofs_local + num_dofs_ghost, dtype=np.int32)
    inactive_dofs_marker[active_dofs] = 0
    return np.flatnonzero(inactive_dofs_marker).astype(np.int32)


def compute_active_interface_integration_entities(active_cells):
    # Find inactive cells to find interface
    inactive_cell_marker = np.ones(num_cells_local, dtype=np.bool_)
    inactive_cell_marker[active_cells] = 0
    inactive_cells = np.flatnonzero(inactive_cell_marker)
    active_cell_marker = np.invert(inactive_cell_marker)

    mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
    active_facets = dolfinx.mesh.compute_incident_entities(
        mesh.topology, active_cells, mesh.topology.dim, mesh.topology.dim - 1
    )
    inactive_facets = dolfinx.mesh.compute_incident_entities(
        mesh.topology, inactive_cells, mesh.topology.dim, mesh.topology.dim - 1
    )
    interface_facets = np.intersect1d(active_facets, inactive_facets)
    # Filter out ghost facets
    num_facets_local = mesh.topology.index_map(mesh.topology.dim - 1).size_local
    interface_facets = interface_facets[interface_facets < num_facets_local]

    integration_entities = dolfinx.fem.compute_integration_domains(
        dolfinx.fem.IntegralType.interior_facet,
        mesh.topology,
        interface_facets,
        mesh.topology.dim - 1,
    ).reshape(-1, 4)

    active_facet_integral = np.full((len(interface_facets), 2), -1, dtype=np.int32)
    first_cell_marker = active_cell_marker[integration_entities[:, 0]]
    second_cell_marker = active_cell_marker[integration_entities[:, 2]]
    active_facet_integral[first_cell_marker] = integration_entities[
        first_cell_marker, 0:2
    ]
    active_facet_integral[second_cell_marker] = integration_entities[
        second_cell_marker, 2:
    ]

    assert np.all(active_facet_integral >= 0)
    return active_facet_integral


uh = dolfinx.fem.Function(V)
vtx = dolfinx.io.VTXWriter(mesh.comm, "uh.bp", [uh], engine="BP5")
for t in [0.1, 0.5, 0.9, 1.1]:  # <---------------- new step: 1.1
    active_cells = compute_active_cells(t)
    inactive_dofs = find_inactive_dofs(active_cells)
    active_facets = compute_active_interface_integration_entities(active_cells)

    bc_inactive = dolfinx.fem.dirichletbc(0.0, inactive_dofs, V)
    dofs_left = dolfinx.fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], 0.0))
    bc = dolfinx.fem.dirichletbc(1.0, dofs_left, V)
    bcs = [bc, bc_inactive]

    local_active_cells = active_cells[active_cells < cell_map.size_local]
    subdomains = {
        dolfinx.fem.IntegralType.cell: [(active_tag, local_active_cells)],
        dolfinx.fem.IntegralType.exterior_facet: [
            (neumann_tag, active_facets),
            (neumann_tag_exterior, exterior_facets),  # <---------------- new
        ],
    }

    specific_a = dolfinx.fem.create_form(
        compiled_a, [V, V], mesh, subdomains, {}, {}, {}
    )

    specific_L = dolfinx.fem.create_form(compiled_L, [V], mesh, subdomains, {}, {}, {})

    A = dolfinx.cpp.la.petsc.create_matrix(mesh.comm, pattern)
    A.zeroEntries()
    dolfinx.fem.petsc.assemble_matrix(A, specific_a, bcs=bcs)
    A.assemble()

    b = dolfinx.fem.petsc.assemble_vector(specific_L)
    dolfinx.fem.petsc.apply_lifting(b, [specific_a], [bcs])
    b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
    dolfinx.fem.petsc.set_bc(b, bcs)

    print(b.norm())
    ksp = PETSc.KSP().create(mesh.comm)
    ksp.setType("preonly")
    ksp.getPC().setType("lu")
    ksp.getPC().setFactorSolverType("mumps")
    ksp.setOperators(A)
    ksp.setErrorIfNotConverged(True)
    ksp.solve(b, uh.x.petsc_vec)
    ksp.destroy()
    b.destroy()
    uh.x.scatter_forward()
    vtx.write(t)

vtx.close()

It looks like we need to use dolfinx.fem.form(...) for meshtags to work automatically. From what I understand, meshtags don’t work with dolfinx.fem.compile_form(...) and dolfinx.fem.create_form(...) unless we manually extract the integration domains as (cell, local facet) pairs. So, explicitly passing the subdomains to create_form(...), including for exterior facets, seems to be the right approach.