Error: Unable to orthonormalize vector basis. Reason: Vector space has linear dependency. Where: This error was encountered inside Vector Space Basis.cpp

Hello, I am having difficulty in understanding the following error -

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
/tmp/ipykernel_3949/1105952868.py in <module>
      5 # AMG). The solution vector is passed so that it can be copied to
      6 # generate compatible vectors for the nullspace.
----> 7 null_space = build_nullspace(V, u.vector())
      8 
      9 # Attach near nullspace to matrix

/tmp/ipykernel_3949/3298775566.py in build_nullspace(V, x)
     15     # Create vector space basis and orthogonalize
     16     basis = VectorSpaceBasis(nullspace_basis)
---> 17     basis.orthonormalize()
     18 
     19     return basis

RuntimeError: 

*** -------------------------------------------------------------------------
*** DOLFIN encountered an error. If you are not able to resolve this issue
*** using the information listed below, you can ask for help at
***
***     fenics-support@googlegroups.com
***
*** Remember to include the error message listed below and, if possible,
*** include a *minimal* running example to reproduce the error.
***
*** -------------------------------------------------------------------------
*** Error:   Unable to orthonormalize vector basis.
*** Reason:  Vector space has linear dependency.
*** Where:   This error was encountered inside VectorSpaceBasis.cpp.
*** Process: 0
*** 
*** DOLFIN version: 2019.2.0.dev0
*** Git changeset:  ubuntu
*** -------------------------------------------------------------------------

I was running out of memory when using linear solver so in this post @dokken suggested me to use CG with a GAMG preconditioner which is available on Bitbucket.
Here is the minimum working example to reproduce the above error -

from dolfin import *
import matplotlib.pyplot as plt

if not has_linear_algebra_backend("PETSc"):
    print("DOLFIN has not been configured with PETSc. Exiting.")
    exit()
parameters["linear_algebra_backend"] = "PETSc"

def build_nullspace(V, x):
    nullspace_basis = [x.copy() for i in range(6)]

    V.sub(0).dofmap().set(nullspace_basis[0], 1.0);
    V.sub(1).dofmap().set(nullspace_basis[1], 1.0);
    V.sub(2).dofmap().set(nullspace_basis[2], 1.0);

    for x in nullspace_basis:
        x.apply("insert")

    # Create vector space basis and orthogonalize
    basis = VectorSpaceBasis(nullspace_basis)
    basis.orthonormalize()

    return basis

from mshr import *

cylinder = Cylinder(Point(0.5, 0.5, 0), Point(0.5, 0.5, 2), 0.25, 0.25)
cube = Box(Point(0, 0, 0),Point(1, 1, 1))
domain = cube - cylinder
mesh = generate_mesh(domain, 40)

E = Constant(200e3) 
nu = Constant(0.3)
mu = E/2/(1+nu)
lmbda = E*nu/(1+nu)/(1-2*nu)

def epsilon(u):
    return sym(grad(u))
def sigma(u):
    return lmbda*tr(epsilon(u))*Identity(3) + 2*mu*epsilon(u)

V = VectorFunctionSpace(mesh, "Lagrange", 1)

f = Constant((0., 0., 0.))
u = TrialFunction(V)
v = TestFunction(V)
a = inner(sigma(u), epsilon(v))*dx
L = inner(f, v)*dx

class left(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[0], 0., 0.1)
class right(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[0], 1., 0.1)

boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1)

left().mark(boundaries, 1)
right().mark(boundaries, 2)

bc_left = DirichletBC(V, Constant((0., 0., 0.)), boundaries, 1)
bc_right = DirichletBC(V.sub(0), Constant(1.) , boundaries, 2)

bcs = [bc_left, bc_right]

A, b = assemble_system(a, L, bcs)

u = Function(V)

null_space = build_nullspace(V, u.vector())

as_backend_type(A).set_near_nullspace(null_space)

pc = PETScPreconditioner("petsc_amg")

PETScOptions.set("mg_levels_ksp_type", "chebyshev")
PETScOptions.set("mg_levels_pc_type", "jacobi")

PETScOptions.set("mg_levels_esteig_ksp_type", "cg")
PETScOptions.set("mg_levels_ksp_chebyshev_esteig_steps", 50)

solver = PETScKrylovSolver("cg", pc)
solver.parameters["monitor_convergence"] = True

solver.set_operator(A);

solver.solve(u.vector(), b);

Any help regarding the above would be highly appreciated.
Thank You.

Your nullspace is wrong, see Bitbucket

Yeah, that actually was the issue, I replaced def build_nullspace(V, x) function according to your suggestion by this and it worked -

def build_nullspace(V, x):
    """Function to build null space for 3D elasticity"""

    # 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.copy() for i in range(dim)]

    # Build translational null space basis
    for i in range(gdim):
        V.sub(i).dofmap().set(nullspace_basis[i], 1.0);

    # Build rotational null space basis
    if gdim == 2:
        V.sub(0).set_x(nullspace_basis[2], -1.0, 1);
        V.sub(1).set_x(nullspace_basis[2], 1.0, 0);
    elif gdim == 3:
        V.sub(0).set_x(nullspace_basis[3], -1.0, 1);
        V.sub(1).set_x(nullspace_basis[3],  1.0, 0);

        V.sub(0).set_x(nullspace_basis[4],  1.0, 2);
        V.sub(2).set_x(nullspace_basis[4], -1.0, 0);

        V.sub(2).set_x(nullspace_basis[5],  1.0, 1);
        V.sub(1).set_x(nullspace_basis[5], -1.0, 2);

    for x in nullspace_basis:
        x.apply("insert")

    return VectorSpaceBasis(nullspace_basis)

But still one issue is when I am using this Gmsh generated .msh file, in the post-processing step, when I am saving stress field by projection into VTK format, it somehow ran out of memory and the kernel dies when try to implement the below code -

# Project and write stress field to post-processing file
W = TensorFunctionSpace(mesh, "Discontinuous Lagrange", 0)
stress = project(sigma(u), V=W)
File("stress_2.pvd", "compressed") << stress

Here is the minimal working example to reproduce the issue -

from dolfin import *
import matplotlib.pyplot as plt

# Test for PETSc
if not has_linear_algebra_backend("PETSc"):
    print("DOLFIN has not been configured with PETSc. Exiting.")
    exit()

# Set backend to PETSC
parameters["linear_algebra_backend"] = "PETSc"

def build_nullspace(V, x):

    gdim = V.mesh().geometry().dim()
    assert gdim == 2 or gdim == 3

    dim = 3 if gdim == 2 else 6

    nullspace_basis = [x.copy() for i in range(dim)]

    for i in range(gdim):
        V.sub(i).dofmap().set(nullspace_basis[i], 1.0);

    if gdim == 2:
        V.sub(0).set_x(nullspace_basis[2], -1.0, 1);
        V.sub(1).set_x(nullspace_basis[2], 1.0, 0);
    elif gdim == 3:
        V.sub(0).set_x(nullspace_basis[3], -1.0, 1);
        V.sub(1).set_x(nullspace_basis[3],  1.0, 0);

        V.sub(0).set_x(nullspace_basis[4],  1.0, 2);
        V.sub(2).set_x(nullspace_basis[4], -1.0, 0);

        V.sub(2).set_x(nullspace_basis[5],  1.0, 1);
        V.sub(1).set_x(nullspace_basis[5], -1.0, 2);

    for x in nullspace_basis:
        x.apply("insert")

    return VectorSpaceBasis(nullspace_basis)

import meshio

msh = meshio.read("3dmesh.msh")

def create_mesh(mesh, cell_type, prune_z=False):
    cells = mesh.get_cells_type(cell_type)
    points = mesh.points[:, :2] if prune_z else mesh.points
    out_mesh = meshio.Mesh(points=points, cells={cell_type: cells})
    return out_mesh

tetra_mesh = create_mesh(msh, "tetra")
triangle_mesh = create_mesh(msh, "triangle")

meshio.write("tri.xdmf", triangle_mesh)
meshio.write("tetra.xdmf", tetra_mesh)

from dolfin import *
mesh = Mesh()
with XDMFFile("tetra.xdmf") as infile:
    infile.read(mesh)

E = Constant(200e3) #E = 200 GPa 
nu = Constant(0.3)
mu = E/2/(1+nu)
lmbda = E*nu/(1+nu)/(1-2*nu)

def epsilon(u):
    return sym(grad(u))
def sigma(u):
    return lmbda*tr(epsilon(u))*Identity(3) + 2*mu*epsilon(u)

V = VectorFunctionSpace(mesh, "Lagrange", 1)

f = Constant((0., 0., 0.))
u = TrialFunction(V)
v = TestFunction(V)
a = inner(sigma(u), epsilon(v))*dx
L = inner(f, v)*dx

class left(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[0], -35.3, 0.1)
class right(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[0], 29.69, 0.1)

boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1)

left().mark(boundaries, 1)
right().mark(boundaries, 2)

bc_left = DirichletBC(V.sub(0), Constant(1.) , boundaries, 1)
bc_right = DirichletBC(V , Constant((0., 0., 0.)), boundaries, 2)

bcs = [bc_left, bc_right]

A, b = assemble_system(a, L, bcs)

u = Function(V)

null_space = build_nullspace(V, u.vector())

as_backend_type(A).set_near_nullspace(null_space)

pc = PETScPreconditioner("petsc_amg")

PETScOptions.set("mg_levels_ksp_type", "chebyshev")
PETScOptions.set("mg_levels_pc_type", "jacobi")

PETScOptions.set("mg_levels_esteig_ksp_type", "cg")
PETScOptions.set("mg_levels_ksp_chebyshev_esteig_steps", 50)

solver = PETScKrylovSolver("cg", pc)
solver.parameters["monitor_convergence"] = True

solver.set_operator(A);

solver.solve(u.vector(), b);

W = TensorFunctionSpace(mesh, "Discontinuous Lagrange", 0)
stress = project(sigma(u), V=W)
File("stress_2.pvd", "compressed") << stress

Thanks for the guidance.

Because projection requires a linear solver and legacy DOLFIN defaults to a direct solver.

See Where to find 'project'-function in dolfinx? - #2 by nate or something like project(*args, solver_type="cg").

Change the projection to use an iterative solver, ref: Out of memory error - #4 by dokken as the default is LU

EDIT: Didn’t see @nate’s reply when I posted this. Same idea different post

Thanks @nate and @dokken for suggesting to change the solver_type to an iterative solver, I changed the code to -

W = TensorFunctionSpace(mesh, "Discontinuous Lagrange", 0)
stress = project(sigma(u), W, solver_type="cg")
File("stress_2.pvd") << stress

But still the same error of running out of memory persists.

I found an alternative to visualize stress using -

s = sigma(u) - (1./3)*tr(sigma(u))*Identity(3)  # deviatoric stress
von_Mises = sqrt(3./2*inner(s, s))
V = FunctionSpace(mesh, 'CG', 1)
vm = project(von_Mises, V, solver_type="cg")
File("von_Mises.pvd") << vm

It worked fine due to same continous Function Space, so yeah, but I am still curious why even using iterative solver is not able to project that stress tensor.
Thanks for the help.

You should not put the stresses in CG, 1, as They are not in that space. Consider using DG,0 and a local projection, as shown in the last cell of SSCP_2023_lectures/L12 (FEniCS Intro)/L03_FEniCS_Darcy.ipynb at main · Simula-SSCP/SSCP_2023_lectures · GitHub

I tried using local_project but the same error of running out of memory exists, I used this which led to dead kernel -

elem_v = VectorElement("DG", mesh.ufl_cell(), 0)
W_v = FunctionSpace(mesh, elem_v)
stress = local_project(sigma(u), W_v)
File("stress_2.pvd") << stress

But in The equations of linear elasticity tutorial, the author is projecting von_Mises stress on CG, 1 and plotting it. Why is it inaccurate ?

Because the tutorial is very out of date.
Local project shouldn’t consume alot of memory, as it only assembles local matrices, and does local inversions, Which should be quicker and less expensive than a normal projection.

I cannot reproduce any of your errors, as you haven’t supllied the msh file that you are using. Here is a version with the original mesh you posted:

from dolfin import *
from mshr import *

def build_nullspace(V, x):

    gdim = V.mesh().geometry().dim()
    assert gdim == 2 or gdim == 3

    dim = 3 if gdim == 2 else 6

    nullspace_basis = [x.copy() for i in range(dim)]

    for i in range(gdim):
        V.sub(i).dofmap().set(nullspace_basis[i], 1.0);

    if gdim == 2:
        V.sub(0).set_x(nullspace_basis[2], -1.0, 1);
        V.sub(1).set_x(nullspace_basis[2], 1.0, 0);
    elif gdim == 3:
        V.sub(0).set_x(nullspace_basis[3], -1.0, 1);
        V.sub(1).set_x(nullspace_basis[3],  1.0, 0);

        V.sub(0).set_x(nullspace_basis[4],  1.0, 2);
        V.sub(2).set_x(nullspace_basis[4], -1.0, 0);

        V.sub(2).set_x(nullspace_basis[5],  1.0, 1);
        V.sub(1).set_x(nullspace_basis[5], -1.0, 2);

    for x in nullspace_basis:
        x.apply("insert")

    return VectorSpaceBasis(nullspace_basis)

cylinder = Cylinder(Point(0.5, 0.5, 0), Point(0.5, 0.5, 2), 0.25, 0.25)
cube = Box(Point(0, 0, 0),Point(1, 1, 1))
domain = cube - cylinder
mesh = generate_mesh(domain, 40)


E = Constant(200e3) #E = 200 GPa 
nu = Constant(0.3)
mu = E/2/(1+nu)
lmbda = E*nu/(1+nu)/(1-2*nu)

def epsilon(u):
    return sym(grad(u))
def sigma(u):
    return lmbda*tr(epsilon(u))*Identity(3) + 2*mu*epsilon(u)

V = VectorFunctionSpace(mesh, "Lagrange", 1)

f = Constant((0., 0., 0.))
u = TrialFunction(V)
v = TestFunction(V)
a = inner(sigma(u), epsilon(v))*dx
L = inner(f, v)*dx

class left(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[0], 0., 0.1)
class right(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[0], 1., 0.1)

boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1)

left().mark(boundaries, 1)
right().mark(boundaries, 2)

bc_left = DirichletBC(V.sub(0), Constant(1.) , boundaries, 1)
bc_right = DirichletBC(V , Constant((0., 0., 0.)), boundaries, 2)

bcs = [bc_left, bc_right]

A, b = assemble_system(a, L, bcs)

u = Function(V)

null_space = build_nullspace(V, u.vector())

as_backend_type(A).set_near_nullspace(null_space)

pc = PETScPreconditioner("petsc_amg")

PETScOptions.set("mg_levels_ksp_type", "chebyshev")
PETScOptions.set("mg_levels_pc_type", "jacobi")

PETScOptions.set("mg_levels_esteig_ksp_type", "cg")
PETScOptions.set("mg_levels_ksp_chebyshev_esteig_steps", 50)

solver = PETScKrylovSolver("cg", pc)
solver.parameters["monitor_convergence"] = True

solver.set_operator(A);

solver.solve(u.vector(), b);


def local_project(f, V):
    u = TrialFunction(V)
    v = TestFunction(V)
    a_proj = inner(u, v)*dx
    b_proj = inner(f, v)*dx
    solver = LocalSolver(a_proj, b_proj)
    solver.factorize()
    u = Function(V)
    solver.solve_local_rhs(u)
    return u

W = TensorFunctionSpace(mesh, "Discontinuous Lagrange", 0)
stress = local_project(sigma(u), W)
with XDMFFile("stress.xdmf") as xdmf:
    xdmf.write(stress)

Here is my .msh file. Yeah, this code works fine on the original inbuilt mesh but I think due to finer mesh that I gave in my .msh file it keeps running out of memory.
Any solution regarding this or do I have to use a relatively coarser mesh ?

EDIT: You can reproduce the same error by setting mesh density to 60 that is -

mesh = generate_mesh(domain, 60)

EDIT2: I made the mesh coarser, now its’ working.
Thanks.

How do I make sure to select sigma_xx, sigma_yy, sigma_zz, or other stresses from these ?

 stress_tensor1 = [[0, 1, 2],
                  [3, 4, 5],
                  [6, 7, 8] ]

 stress_tensor2 = [[sigma_xx, sigma_xy, sigma_xz],
                  [sigma_yx, sigma_yy, sigma_yz],
                  [sigma_zx, sigma_zy, sigma_zz] ]

Are stress_tensor1 and stress_tensor2 equivalent ?

The questions asked have become far unrelated from the original question. Furthermore the questions are not directly related to FEniCS. I suggest you discuss your project with your academic peers, supervisor or collaborators.