Imposing normal velocity Dirichiet BC

I want to define a Dirichlet BC for velocity on multiple surfaces as velocity with some magnitude A in the direction normal to the surface. I am not sure how to do so.
Specifically, I have some mesh defined only on the connected pore space mesh_background_domain,, and a mesh function markers_facetsSolidsIndividual that labels the solid-fluid interface like below,

I want to acheive something similar to this post, but I have no x_c. y_c defined to compute the normal vector numerically. I tried these two versions below, but neither works nor really makes sense. Could someone point me to the right direction? Really appreciate your help. I am not able to upload the specific mesh and mesh function I used, but I am adding the following code that is simlar to how I generated my mesh.

To generate mesh and mesh function

from dolfin import *
from mshr import *
import matplotlib.pyplot as plt
import numpy as np
# Outer rectangular domain
domain = Rectangle(Point(0, 0), Point(20, 10))

# Define 4 irregular polygons with 6–10 vertices
solid_vertices = [
    [Point(4, 2), Point(5.2, 2.3), Point(5.5, 3.1), Point(5.1, 3.8), Point(4.3, 4.2), Point(3.5, 3.5), Point(3.6, 2.6)],
    [Point(10, 1), Point(10.8, 1.2), Point(11.6, 2.2), Point(11.4, 3.2), Point(10.7, 3.9), Point(9.8, 3.4), Point(9.2, 2.5), Point(9.5, 1.4)],
    [Point(15, 5), Point(15.7, 5.8), Point(16.3, 6.7), Point(15.8, 7.6), Point(15, 8), Point(14.2, 7.5), Point(13.7, 6.8), Point(14.1, 5.7), Point(14.6, 5.2)],
    [Point(6, 6), Point(6.8, 6.2), Point(7.4, 6.6), Point(7.7, 7.3), Point(7.3, 8.1), Point(6.5, 8.3), Point(5.8, 7.9), Point(5.6, 7.2)]
]

# Subtract holes from the domain
for vertices in solid_vertices:
    poly = Polygon(vertices)   # âś… vertices is a list of Points (iterable)
    domain -= poly  
# Generate mesh
mesh = generate_mesh(domain, 64)

class HoleBoundary(SubDomain):
    def __init__(self, points, tol=0.05):
        super().__init__()
        self.edges = []
        self.tol = tol
        for i in range(len(points)):
            p1 = np.array([points[i].x(), points[i].y()])
            p2 = np.array([points[(i+1) % len(points)].x(), points[(i+1) % len(points)].y()])
            self.edges.append((p1, p2))

    def inside(self, x, on_boundary):
        if not on_boundary:
            return False
        px = np.array([x[0], x[1]])
        for p1, p2 in self.edges:
            # Compute distance from point to edge
            v = p2 - p1
            w = px - p1
            c1 = np.dot(w, v)
            if c1 <= 0:
                dist = np.linalg.norm(px - p1)
            else:
                c2 = np.dot(v, v)
                if c2 <= c1:
                    dist = np.linalg.norm(px - p2)
                else:
                    b = c1 / c2
                    pb = p1 + b * v
                    dist = np.linalg.norm(px - pb)
            if dist < self.tol:
                return True
        return False

grain_surface_markers = MeshFunction("size_t", mesh, mesh.topology().dim() - 1, 0)
for i, vertices in enumerate(solid_vertices, start=1):
    subdomain = HoleBoundary(vertices)
    subdomain.mark(grain_surface_markers, i)

source_grains = [3,4]
facet_vals =  np.sort(np.unique(grain_surface_markers.array()))
                                
for val in facet_vals:
    if val in source_grains:
        submesh_coordinates = MeshView.create(grain_surface_markers, val).coordinates()
        plt.scatter(submesh_coordinates[:, 0], submesh_coordinates[:, 1],s= 1,label=val)
    elif val !=1:
        submesh_coordinates = MeshView.create(grain_surface_markers, val).coordinates()
        plt.scatter(submesh_coordinates[:, 0], submesh_coordinates[:, 1],s= 1,c='grey')
plt.gca().set_aspect("equal") 
plt.show()

Version 1: error “UFLException: Integral of type cell cannot contain a ReferenceNormal.”

V = VectorFunctionSpace(mesh_background_domain, 'P', 2)
n = FacetNormal(mesh_background_domain)
g = Constant(1)
u_normal = project(g * n, V)   
u_bc_func = Function(V)     
u_bc.interpolate(u_on_facets)
bc = DirichletBC(V, u_bc, markers_facetsSolidsIndividual,[10, 20])

Version 2: error “RuntimeError: Unable to compile C++ code with dijitso”

n = FacetNormal(mesh_background_domain)
source_profile =  Expression(('(1+t)*n0', '(1+t)*n1'),g=g,n0=n[0], n1=n[1], t=0,degree=2)
bc = DirichletBC(V, source_profile , markers_facetsSolidsIndividual, [10, 20])

This is not possible with legacy DOLFIN’s DirichletBC nor DOLFINx’s dirichletbc due to the complexity of defining a linear constraint relative to the facet normal.

However, there are other techniques. Consider for example “multi-point constraints” (MPCs) as implemented by @dokken in dolfinx_mpc, e.g., dolfinx_mpc/python/demos/demo_stokes.py at 817cd4c385e1c61761e22c6fedc0c6b31094cd2a · jorgensd/dolfinx_mpc · GitHub.

Also consider the flexibility offered by weakly imposing boundary conditions using Nitsche’s method. E.g., mantle-convection/sectant-convection/sectant-convection.py at eb6c9916c2e158e4c629701ec554e253abead56e · nate-sime/mantle-convection · GitHub.

This is equivalent to a controlled flux BC, isn’t it?
Solved at Setting scalar function for BDM boundary condition with controlled flux [mixed Poisson] - #9 by dokken

1 Like

Hi, thank you for the recommendation. I believe I now have a way to 1) create a mesh just on the interface, 2) compute the normal vector on the boundary interface and 3) The normal computed are saved as a vector function defined on the domain of interface mesh norm. Do you know if in the legacy DOLFIN’s DirichletBC, I can use this vector mesh function to prescribe the Dirichlet BC? I tried to project the function norm on the function space defined on the entire domain, hoping maybe I can use that projected function to define DirichletBC. Is this a possible approach? I am getting error “Shapes do not match: and .” when projecting the function I computed by norm_cont = project(norm, V_cg). I am attaching my code below. This follows the previous code. Thank everyone in the community for taking your time to help me

Functions to generate submesh and compute normal vector:

def GenerateSubmesh(markers, domain_number):
    if isinstance(domain_number, list):
        markers_copy = markers
        for i_DN in range(len(domain_number) - 1):
            marked_cells = SubsetIterator(markers, domain_number[i_DN])
            for cell in marked_cells:
                markers_copy[cell] = domain_number[-1]
        submesh = MeshView.create(markers_copy, domain_number[-1])

    else:
        submesh = MeshView.create(markers, domain_number)
    return submesh

def get_facet_normal(interface_mesh, SD_mesh = None, i_outside = -1, j_outside = -1, X_Y_length_dimless__PS = None):
    #Manually calculate FacetNormal function
    vertices = interface_mesh.coordinates()
    cells = interface_mesh.cells()

    dx_dy = vertices[cells[:, 1]] - vertices[cells[:, 0]]

    normals = np.transpose( np.array([ -dx_dy[:, 1], dx_dy[:, 0] ]) )
    normals /= np.sqrt((normals**2).sum(axis=1))[:, np.newaxis]
    
    if SD_mesh == None:
        X_bound = X_Y_length_dimless__PS[0]/2
        Y_bound = X_Y_length_dimless__PS[1]/2
        for i in range(len(cells)):
            if vertices[cells[i, 1]][0] == X_bound and vertices[cells[i, 0]][0] == X_bound:
                if np.dot(np.array([1, 0]), normals[i]) < 0:
                    normals[i] = -normals[i]
            elif vertices[cells[i, 1]][0] == -X_bound and vertices[cells[i, 0]][0] == -X_bound:
                if np.dot(np.array([-1, 0]), normals[i]) < 0:
                    normals[i] = -normals[i]
            elif vertices[cells[i, 1]][1] == Y_bound and vertices[cells[i, 0]][1] == Y_bound:
                if np.dot(np.array([0, 1]), normals[i]) < 0:
                    normals[i] = -normals[i]
            elif vertices[cells[i, 1]][1] == -Y_bound and vertices[cells[i, 0]][1] == -Y_bound:
                if np.dot(np.array([0, -1]), normals[i]) < 0:
                    normals[i] = -normals[i]
    else:
        # Ensure outward pointing normal
        SD_vertices = SD_mesh.coordinates()
        SD_cells = SD_mesh.cells()
        SD_cell_ID = []
        for i in range(len(cells)):
            # Find the cell in the SD mesh
            SD_vertices_IDs = []
            for ii in range(len(cells[i])):
                SD_vertex_ID = np.where( (SD_vertices == vertices[cells[i, ii]]).all(axis=1) )[0]
                assert len(SD_vertex_ID) == 1
                SD_vertices_IDs.append( SD_vertex_ID )
            potential_SD_cell_IDs = np.where( (SD_cells == SD_vertices_IDs[0]).any(axis=1) )[0]
            potential_potential_SD_cell_IDs_IDs = np.where( (SD_cells[potential_SD_cell_IDs] == SD_vertices_IDs[1]).any(axis=1) )[0]
            assert len(potential_potential_SD_cell_IDs_IDs) == 1
            SD_cell_ID.append( potential_SD_cell_IDs[potential_potential_SD_cell_IDs_IDs[0]] )
            # Figure out the location of the third vertex
            cell_vertices = SD_vertices[SD_cells[SD_cell_ID[i]]]
            not_cell_vertices_IDs = np.array([])
            for ii in range(len(cells[i])):
                not_cell_vertices_IDs = np.append( not_cell_vertices_IDs, np.where( (cell_vertices == vertices[cells[i, ii]]).all(axis=1) == False )[0] )
            vertex_ID = np.argmax(np.bincount(not_cell_vertices_IDs.astype(int)))
            if vertex_ID == 0:
                dif = cell_vertices[1] - cell_vertices[vertex_ID]
            else:
                dif = cell_vertices[0] - cell_vertices[vertex_ID]
            dif_normals = np.transpose( np.array([ dif[0], dif[1] ]) )
            dif_normals /= np.sqrt( (dif_normals**2).sum() )
            if np.dot(dif_normals, normals[i]) < 0:
                normals[i] = -normals[i]

    V = VectorFunctionSpace(interface_mesh, 'DG', 0)
    norm = Function(V)
    nv = norm.vector()
    
    for n in (0, 1):
        dofmap = V.sub(n).dofmap()
        for i in range(dofmap.global_dimension()):
            dof_indices = dofmap.cell_dofs(i)
            assert len(dof_indices) == 1
            nv[dof_indices[0]] = normals[i, n]

    
    return norm

Command to generate submesh and compute normal, as well as my attempt to project and define BC.

interface_grain_Mesh = GenerateSubmesh(grain_surface_markers, source_grains)
norm = get_facet_normal(interface_grain_Mesh,SD_mesh=mesh)
plot(norm)

V_cg = FunctionSpace(mesh, 'DG', 2)
norm_cont = project(norm, V_cg)
bc = DirichletBC(V, norm_cont ,grain_surface_markers,source_grains)