Circular Symmetry Boundary Condition on a sector of circle

Hi Everyone, I have a question in regards to application of circular periodic boundary condition on the sector of a circle for a plane strain case.

In the following problem definition shown below, I apply a constant force on top edge and periodic boundary condition on side edges. In order to obtain a finite element solution for linear elastic analysis, are described boundary conditions enough or do I need to constrain by applying dirichlet boundary condition some where else also?

As I tried this problem in fenicsx and I am getting large displacements as if the there is no constraint applied. Here is my code.

Point(1) = {0, 0, 0, 0.001};
//+
Point(2) = {0.2, 0.5, 0, 0.001};
//+
Point(3) = {-0.2, 0.5, 0, 0.001};
//+
Line(1) = {1, 2};
//+
Line(2) = {2, 3};
//+
Line(3) = {3, 1};
//+
Curve Loop(1) = {2, 3, 1};
//+
Plane Surface(1) = {1};
//+
Physical Curve("right", 1) = {1};
//+
Physical Curve("top", 2) = {2};
//+
Physical Curve("left", 3) = {3};
//+
Physical Surface("my_surface", 4) = {1};
mesh_path = "finalb.msh"
domain, cell_markers, facet_markers = gmshio.read_from_msh(mesh_path, MPI.COMM_WORLD, gdim=2)

import gmsh
import os
import numpy as np
import matplotlib.pyplot as plt
import sys
from mpi4py import MPI
from petsc4py import PETSc
from dolfinx.cpp.mesh import to_type, cell_entity_type
from dolfinx import mesh, fem, io, common, default_scalar_type
import dolfinx.fem.petsc
from dolfinx.io import VTXWriter, distribute_entity_data, gmshio
from dolfinx.mesh import create_mesh, meshtags_from_entities, locate_entities
from dolfinx.plot import vtk_mesh
from dolfinx.io import XDMFFile
from ufl import (FacetNormal, FiniteElement, as_matrix, Identity, Measure, TestFunction, TrialFunction, VectorElement, as_vector, div, dot, ds, dx, inner, lhs, grad, nabla_grad, rhs, sym)
from dolfinx_mpc.utils import (create_point_to_point_constraint, determine_closest_block, rigid_motions_nullspace)
from dolfinx_mpc import LinearProblem, MultiPointConstraint

gdim = 2
import ufl

def strain(u, repr ="vectorial"):
    eps_t = sym(grad(u))
    if repr =="vectorial":
        return as_vector([eps_t[0,0], eps_t[1,1], 2*eps_t[0,1]])
    elif repr =="tensorial":
        return eps_t

E = fem.Constant(domain, 200000000000.0)
nu = fem.Constant(domain,0.3)

lmbda = E*nu/(1+nu)/(1-2*nu)
mu = E/2/(1+nu)

C = as_matrix([[lmbda+2*mu, lmbda, 0.0],[lmbda, lmbda+2*mu,  0.0],[0.0, 0.0, mu]])

def stress(u, repr ="vectorial"):
    sigv = dot(C, strain(u))
    if repr =="vectorial":
        return sigv
    elif repr =="tensorial":
        return as_matrix([[sigv[0], sigv[2]], [sigv[2], sigv[1]]])

# Define Function Space
degree = 2
V = fem.functionspace(domain, ("P",degree, (gdim,)))

#Cyclic Symmetry Condition
def periodic_relation(x):
    out_x = np.zeros(x.shape)
    out_x[0] = x[1]*(-0.2/0.5)
    out_x[1] = x[1]
    return out_x


#Define Variational Problem
du = TrialFunction(V)
u_ = TestFunction(V)
u = fem.Function(V, name = "Displacement")
a_form = inner(stress(du),strain(u_))*dx

# Applying a uniform pressure

T = fem.Constant(domain, 1000000.0)

#Self-weight on the surface
n = FacetNormal(domain)

ds = ufl.Measure("ds", domain=domain, subdomain_data=facet_markers)
L_form = dot(T*n,u_) * ds(2)

bcs = []

with common.Timer("~~Periodic: Compute mpc condition"):
    mpc = MultiPointConstraint(V)
    mpc.create_periodic_constraint_topological(V.sub(0), facet_markers, 1, periodic_relation, bcs, default_scalar_type(1))
    mpc.finalize()
                     

WPsolve = LinearProblem(a_form, L_form, mpc, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
uh = WPsolve.solve()
uh.name = "u"

In the problem you are describing, the geometry is free to both rotate and translate arbitrarily. You should remove rigid motion null-spaces.

Hi Jorgen, I applied the rigid motion null spaces as you suggested however, I am encountering an error. Here is the mesh.

// Gmsh project created on Thu Aug 29 16:35:16 2024
//+
Point(1) = {0, 0, 0, 0.001};
//+
Point(2) = {0.2, 0.5, 0, 0.001};
//+
Point(3) = {-0.2, 0.5, 0, 0.001};
//+
Line(1) = {1, 2};
//+
Line(2) = {2, 3};
//+
Line(3) = {3, 1};
//+
Curve Loop(1) = {2, 3, 1};
//+
Plane Surface(1) = {1};
//+
Physical Curve("right", 1) = {1};
//+
Physical Curve("top", 2) = {2};
//+
Physical Curve("left", 3) = {3};
//+
Physical Surface("my_surface", 4) = {1};

Here is the MWE

import gmsh
import os
import numpy as np
import matplotlib.pyplot as plt
import sys
from mpi4py import MPI
from petsc4py import PETSc
from dolfinx.cpp.mesh import to_type, cell_entity_type
import dolfinx.cpp as _cpp
from dolfinx import mesh, fem, io, common, default_scalar_type, la
import dolfinx.fem.petsc
from dolfinx.io import VTXWriter, distribute_entity_data, gmshio
from dolfinx.mesh import create_mesh, meshtags_from_entities, locate_entities
from dolfinx.plot import vtk_mesh
from dolfinx.io import XDMFFile
from ufl import (FacetNormal, FiniteElement, as_matrix, Identity, Measure, TestFunction, TrialFunction, VectorElement, as_vector, div, dot, ds, dx, inner, lhs, grad, nabla_grad, rhs, sym)
from dolfinx_mpc.utils import (create_point_to_point_constraint, determine_closest_block, rigid_motions_nullspace)
from dolfinx_mpc import LinearProblem, MultiPointConstraint

# Mesh
mesh_path = "finalb.msh"
domain, cell_markers, facet_markers = gmshio.read_from_msh(mesh_path, MPI.COMM_WORLD, gdim=2)

gdim = 2
import ufl

def strain(u, repr ="vectorial"):
    eps_t = sym(grad(u))
    if repr =="vectorial":
        return as_vector([eps_t[0,0], eps_t[1,1], 2*eps_t[0,1]])
    elif repr =="tensorial":
        return eps_t

E = fem.Constant(domain, 200000000000.0)
nu = fem.Constant(domain,0.3)

lmbda = E*nu/(1+nu)/(1-2*nu)
mu = E/2/(1+nu)

C = as_matrix([[lmbda+2*mu, lmbda, 0.0],[lmbda, lmbda+2*mu,  0.0],[0.0, 0.0, mu]])

def stress(u, repr ="vectorial"):
    sigv = dot(C, strain(u))
    if repr =="vectorial":
        return sigv
    elif repr =="tensorial":
        return as_matrix([[sigv[0], sigv[2]], [sigv[2], sigv[1]]])



def rigid_motions_nullspace(V:fem.FunctionSpace):
    """
    Function to build nullspace for 2D/3D elasticity.

    Args:
        V: The function space
    """
    _x = fem.Function(V)

    # Set dimension of nullspace
    dim = gdim + 1

    # Create list of vectors for null space
    nullspace_basis = [
        la.vector(V.dofmap.index_map, bs=V.dofmap.index_map_bs, dtype=PETSc.ScalarType)  # type: ignore
        for i in range(dim)
    ]

    basis = [b.array for b in nullspace_basis]
    dofs = [V.sub(i).dofmap.list.reshape(-1) for i in range(gdim)]

    # Build translational null space basis
    for i in range(gdim):
        basis[i][dofs[i]] = 1.0
    # Build rotational null space basis
    x = V.tabulate_dof_coordinates()
    dofs_block = V.dofmap.list.reshape(-1)
    x0, x1, x2 = x[dofs_block, 0], x[dofs_block, 1], x[dofs_block, 2]
    if gdim == 2:
        basis[2][dofs[0]] = -x1
        basis[2][dofs[1]] = x0
        
    for b in nullspace_basis:
        b.scatter_forward()

    la.orthonormalize(nullspace_basis)
    assert la.is_orthonormal(nullspace_basis, np.finfo(_x.x.array.dtype).eps)
    local_size = V.dofmap.index_map.size_local * V.dofmap.index_map_bs
    basis_petsc = [
        PETSc.Vec().createWithArray(x[:local_size], bsize=gdim, comm=V.mesh.comm)  # type: ignore
        for x in basis
    ]
    return PETSc.NullSpace().create(comm=V.mesh.comm, vectors=basis_petsc)  # type: ignore


# Define Function Space
degree = 2
V = fem.functionspace(domain, ("P",degree, (gdim,)))

#Cyclic Symmetry Condition
def periodic_relation(x):
    out_x = np.zeros(x.shape)
    out_x[0] = x[1]*(-0.2/0.5)
    #out_x[0] = -1*x[0]
    out_x[1] = x[1]
    return out_x


#Define Variational Problem
du = TrialFunction(V)
u_ = TestFunction(V)
u = fem.Function(V, name = "Displacement")
a_form = inner(stress(du),strain(u_))*dx

T = fem.Constant(domain, 1000000.0)

n = FacetNormal(domain)

ds = ufl.Measure("ds", domain=domain, subdomain_data=facet_markers)
L_form = dot(T*n,u_) * ds(2)


bcs = []


with common.Timer("~~Periodic: Compute mpc condition"):
    mpc = MultiPointConstraint(V)
    mpc.create_periodic_constraint_topological(V.sub(0), facet_markers, 1, periodic_relation, bcs, default_scalar_type(1))
    mpc.finalize()


null_space = rigid_motions_nullspace(mpc.function_space)

ksp_rtol = 5e2 * np.finfo(default_scalar_type).resolution
petsc_options = {
    "ksp_rtol": ksp_rtol,
    "pc_type": "gamg",
    "pc_gamg_type": "agg",
    "pc_gamg_coarse_eq_limit": 1000,
    "pc_gamg_sym_graph": True,
    "mg_levels_ksp_type": "chebyshev",
    "mg_levels_pc_type": "jacobi",
    "mg_levels_esteig_ksp_type": "cg",
    "matptap_via": "scalable",
    "pc_gamg_square_graph": 2,
    "pc_gamg_threshold": 0.02,
    # ,"help": None, "ksp_view": None
}

WPsolve = LinearProblem(a_form, L_form, mpc, bcs=bcs, petsc_options=petsc_options)
WPsolve.A.setNearNullSpace(null_space)
uh = WPsolve.solve()
uh.name = "u"

Here is the error message:

AttributeError                            Traceback (most recent call last)
Cell In[13], line 179
    175     mpc.create_periodic_constraint_topological(V.sub(0), facet_markers, 1, periodic_relation, bcs, default_scalar_type(1))
    176     mpc.finalize()
--> 179 null_space = rigid_motions_nullspace(mpc.function_space)
    181 ksp_rtol = 5e2 * np.finfo(default_scalar_type).resolution
    182 petsc_options = {
    183     "ksp_rtol": ksp_rtol,
    184     "pc_type": "gamg",
   (...)
    194     # ,"help": None, "ksp_view": None
    195 }

Cell In[13], line 63, in rigid_motions_nullspace(V)
     60 for b in nullspace_basis:
     61     b.scatter_forward()
---> 63 la.orthonormalize(nullspace_basis)
     64 assert la.is_orthonormal(nullspace_basis, np.finfo(_x.x.array.dtype).eps)
     66 #_basis = [x._cpp_object for x in basis]
     67 #dolfinx.cpp.la.orthonormalize(_basis)
     68 #assert dolfinx.cpp.la.is_orthonormal(_basis)

File ~/anaconda3/envs/bluemira-dolmpc/lib/python3.11/site-packages/dolfinx/la.py:290, in orthonormalize(basis)
    288     alpha = x.dot(y)
    289     x.axpy(-alpha, y)
--> 290 x.normalize()

AttributeError: 'Vector' object has no attribute 'normalize'

Without specifying what version of DOLFINx, DOLFINx_mpc etc you are running, I cannot help you further.

Hi Jorgen, my DOLFINX version is 0.7.3 whereas DOLFINx-mpc version is 0.7.2.

Please see DOLFINx_MPC demos for the relevant version, As I believe this is due to you using the v0.8.x or main branch demos as a starting point: dolfinx_mpc/python/dolfinx_mpc/utils/mpc_utils.py at v0.7.2 · jorgensd/dolfinx_mpc · GitHub
i.e. using the nullspace function form v0.7.2 works fine:

def rigid_motions_nullspace(V: fem.FunctionSpaceBase):
    """
    Function to build nullspace for 2D/3D elasticity.

    Args:
        V: The function space
    """
    _x = fem.Function(V)
    # Get geometric dim
    gdim = V.mesh.geometry.dim
    assert gdim == 2 or gdim == 3

    # Set dimension of nullspace
    dim = 3 if gdim == 2 else 6

    # Create list of vectors for null space
    nullspace_basis = [_x.vector.copy() for _ in range(dim)]
    from contextlib import ExitStack

    with ExitStack() as stack:
        vec_local = [stack.enter_context(x.localForm()) for x in nullspace_basis]
        basis = [np.asarray(x) for x in vec_local]

        dofs = [V.sub(i).dofmap.list.reshape(-1) for i in range(gdim)]

        # Build translational null space basis
        for i in range(gdim):
            basis[i][dofs[i]] = 1.0
        # Build rotational null space basis
        x = V.tabulate_dof_coordinates()
        dofs_block = V.dofmap.list.reshape(-1)
        x0, x1, x2 = x[dofs_block, 0], x[dofs_block, 1], x[dofs_block, 2]
        if gdim == 2:
            basis[2][dofs[0]] = -x1
            basis[2][dofs[1]] = x0
        elif gdim == 3:
            basis[3][dofs[0]] = -x1
            basis[3][dofs[1]] = x0

            basis[4][dofs[0]] = x2
            basis[4][dofs[2]] = -x0
            basis[5][dofs[2]] = x1
            basis[5][dofs[1]] = -x2

    la.orthonormalize(nullspace_basis)
    assert la.is_orthonormal(
        nullspace_basis, float(500 * np.finfo(_x.x.array.dtype).resolution)
    )
    return PETSc.NullSpace().create(vectors=nullspace_basis)  # type: ignore

If I then add null_space.remove(uh.vector)
after solving the problem, I get an almost sensible solution.
The current issue is that you are mapping the bottom-most dof to itself.
What should the boundary condition be at that node?

You could do something along the lines of:

import gmsh
import os
import numpy as np
import matplotlib.pyplot as plt
import sys
from mpi4py import MPI
from petsc4py import PETSc
from dolfinx.cpp.mesh import to_type, cell_entity_type
import dolfinx.cpp as _cpp
from dolfinx import mesh, fem, io, common, default_scalar_type, la
import dolfinx.fem.petsc
from dolfinx.io import VTXWriter, distribute_entity_data, gmshio
from dolfinx.mesh import create_mesh, meshtags_from_entities, locate_entities
from dolfinx.plot import vtk_mesh
from dolfinx.io import XDMFFile
from ufl import (
    FacetNormal,
    FiniteElement,
    as_matrix,
    Identity,
    Measure,
    TestFunction,
    TrialFunction,
    VectorElement,
    as_vector,
    div,
    dot,
    ds,
    dx,
    inner,
    lhs,
    grad,
    nabla_grad,
    rhs,
    sym,
)
from dolfinx_mpc.utils import (
    create_point_to_point_constraint,
    determine_closest_block,
    rigid_motions_nullspace,
)
from dolfinx_mpc import LinearProblem, MultiPointConstraint

# Mesh
mesh_path = "finalb.msh"
domain, cell_markers, facet_markers = gmshio.read_from_msh(
    mesh_path, MPI.COMM_WORLD, gdim=2
)

gdim = 2
import ufl


def strain(u, repr="vectorial"):
    eps_t = sym(grad(u))
    if repr == "vectorial":
        return as_vector([eps_t[0, 0], eps_t[1, 1], 2 * eps_t[0, 1]])
    elif repr == "tensorial":
        return eps_t


E = fem.Constant(domain, 200000000000.0)
nu = fem.Constant(domain, 0.3)

lmbda = E * nu / (1 + nu) / (1 - 2 * nu)
mu = E / 2 / (1 + nu)

C = as_matrix(
    [[lmbda + 2 * mu, lmbda, 0.0], [lmbda, lmbda + 2 * mu, 0.0], [0.0, 0.0, mu]]
)


def stress(u, repr="vectorial"):
    sigv = dot(C, strain(u))
    if repr == "vectorial":
        return sigv
    elif repr == "tensorial":
        return as_matrix([[sigv[0], sigv[2]], [sigv[2], sigv[1]]])


def rigid_motions_nullspace(V: fem.FunctionSpaceBase):
    """
    Function to build nullspace for 2D/3D elasticity.

    Args:
        V: The function space
    """
    _x = fem.Function(V)
    # Get geometric dim
    gdim = V.mesh.geometry.dim
    assert gdim == 2 or gdim == 3

    # Set dimension of nullspace
    dim = 3 if gdim == 2 else 6

    # Create list of vectors for null space
    nullspace_basis = [_x.vector.copy() for _ in range(dim)]
    from contextlib import ExitStack

    with ExitStack() as stack:
        vec_local = [stack.enter_context(x.localForm()) for x in nullspace_basis]
        basis = [np.asarray(x) for x in vec_local]

        dofs = [V.sub(i).dofmap.list.reshape(-1) for i in range(gdim)]

        # Build translational null space basis
        for i in range(gdim):
            basis[i][dofs[i]] = 1.0
        # Build rotational null space basis
        x = V.tabulate_dof_coordinates()
        dofs_block = V.dofmap.list.reshape(-1)
        x0, x1, x2 = x[dofs_block, 0], x[dofs_block, 1], x[dofs_block, 2]
        if gdim == 2:
            basis[2][dofs[0]] = -x1
            basis[2][dofs[1]] = x0
        elif gdim == 3:
            basis[3][dofs[0]] = -x1
            basis[3][dofs[1]] = x0

            basis[4][dofs[0]] = x2
            basis[4][dofs[2]] = -x0
            basis[5][dofs[2]] = x1
            basis[5][dofs[1]] = -x2

    la.orthonormalize(nullspace_basis)
    assert la.is_orthonormal(
        nullspace_basis, float(500 * np.finfo(_x.x.array.dtype).resolution)
    )
    return PETSc.NullSpace().create(vectors=nullspace_basis)  # type: ignore


# Define Function Space
degree = 1
V = fem.functionspace(domain, ("P", degree, (gdim,)))


# Cyclic Symmetry Condition
def periodic_relation(x):
    out_x = np.zeros(x.shape)
    out_x[0] = x[1] * (-0.2 / 0.5)
    # out_x[0] = -1*x[0]
    out_x[1] = x[1]

    return out_x


# Define Variational Problem
du = TrialFunction(V)
u_ = TestFunction(V)
a_form = inner(stress(du), strain(u_)) * dx

T = fem.Constant(domain, 1000000.0)

n = FacetNormal(domain)

ds = ufl.Measure("ds", domain=domain, subdomain_data=facet_markers)
L_form = dot(T * n, u_) * ds(2)


bcs = []

# Remove facet-marker that is at the end of the periodic boundary
vertex = dolfinx.mesh.locate_entities_boundary(
    domain, 0, lambda x: np.isclose(x[0], 0) & np.isclose(x[1], 0)
)
V0, V0_to_V = V.sub(0).collapse()
dof = dolfinx.fem.locate_dofs_topological((V.sub(0), V0), 0, vertex)
u_bc = dolfinx.fem.Function(V)
fake_bc = dolfinx.fem.dirichletbc(u_bc, dof, V.sub(0))
# Compute vertices connected to facets

with common.Timer("~~Periodic: Compute mpc condition"):
    mpc = MultiPointConstraint(V)
    mpc.create_periodic_constraint_topological(
        V.sub(0), facet_markers, 1, periodic_relation, [fake_bc], default_scalar_type(1)
    )
    mpc.finalize()
    print(len(mpc.slaves))


null_space = rigid_motions_nullspace(mpc.function_space)

ksp_rtol = 5e2 * np.finfo(default_scalar_type).resolution
petsc_options = {
    "ksp_rtol": ksp_rtol,
    "pc_type": "gamg",
    "pc_gamg_type": "agg",
    "pc_gamg_coarse_eq_limit": 1000,
    "pc_gamg_sym_graph": True,
    "mg_levels_ksp_type": "chebyshev",
    "mg_levels_pc_type": "jacobi",
    "mg_levels_esteig_ksp_type": "cg",
    "matptap_via": "scalable",
    "pc_gamg_square_graph": 2,
    "pc_gamg_threshold": 0.02,
    # ,"help": None, "ksp_view": None
}

uh = fem.Function(mpc.function_space, name="Displacement")

WPsolve = LinearProblem(a_form, L_form, mpc, u=uh, bcs=bcs, petsc_options=petsc_options)
WPsolve.A.setNearNullSpace(null_space)
uh = WPsolve.solve()
uh.name = "u"
null_space.remove(uh.vector)
# Set x-movement at (0,0) to 0
uh.x.array[V0_to_V] -= uh.x.array[dof[0]]

mpc.homogenize(uh)
mpc.backsubstitution(uh)
uh.x.scatter_forward()
with dolfinx.io.VTXWriter(MPI.COMM_WORLD, "debug_Deadlock.bp", [uh]) as bp:
    bp.write(0.0)

which removes the bottom dof from the MPC, and afterwards removes the nullspace and backsubstitutes any relation, as well as setting the x-displacement at (0,0) to 0.
Yields

1 Like