Using null space to eliminate translations and rotations in dolfinx

Hello everyone, I am solving an unconstrained thermal expansion problem in a 2D circular domain(The temperature is 1000K). I am using the method from this link to eliminate translations and rotations in the model:https://fenicsproject.discourse.group/t/issues-solving-pure-neumann-problems-in-dolfinx/10468/3?u=french_fries
but the visualization results are not correct. Displacements caused by translations and rotations still exist. How should I proceed?
Here are my code:

from mpi4py import MPI
from dolfinx.fem import form
from dolfinx.fem.petsc import create_vector,assemble_matrix,assemble_vector
from petsc4py import PETSc
from dolfinx.io import gmshio
from dolfinx.fem import (functionspace, Function)
import materials
from ufl import (tr, inner, lhs, rhs, Identity, TestFunction, TrialFunction, dx, grad)

mesh, cell_markers, facet_markers = gmshio.read_from_msh("f1.msh", MPI.COMM_WORLD, gdim=2)
VU = functionspace(mesh, ("CG", 1, (mesh.geometry.dim, )))
uh = Function(VU)
def eps(v):
    return 0.5 * (grad(v) + grad(v).T)
def sigma(v, dT):
    return (materials.lmbda_f * tr(eps(v)) - materials.alpha_f * (3 * materials.lmbda_f + 2 * materials.mu_f) * dT) * Identity(2) + 2.0 * materials.mu_f * eps(v)

dU = TrialFunction(VU)
U_ = TestFunction(VU)
Wint = inner(sigma(dU, 1000), eps(U_)) * dx
aM = lhs(Wint)
LM = rhs(Wint)
# Assemble system
A = assemble_matrix(form(aM))
A.assemble()
b=create_vector(form(LM))
with b.localForm() as b_loc:
            b_loc.set(0)
assemble_vector(b,form(LM))
# Create Krylov solver
solver = PETSc.KSP().create(A.getComm())
solver.setOperators(A)
# Create vector that spans the null space
nullspace = PETSc.NullSpace().create(constant=True,comm=MPI.COMM_WORLD)
A.setNullSpace(nullspace)
nullspace.remove(b)

solver.solve(b,uh.vector)

and geo files:

Point(1) = {0, 0, 0, 1.0};
Point(2) = {0, 7, 0, 1.0};
Point(3) = {0, -7, 0, 1.0};
Circle(1) = {2, 1, 3};
Circle(2) = {3, 1, 2};
Line(3) = {2, 3};
Curve Loop(1) = {2, 3};
Plane Surface(1) = {1};
Curve Loop(2) = {1, -3};
Plane Surface(2) = {2};
Physical Curve("f_s", 4) = {2, 1};
Physical Surface("f", 5) = {1, 2};
Mesh 2;
RecombineMesh;
Mesh.SubdivisionAlgorithm = 1;

The visualization results are shown in the figure:
c3ca93294c47f9f58370f5ba2e234eb

Not sure why you state that this is supposed to eliminate translations and rotations. To me, this seems to eliminate only a specific translation (the one associated with a function equal to 1 on all DOFs of all components), and no rotations.

Sorry, actually I have no idea how to eliminate translations and rotations at all. I just imitated the link mentioned in the previous post. What is the correct approach?

1 Like

See for instance dolfinx_mpc/python/dolfinx_mpc/utils/mpc_utils.py at 4adab42b19003f92dfb682f4de45e6bf9e4a8aa4 · jorgensd/dolfinx_mpc · GitHub

When I called the function, I encountered an error: the Vector object does not have a normalize() method (I am currently using dolfinx-0.8.0).

Traceback (most recent call last):
  File "/home/dyfluid/Desktop/empty/test3.py", line 93, in <module>
    null_space = rigid_motions_nullspace(VU)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dyfluid/Desktop/empty/test3.py", line 59, in rigid_motions_nullspace
    _la.orthonormalize(nullspace_basis)
  File "/home/dyfluid/anaconda3/envs/fenicsx-env/lib/python3.11/site-packages/dolfinx/la.py", line 363, in orthonormalize
    x.normalize()
    ^^^^^^^^^^^
AttributeError: 'Vector' object has no attribute 'normalize'

Please make a reproducible example, possibly copying and pasting bits and pieces of the dolfinx-mpc snippet. You may want to work together with @arash_kazemi who just posted a similar question.

Sorry, this is my code (for eliminating translation and rotation, with the same mesh file as in the current post). I encountered the error mentioned in the previous reply when I ran it. I hope you can help me solve this. Thank you.

from petsc4py import PETSc
import dolfinx.fem as _fem
import dolfinx.la as _la
import numpy as np
from dolfinx.io import gmshio
from mpi4py import MPI
from ufl import (tr, inner, lhs, rhs, Identity, TestFunction, TrialFunction, dx, grad)
from dolfinx.fem.petsc import create_vector,assemble_matrix,assemble_vector
from dolfinx.fem import (functionspace, Function)
from dolfinx.fem import form
from dolfinx import io
def rigid_motions_nullspace(V: _fem.FunctionSpace):
    _x = _fem.Function(V)
    # Get geometric dim
    gdim = V.mesh.geometry.dim
    assert gdim == 2 or gdim == 3
    # Set dimension of nullspace.py
    dim = 3 if gdim == 2 else 6
    # Create list of vectors for null space
    nullspace_basis = [
        _la.vector(V.dofmap.index_map, bs=V.dofmap.index_map_bs, dtype=PETSc.ScalarType)
        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
    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
    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)
        for x in basis
    ]
    return PETSc.NullSpace().create(comm=V.mesh.comm, vectors=basis_petsc)

mesh, cell_markers, facet_markers = gmshio.read_from_msh("f1.msh", MPI.COMM_WORLD, gdim=2)
E = 2e5
nu = 0.26
mu = E/2/(1+nu)
lmbda = E*nu/(1+nu)/(1-2*nu)
alpha = 1e-5

def eps(v):
    return 0.5 * (grad(v) + grad(v).T)
def sigma(v, dT):
    return (lmbda * tr(eps(v)) - alpha * (3 * lmbda + 2 * mu) * dT) * Identity(2) + 2.0 * mu * eps(v)

VU = functionspace(mesh, ("CG", 1, (mesh.geometry.dim, )))
dU = TrialFunction(VU)
U_ = TestFunction(VU)
Wint = inner(sigma(dU, 1000), eps(U_)) * dx
aM = form(lhs(Wint))
LM = form(rhs(Wint))

A = assemble_matrix(aM)
A.assemble()
b = create_vector(LM)
with b.localForm() as b_loc:
            b_loc.set(0)
assemble_vector(b,LM)

# Create Krylov solver
solver = PETSc.KSP().create(A.getComm())
solver.setOperators(A)
null_space = rigid_motions_nullspace(VU)
A.setNullSpace(null_space)
null_space.remove(b)

u = Function(VU)
solver.solve(b,u.vector)

Hi,
I had a similar issue and it solved by using

_basis = [x._cpp_object for x in basis]
    dolfinx.cpp.la.orthonormalize(_basis)

I am using 0.8.0 dolfinx and I follwed the post here.
Currently, I am working on how I can make sure that the null space is correct. What are the options to check the valididy of the null space in fenicsx?

Something along the lines of assert null_space.test(A).

Hi, based on the previous responses, I obtained the results shown in the figure below. The magnitudes of model translation and rotation are around 1e-6, which seems to meet the requirements:
806daa5b8b1e76982db704f4a1f5637
However, I have some other questions:

  1. Regarding the def nullspace(V) function, why does choosing such basis vectors eliminate the translation and rotation of the model? I couldn’t find relevant mathematical principles. Could you please explain briefly?
  2. Must this singular Poisson problem be solved using the KSP solver? Can I still use the LinearProblem within fenicsx as shown below:
 problem = LinearProblem(a, L, bcs=[bcT], petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
 uh = problem.solve()

Looking forward to your reply. Thank you very much.

Thank you for your solution.