The CG solver is ineffective in solving frequency response problems

Hello everyone! When I solve the frequency response of acantilever beam, I found that the solution obtained from direct solver and CG solver were different. Even I set the rtol to 1e-20, it still didin’t work. But the same code worked when set the frequency of force to 0. Besids, when I use GMRES solver, it works for freq=50.
The follows is my MWC:

import basix
import dolfinx
import dolfinx.fem.petsc
from mpi4py import MPI
import numpy as np
import ufl
from petsc4py import PETSc

def epsilon(u):
    """
    Strain function

    Args:
        u (dolfinx.fem.Function): Displacement function

    Returns:
        Strain tensor function

    **Mathematical formulation:**

    The strain tensor is calculated using the formula:

    .. math::
        \\epsilon(u) = \\frac{1}{2}\\left(\\nabla u + (\\nabla u )^T\\right)
    """
    return ufl.sym(ufl.grad(u))


def sigma(u, lmbda, mu):  # 3D or plane strain
    """
    Cauchy stress tensor function

    Args:
        u (dolfinx.fem.Function): displacement function
        lmbda (float | dolfinx.fem.Constant): Lamé’s elasticity parameter
        mu (float | dolfinx.fem.Constant): Lamé’s elasticity parameter

    Returns:
        Cauchy stress tensor function

    **Mathematical formulation:**

    The Cauchy stress tensor (\\sigma) is calculated using the Lamé parameters and the strain tensor (\\epsilon):

    .. math::
        \\sigma(u) =\\lambda(\\nabla \\cdot u)I + \\mu(\\nabla u + (\\nabla u)^T)
    """
    return lmbda * ufl.nabla_div(u) * ufl.Identity(len(u)) + 2 * mu * epsilon(u)


def main():
    # parameters
    pdic = {
        "lC": 8, "wC": 2, "hC": 2,  # the size of the model
        'Nx':16, 'Ny': 4, 'Nz': 4,  # the number of the coarse grids
        "E":210e9,
        "nu":0.3,
        "rho":7.8e3,
        "force": (1.0e5)/3, "freq": 50  # the force and frequency of the dynamic analysis
    }
    mu = (pdic['E'] / (2 * (1 + pdic['nu'])))
    lmbda = pdic['nu'] * pdic['E'] / ((1 - 2 * pdic['nu']) * (1 + pdic['nu']))
    
    # Create mesh-------------------------------------------------------------------------------------------------------
    mesh = dolfinx.mesh.create_box(comm=MPI.COMM_WORLD, points=[[0, 0, 0], [pdic['lC'], pdic['wC'], pdic['hC']]], 
            n=[pdic['Nx'], pdic['Ny'], pdic['Nz']],
            cell_type=dolfinx.mesh.CellType.hexahedron)
    
    # Create the function space-----------------------------------------------------------------------------------------
    c_el = basix.ufl.element(family="Lagrange", cell=mesh.topology.cell_name(),
            degree=1, shape=(mesh.topology.dim,))
    V = dolfinx.fem.functionspace(mesh, c_el)

    # Define the boundary conditions------------------------------------------------------------------------------------
    bc = dolfinx.fem.dirichletbc(np.array([0, 0, 0], dtype=dolfinx.default_scalar_type),
            dolfinx.fem.locate_dofs_geometrical(V, lambda x: np.isclose(x[0], 0)),
            V)
    fixed_dofs = bc._cpp_object.dof_indices()[0]

    # Define the force--------------------------------------------------------------------------------------------------
    p1 = lambda x: np.isclose(x[0], pdic['lC']) & np.isclose(x[1], 0) & np.isclose(x[2], 0)
    p2 = lambda x: np.isclose(x[0], pdic['lC']) & np.isclose(x[1], pdic['wC'] / 2) & np.isclose(x[2], 0)
    p3 = lambda x: np.isclose(x[0], pdic['lC']) & np.isclose(x[1], pdic['wC']) & np.isclose(x[2], 0)
    location = lambda x: p1(x) | p2(x) | p3(x)
    f_loc = dolfinx.mesh.locate_entities(mesh, 0, location)
    marker = dolfinx.mesh.meshtags(mesh, 0, f_loc, np.full(len(f_loc), 1, dtype=np.int32))
    f = np.zeros(mesh.geometry.x.size)
    f[marker.find(1)*3+2] = -pdic['force']  # force in z-axis

    # Create stiffness and mass matrices--------------------------------------------------------------------------------
    u = ufl.TrialFunction(V)
    v = ufl.TestFunction(V)
    ak = ufl.inner(sigma(u, lmbda, mu), epsilon(v)) * ufl.dx
    k_form = dolfinx.fem.form(ak)
    K = dolfinx.fem.petsc.assemble_matrix(k_form)
    K.assemble()
    am = pdic['rho'] * ufl.dot(v, u) * ufl.dx
    m_form = dolfinx.fem.form(am)
    M = dolfinx.fem.petsc.assemble_matrix(m_form)
    M.assemble()
    # dynamic stiffness matrix
    w = 2 * np.pi * pdic["freq"]
    W = -w ** 2 * M + K

    # Apply boundary conditions using lifting method--------------------------------------------------------------------
    b = PETSc.Vec().createWithArray(f)
    x_ref = PETSc.Vec().createWithArray(np.zeros_like(f))
    x = PETSc.Vec().createWithArray(np.zeros_like(f))
    W_ref = W.copy()
    # Apply lifting on rhs
    g = W.createVecRight()
    g.array[:] = 0
    g.setValues(fixed_dofs, np.zeros_like(fixed_dofs))
    g.assemble()
    W_ref.multAdd(-g, b, b)
    # set rhs boundary conditions
    b.setValues(fixed_dofs, np.zeros_like(fixed_dofs))
    # Apply lifting on matrix
    W_ref.zeroRowsColumns(fixed_dofs, diag=1)

    # Solve the system using Direct solver------------------------------------------------------------------------------
    ksp0 = PETSc.KSP().create(comm=MPI.COMM_WORLD)
    ksp0.setType(PETSc.KSP.Type.PREONLY)
    ksp0.getPC().setType(PETSc.PC.Type.LU)
    ksp0.setOperators(W_ref)
    ksp0.setUp()

    ksp0.solve(b, x_ref)

    # Save the solution using cg--------------------------------------------------------------------------------------------
    ksp1 = PETSc.KSP().create(comm=MPI.COMM_WORLD)
    ksp1.setType(PETSc.KSP.Type.CG)
    ksp1.getPC().setType(PETSc.PC.Type.ILU)
    ksp1.setOperators(W_ref)
    ksp1.setUp()

    ksp1.solve(b, x)

    print(x.array)
    print(x_ref.array)

    print(np.allclose(x.array, x_ref.array))

    with dolfinx.io.XDMFFile(MPI.COMM_WORLD, "solution.xdmf", "w") as xdmf:
        xdmf.write_mesh(mesh)
        funx = dolfinx.fem.Function(V)
        funx.x.petsc_vec.array[:] = x_ref.array
        xdmf.write_function(funx, 0.0)

if __name__ == '__main__':
    main()

When pdic['freq']=0, output:

[ 0.00000000e+00  0.00000000e+00  0.00000000e+00 ...  1.07782458e-05
 -1.97558494e-08 -6.00293039e-05]
[ 0.00000000e+00  0.00000000e+00  0.00000000e+00 ...  1.07782458e-05
 -1.97558494e-08 -6.00293039e-05]
True

When pdic['freq']=50, output:

[ 0.00000000e+00  0.00000000e+00  0.00000000e+00 ...  1.76022382e-05
 -1.02110686e-05 -6.96968041e-05]
[ 0.00000000e+00  0.00000000e+00  0.00000000e+00 ... -2.13367591e-06
 -1.92054057e-08  1.89030640e-05]
False

I don’t know if it’s a problem with my own code or the CG solver. Thank you very much for your help!

For higher frequencies your system will longer be positive definite (the sign in front of M is negative), in which case CG will fail. You will need a solver that can solve indefinite systems, e.g. GMRES, MINRES (for symmetric matrices).

I got it. Thank you very much, Mr. garth. For large scale linear system like that may not be positive definite but symmetric , which kind of iterative solver and preconditioner has better effciency, would you give me some advice?

Sadly iterative solvers can rarely function as a black box. You’d need to tailor their application depending on your underlying problem. It’s a deep topic. Consider Yousef Saad’s book for an introduction.

So that’s it. Thank you for answering my doubts and providing the learning materials. :pray: I will study this book.