Rigid Body Modes at non-zero frequencies

Hi guys,
I am performing modal analysis of a 2D rectangular beam and I am getting what looks like rigid body modes at non-zero frequencies and repeated modes at higher frequencies.

Can anyone tell me how to resolve this issue?

# To add a new cell, type '# %%'
# To add a new markdown cell, type '# %% [markdown]'
# %%
#
# ..    # gedit: set fileencoding=utf8 :
#
# .. raw:: html
#
#  <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><p align="center"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png"/></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a></p>
#
# .. _ModalAnalysis:
#
# ==========================================
# Modal analysis of an elastic structure
# ==========================================
#
# -------------
# Introduction
# -------------
#
# This program performs a dynamic modal analysis of an elastic cantilever beam
# represented by a 3D solid continuum. The eigenmodes are computed using the
# **SLEPcEigensolver** and compared against an analytical solution of beam theory.
# The corresponding file can be obtained from :download:`cantilever_modal.py`.
#
#
# The first four eigenmodes of this demo will look as follows:
#
# .. image:: vibration_modes.gif
#    :scale: 80 %
#
# The first two fundamental modes are on top with bending along the weak axis (left) and along
# the strong axis (right), the next two modes are at the bottom.
#
# ---------------
# Implementation
# ---------------
#
# After importing the relevant modules, the geometry of a beam of length :math:`L=20`
# and rectangular section of size :math:`B\times H` with :math:`B=0.5, H=1` is first defined::


# %%
from dolfin import *
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path

Path("plots").mkdir(parents=True, exist_ok=True)
dim = 2
N_eig = 16   # number of eigenvalues

if dim ==2:
    L, H = 1.0, 0.02
    point0 = Point(-0.4,1.02)
#     point0 = Point(-0.5,-0.01)

    Nx = 600
    Nz = int(H/L*Nx)
    print(Nz)

    mesh = RectangleMesh(point0,Point(point0[0]+L,point0[1]+H), Nx, Nz)

elif dim == 3:
    L, B, H = 1.0, 0.02, 0.02
    point0 = Point(-0.4,0.05,1.02)

    Nx = 200
    Ny = int(B/L*Nx)+1
    Nz = int(H/L*Nx)+1

    mesh = BoxMesh(point0,Point(point0[0]+L,point0[1]+B,point0[2]+H), Nx, Ny, Nz)
else:
    raise ValueError("The dimension is neither 2D or 3D")
mesh


# %%
# Material parameters and elastic constitutive relations are classical (here we
# take :math:`\nu=0`) and we also introduce the material density :math:`\rho` for
# later definition of the mass matrix::

E, nu = Constant(0.025e9), Constant(0.3)
rho = Constant(500.0)
# E, nu = 1e5, 0.0
# rho = 1e-3

# Lame coefficient for constitutive relation
mu = E/2./(1+nu)
lmbda = E*nu/(1+nu)/(1-2*nu)

def eps(v):
    return sym(grad(v))
def sigma(v):
    dim = v.geometric_dimension()
    return 2.0*mu*eps(v) + lmbda*tr(eps(v))*Identity(dim)

# Standard FunctionSpace is defined and boundary conditions correspond to a
# fully clamped support at :math:`x=0`::

V = VectorFunctionSpace(mesh, 'Lagrange', degree=1)
v2d = vertex_to_dof_map(V).reshape((-1, dim))
d2v = dof_to_vertex_map(V)[range(0, len(dof_to_vertex_map(V)), dim)]/dim
u_ = TrialFunction(V)
du = TestFunction(V)


# def left(x, on_boundary):
#     return near(x[0],0.)

# bc = DirichletBC(V, Constant((0.,0.,0.)), left)

# The system stiffness matrix :math:`[K]` and mass matrix :math:`[M]` are
# respectively obtained from assembling the corresponding variational forms::

k_form = inner(sigma(du),eps(u_))*dx
l_form = Constant(1.)*u_[0]*dx
K = PETScMatrix()
b = PETScVector()
assemble_system(k_form, l_form, None, A_tensor=K, b_tensor=b)

m_form = rho*dot(du,u_)*dx
M = PETScMatrix()
assemble(m_form, tensor=M)



# Matrices :math:`[K]` and :math:`[M]` are first defined as PETSc Matrix and
# forms are assembled into it to ensure that they have the right type.
# Note that boundary conditions have been applied to the stiffness matrix using
# ``assemble_system`` so as to preserve symmetry (a dummy ``l_form`` and right-hand side
# vector have been introduced to call this function).
#
#
# Modal dynamic analysis consists in solving the following generalized
# eigenvalue problem :math:`[K]\{U\}=\lambda[M]\{U\}` where the eigenvalue
# is related to the eigenfrequency :math:`\lambda=\omega^2`. This problem
# can be solved using the ``SLEPcEigenSolver``. ::

eigensolver = SLEPcEigenSolver(K, M)
eigensolver.parameters['problem_type'] = 'gen_hermitian'
eigensolver.parameters['spectral_transform'] = 'shift-and-invert'
eigensolver.parameters['spectral_shift'] = 0.

# The problem type is specified to be a generalized eigenvalue problem with
# Hermitian matrices. By default, SLEPc computes the largest eigenvalues. Here
# we instead look for the smallest eigenvalues (they should all be real). A
# spectral transform is therefore performed
# using the keyword ``shift-invert`` i.e. the original problem is transformed into
# an equivalent problem with eigenvalues given by :math:`\dfrac{1}{\lambda - \sigma}`
# instead of :math:`\lambda` where :math:`\sigma` is the value of the spectral shift.
# It is therefore much easier to compute eigenvalues close to :math:`\sigma` i.e.
# close to :math:`\sigma = 0` in the present case. Eigenvalues are then
# transformed back by SLEPc to their original value :math:`\lambda`.
#
#
# We now ask SLEPc to extract the first 6 eigenvalues by calling its solve function
# and extract the corresponding eigenpair (first two arguments of ``get_eigenpair``
# correspond to the real and complex part of the eigenvalue, the last two to the
# real and complex part of the eigenvector)::

print("Computing {} first eigenvalues...".format(N_eig))
eigensolver.solve(N_eig)

# Exact solution computation
from scipy.optimize import root
from math import cos, cosh
falpha = lambda x: cos(x)*cosh(x)+1
alpha = lambda n: root(falpha, (2*n+1)*pi/2.)['x'][0]


# Extraction
normal_M = np.zeros(N_eig)
normal_K = np.zeros(N_eig)
lamba = np.zeros(N_eig)


# %%

Modes = []
file_results = XDMFFile("xdmf/elasticity_results1.xdmf")
file_results.parameters["flush_output"] = True
file_results.parameters["functions_share_mesh"] = True
# file_results.write(mesh)

for i in range(N_eig):
    # Extract eigenpair
    r, c, rx, cx = eigensolver.get_eigenpair(i)

    # 3D eigenfrequency
    freq_3D = sqrt(r)/2/pi
    lamba[i] = r
#     freq[i] = sqrt(lamba[i])/2/pi

    # Beam eigenfrequency
#     if i % 2 == 0: # exact solution should correspond to weak axis bending
#         I_bend = H*B**3/12.
#     else:          #exact solution should correspond to strong axis bending
#         I_bend = B*H**3/12.
#     freq_beam = alpha(i/2)**2*sqrt(E*I_bend/(rho*B*H*L**4))/(2*pi)

    print(f"{freq_3D:.3f}")

    # Initialize function and assign eigenvector
    eigenmode = Function(V,name="Eigenvector "+str(i))
    eigenmode.vector()[:] = rx
    Modes.append(rx[:].reshape((dim,int(rx[:].shape[0]/dim)),order='F').T)
    normal_M[i] = np.dot(rx[:],M*rx[:])
    normal_K[i] = np.dot(rx[:],K*rx[:])


    # Function can be plotted as usual:
    rx_func = Function(V)
    rx_func.vector()[:] = rx
    file_results.write(rx_func, 0.)
    plt.figure(figsize=(20,5))
    plot(rx_func)
    plt.savefig(f"plots/pmode_{i}_freq_{freq_3D:6f}.png", format="png", dpi=600)
#     plt.autoscale()
    plt.close()
Modes = np.array(Modes)
freq=np.sqrt(np.abs(lamba))/(2*np.pi)
Modes.shape


# %%
arr = rx_func.vector()[:]
coor = mesh.coordinates()

d2v.shape, v2d.shape
N_eig


# %%

v2d.shape, d2v.shape


# %%
# values = []
# for i, dum in enumerate(coor):
#     values.append([arr[v2d[dim*i]],arr[v2d[dim*i+1]]])
# values = np.array(values)
# values


# %%
V.tabulate_dof_coordinates()


# %%
rx_func.vector()[:].reshape((dim,int(rx_func.vector()[:].shape[0]/dim)),order='F').T


# %%
Modes.shape


# %%
Modes_new = np.array([
    Modes[:,i,:].reshape(N_eig*dim) for i in range(Modes.shape[1])
                     ])
Modes_new.shape


# %%
mesh.coordinates()[int(d2v[1]),:]


# %%
nodenum =  mesh.coordinates().shape[0]
coords = np.zeros((nodenum, dim))
for i in range(mesh.coordinates().shape[0]):
    coords[i,:] = mesh.coordinates()[int(d2v[i]),:]
coords


# %%
for i in range(20):
    print(f"coord x-{coords[i,0]:4f}, y-{coords[i,1]:4f} \t mode[0] x-{Modes[0,i,0]:4f}, y-{Modes[0,i,1]:4f}")


# %%
if dim==2:
    rigid_dof = 3
elif dim==3:
    rigifd_dof = 6
Assembled_mode_nodes = np.hstack((mesh.coordinates(),Modes_new[:,rigid_dof*dim:]))
Assembled_mass_stiff = np.transpose([normal_M,normal_K])[rigid_dof:,:]
Modes_new[0,rigid_dof*dim:]
Modes_new[0,:]
Assembled_mode_nodes.shape


# %%
if dim==2:
    nodes_2d_to_3d = coords[:,0]
    nodes_2d_to_3d =  np.vstack((nodes_2d_to_3d, 0.05*np.ones(nodenum)))
    nodes_2d_to_3d =  np.vstack((nodes_2d_to_3d, coords[:,1]))
    for i in range(1,N_eig-rigid_dof+1):
        nodes_2d_to_3d = np.vstack((nodes_2d_to_3d, Assembled_mode_nodes[:,2*i]))
        nodes_2d_to_3d = np.vstack((nodes_2d_to_3d, np.zeros(nodenum)))
        nodes_2d_to_3d = np.vstack((nodes_2d_to_3d, Assembled_mode_nodes[:,2*i+1]))
        print(i, 2*i, 2*i+1, int(Assembled_mode_nodes.shape[1]/dim))
    print("nodes_2d_to_3d.shape", nodes_2d_to_3d.shape)
    nodes_2d_to_3d = nodes_2d_to_3d.T
    for i in range(1,N_eig-rigid_dof+1):
        print(i)
        np.savetxt(f'modeshapes2d_to_3d_mode{i-1}.csv',nodes_2d_to_3d[:,:3*i], delimiter=",",fmt='%.8f')
nodes_2d_to_3d.shape


# %%
np.savetxt(f'mass_stiff{dim}D.csv',Assembled_mass_stiff,delimiter=",",fmt='%.8f')
np.savetxt(f'modeshapes{dim}D.csv',Assembled_mode_nodes, delimiter=",",fmt='%.8f')

# %%
fig, ax = plt.subplots(N_eig,1,figsize=(50,50))
for k, freqi in enumerate(freq):
    X = coords[:,0]
    Y = coords[:,1]
    U = Modes_new[:,2*k]
    V1 = Modes_new[:,2*k+1]
    color_array = np.sqrt(U**2 + V1**2)
    
#     ax1.set_ylim(-0.02, 0.02)
#     ax1.set_title(f'Mode Displacement vector plots for frequency = {freqi:6f} [Hz]')
    Q = ax[k].quiver(X, Y, U, V1, color_array, units='inches',color='r')
    qk = ax[k].quiverkey(Q, 0.5, 0.5, 0.1, "", labelpos='E',
                       coordinates='figure')
    ax[k].get_xaxis().set_visible(False)
    ax[k].set_ylabel(f"{freq[k]:.1f}", rotation=0, fontsize=20, labelpad=20)

    
plt.tight_layout()
plt.savefig(f"plots/modes.png", format="png", dpi=300)
plt.show()    

# %%
N_eig-rigid_dof, nodenum


# %%
lamba_n = np.double(lamba[3:])
lamba_n

modes

Hi, does this fixes the problem ?

# Zero out the rows to enforce boundary condition
for bc in bcs:
    bc.zero(M)

I forgot to mention, I am using Free-Free Beam. For this simulation, there should be no boundary condition.

I resolve the issue. Actually, there was no issue. The modes (117 and 234.4 Hz) look like rigid modes but when I animated in paraview they are actually in-plane bending modes.

Thanks a lot for the help.