Applying BC in parallel (elastic bar in dynamic explicit)

Dear community,

I am trying to launch my simulations in parallel but I get some issue with boundary condition.
I attached a test code of a pure traction bar in dynamic explicit, loading in displacement. I am solving the problem :
M_lumped a = - F_int with F_int = K*u
a = (M_lumped)⁻1 * (-F_int)
I followed the guidelines regarding a previous post here. It seems ok with 1 proc but when I am used more than 1 proc the problem appears.

This is the code of the test :

#!/usr/bin/env python
# coding: utf-8

from dolfinx import log, io, mesh, fem, cpp, geometry
from petsc4py import PETSc
from mpi4py import MPI
import ufl
import numpy as np
import matplotlib.pyplot as plt

savedir = "result_mpi_1proc/"

comm = MPI.COMM_WORLD
def mpi_print(s):
    print(f"Rank {comm.rank}: {s}")

#loading
t_start, t_end, nsteps = 0.0, 3., 5*800
dt = t_end/nsteps 
steps = np.linspace(t_start, t_end, nsteps)

# Geometry & Mesh
Lx , Ly = 1., 0.1
msh = mesh.create_rectangle(comm, [[0., 0.], [Lx,Ly]], [10,1], mesh.CellType.quadrilateral, ghost_mode=cpp.mesh.GhostMode.none) # cpp.mesh.GhostMode.shared_facet) 
ndim = msh.topology.dim
fdim = ndim - 1
# Material Parameters
E, nu     = fem.Constant(msh, PETSc.ScalarType(10)), fem.Constant(msh, PETSc.ScalarType(0.0)) #[MPa]
rho       = fem.Constant(msh, PETSc.ScalarType(1.))
mu        = E / (2.0*(1.0 + nu)) # [MPa]
lmbda     = E * nu / ((1.0 + nu)* (1.0 - 2.0*nu))
K0        = lmbda+2./3.*mu

# Space function

# index_map(n) n = 0 (vertex), n=1 (edge), n=2 (cell)
V_element=ufl.VectorElement("CG",msh.ufl_cell(),1)
V=fem.FunctionSpace(msh,V_element)
nb_cell_loc = msh.topology.index_map(2).size_local
nb_vertex_loc = msh.topology.index_map(0).size_local
nb_vertex_glo = msh.topology.index_map(0).size_global
nb_node_x = len(msh.geometry.x)

# Facets / Geometry

def right(x):
    return np.isclose(x[0], Lx)
def left(x):
   return np.isclose(x[0], 0)
def bottom(x):
   return np.isclose(x[1], 0)

facet_bottom  = mesh.locate_entities(msh, fdim, bottom)
facet_right   = mesh.locate_entities(msh, fdim, right)
facet_left    = mesh.locate_entities(msh, fdim, left)

facet_left_dofs = fem.locate_dofs_geometrical((V.sub(0),V.sub(0).collapse()[0]), left)
facet_right_dofs = fem.locate_dofs_geometrical((V.sub(0), V.sub(0).collapse()[0]), right)
facet_bottom_dofs = fem.locate_dofs_geometrical((V.sub(1),V.sub(1).collapse()[0]), bottom)  

facet_indices, facet_markers = [], []
facet_indices.append(facet_right)
facet_indices.append(facet_left)
facet_indices.append(facet_bottom)

facet_indices = np.array(np.hstack(facet_indices), dtype=np.int32)
sorted_facets = np.argsort(facet_indices)
boundary_indices = {"left": 0, "right": 1, "top": 2, "bottom": 3}
facet_markers.append(np.full(len(facet_right ),  boundary_indices["right"]))
facet_markers.append(np.full(len(facet_left ),  boundary_indices["left"]))
facet_markers.append(np.full(len(facet_bottom ), boundary_indices["bottom"]))
facet_markers = np.array(np.hstack(facet_markers), dtype=np.int32)
facet_tag = mesh.meshtags(msh, fdim, facet_indices[sorted_facets], facet_markers[sorted_facets])


metadata = {"quadrature_degree": 4}
ds = ufl.Measure("ds", domain=msh, subdomain_data=facet_tag, metadata=metadata)
dx = ufl.Measure("dx", domain=msh, metadata=metadata)


# BOUNDARY CONDITIONS
u_zero = np.array((0. ,)*msh.geometry.dim, dtype=PETSc.ScalarType) 


class Load():
    v0 = 3.0 #m/s
    t0 = 0.00001 #s
    def __init__(self, t):
        self.t = t
    def __call__(self,x):
        values = np.zeros((ndim-1, x.shape[1]),dtype=PETSc.ScalarType)
        if self.t < Load.t0:
            values[0] = 0.5 * (Load.v0 / Load.t0) * self.t * self.t
        else:
            values[0] = Load.v0 * (self.t-Load.t0) + 0.5 * Load.v0 * Load.t0
        return values
t = 0.    
trac_func = fem.Function(V.sub(0).collapse()[0])
s_app = Load(t)
trac_func.interpolate(s_app)


zero_u_bottom = fem.Function(V.sub(1).collapse()[0])
with zero_u_bottom.vector.localForm() as bc_local:
   bc_local.set(0.0)
zero_u_left = fem.Function(V.sub(0).collapse()[0])
with zero_u_left.vector.localForm() as bc_local:
   bc_local.set(0.0)

bc_right = fem.dirichletbc(trac_func , facet_right_dofs, V.sub(0))
bc_left = fem.dirichletbc(zero_u_left, facet_left_dofs ,V.sub(0))
bc_bottom = fem.dirichletbc( zero_u_bottom,facet_bottom_dofs, V.sub(1))
bcu = [bc_left, bc_bottom ,bc_right]


u, u_p        = fem.Function(V, name='Displacement'),fem.Function(V, name='Velocity') # solution u, velocity
u_pp          = fem.Function(V, name='Acceleration') # acceleration
u_pp_old      = fem.Function(V, name='Previous Acceleration') 
v, u_trial    = ufl.TestFunction(V), ufl.TrialFunction(V)

# Linear elastic model

def epsilon(u):
    return ufl.sym(ufl.grad(u)) 
def sigma(u):
    return lmbda * ufl.tr(epsilon(u)) * ufl.Identity(u.geometric_dimension()) + 2*mu*epsilon(u)
def mass(u, v):
    return rho*ufl.inner(u, v)*dx
def stiffness(u, v):
    return ufl.inner(sigma(u),epsilon(v))*dx
def p_ext(u):
    return ufl.dot(u, trac_func)*ds(1)

mass_form= fem.form(mass(u_trial, v))
M = fem.petsc.create_matrix(mass_form)
M = fem.petsc.assemble_matrix(mass_form)
M.assemble()
# Matrix Lumping
# Compute the sum of the rows of the mass matrix
ones       = fem.Function(V)
with ones.vector.localForm() as loc:
    loc.set(1.0)
m_action= ufl.action(mass(u_trial, v), ones)
M_lumped = fem.petsc.assemble_vector(fem.form(m_action))
M_lumped_inv = 1./M_lumped

with io.XDMFFile(msh.comm, savedir+ "displacement.xdmf", "w") as xdmf0:
    xdmf0.write_mesh(msh)
    xdmf0.write_function(u,t)

E_elas = 0
inc = 0
for t in steps[:50]:
    s_app.t = t
    trac_func.interpolate(s_app)
    if comm.rank == 0 :
        print("\nIncrement: %i"%(inc))
        print("Time: %.8g ;  Load : %.8g"%(t,trac_func.x.array[0] ))
    u.x.array[:] = u.x.array[:] + dt * u_p.x.array[:] + 0.5 * dt**2 * u_pp_old.x.array[:]
    fem.set_bc(u.vector.array, bcu)

    # f = fext-fint
    f_int_form = fem.form(stiffness(u, v))
    f_int_vec = fem.petsc.create_vector(f_int_form)

    with f_int_vec.localForm() as f_local:
        f_local.set(0.0)
    fem.petsc.assemble_vector(f_int_vec, f_int_form)
    fem.petsc.apply_lifting(f_int_vec, [mass_form], [bcu])
    f_int_vec.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    fem.petsc.set_bc(f_int_vec, bcu)

#    u_pp.vector.setArray(-M_lumped_inv*f_int_vec)
    u_pp.vector.array = -M_lumped_inv*f_int_vec
    mpi_print(f" u_pp.x.array[:] : { u_pp.x.array[:]}")
    u_pp.x.scatter_forward()

    u_p.x.array[:] = u_p.x.array + 0.5*dt * (u_pp.x.array + u_pp_old.x.array)
    u_pp_old.x.array[:] = u_pp.x.array

    E_elas = fem.assemble_scalar(fem.form(0.5*stiffness(u, u)))
    #mpi_print(f"Elas: {E_elas}")
    
    xdmf0.write_function(u, t)

    inc +=1

Some results :

Thank you for your help,

Best regards,

Lamia

You need to narrow down the problem with your code, as it is not with the boundary conditions.
I’ve slightly simplified and re-written your code to illustrate this:


from dolfinx import io, mesh, fem, cpp
from petsc4py import PETSc
from mpi4py import MPI
import ufl
import numpy as np

savedir = "result_mpi_1proc/"

comm = MPI.COMM_WORLD


def mpi_print(s):
    print(f"Rank {comm.rank}: {s}")


# loading
t_start, t_end, nsteps = 0.0, 3., 5*800
dt = t_end/nsteps
steps = np.linspace(t_start, t_end, nsteps)

# Geometry & Mesh
Lx, Ly = 1., 0.1
msh = mesh.create_rectangle(comm, [[0., 0.], [Lx, Ly]], [
                            10, 1], mesh.CellType.quadrilateral, ghost_mode=mesh.GhostMode.none)
ndim = msh.topology.dim
fdim = ndim - 1

# Space function

# index_map(n) n = 0 (vertex), n=1 (edge), n=2 (cell)
V_element = ufl.VectorElement("CG", msh.ufl_cell(), 1)
V = fem.FunctionSpace(msh, V_element)
nb_cell_loc = msh.topology.index_map(2).size_local
nb_vertex_loc = msh.topology.index_map(0).size_local
nb_vertex_glo = msh.topology.index_map(0).size_global
nb_node_x = len(msh.geometry.x)

# Facets / Geometry


def right(x):
    return np.isclose(x[0], Lx)


def left(x):
    return np.isclose(x[0], 0)


def bottom(x):
    return np.isclose(x[1], 0)


facet_bottom = mesh.locate_entities(msh, fdim, bottom)
facet_right = mesh.locate_entities(msh, fdim, right)
facet_left = mesh.locate_entities(msh, fdim, left)


V0, _ = V.sub(0).collapse()
V1, _ = V.sub(1).collapse()

facet_left_dofs = fem.locate_dofs_topological((V.sub(0), V0), fdim, facet_left)
facet_right_dofs = fem.locate_dofs_topological(
    (V.sub(0), V0), fdim, facet_right)
facet_bottom_dofs = fem.locate_dofs_topological(
    (V.sub(1), V1), fdim,  facet_bottom)

facet_indices, facet_markers = [], []
facet_indices.append(facet_right)
facet_indices.append(facet_left)
facet_indices.append(facet_bottom)

facet_indices = np.array(np.hstack(facet_indices), dtype=np.int32)
sorted_facets = np.argsort(facet_indices)
boundary_indices = {"left": 0, "right": 1, "top": 2, "bottom": 3}
facet_markers.append(np.full(len(facet_right),  boundary_indices["right"]))
facet_markers.append(np.full(len(facet_left),  boundary_indices["left"]))
facet_markers.append(np.full(len(facet_bottom), boundary_indices["bottom"]))
facet_markers = np.array(np.hstack(facet_markers), dtype=np.int32)
facet_tag = mesh.meshtags(
    msh, fdim, facet_indices[sorted_facets], facet_markers[sorted_facets])


metadata = {"quadrature_degree": 4}
ds = ufl.Measure("ds", domain=msh, subdomain_data=facet_tag, metadata=metadata)
dx = ufl.Measure("dx", domain=msh, metadata=metadata)


# BOUNDARY CONDITIONS
u_zero = np.array((0.,)*msh.geometry.dim, dtype=PETSc.ScalarType)


class Load():
    v0 = 3.0  # m/s
    t0 = 0.00001  # s

    def __init__(self, t):
        self.t = t

    def __call__(self, x):
        values = np.zeros((ndim-1, x.shape[1]), dtype=PETSc.ScalarType)
        if self.t < Load.t0:
            values[0] = 0.5 * (Load.v0 / Load.t0) * self.t * self.t
        else:
            values[0] = Load.v0 * (self.t-Load.t0) + 0.5 * Load.v0 * Load.t0
        return values


t = 0.1
trac_func = fem.Function(V.sub(0).collapse()[0])
s_app = Load(t)
trac_func.interpolate(s_app)


zero_u_bottom = fem.Function(V1)
zero_u_bottom.x.set(0)
zero_u_left = fem.Function(V0)
zero_u_left.x.set(0)

bc_right = fem.dirichletbc(trac_func, facet_right_dofs, V.sub(0))
bc_left = fem.dirichletbc(zero_u_left, facet_left_dofs, V.sub(0))
bc_bottom = fem.dirichletbc(zero_u_bottom, facet_bottom_dofs, V.sub(1))
bcu = [bc_left, bc_bottom, bc_right]


uh = fem.Function(V)
fem.petsc.set_bc(uh.vector, bcu)

with io.XDMFFile(msh.comm, "u_bc.xdmf", "w") as xdmf:
    xdmf.write_mesh(msh)
    xdmf.write_function(uh)

with this, you can check that the loading condition is set correctly on the right side of your domain for a non-zero t. Thus you should go through the following steps in your loop step by step to figure out what goes wrong.

Please note that

is not recommended to do, as you have not redefined u or v in your loop, thus calling f_int_form outside the loop is sufficient.

Hello dokken,
Thank you very much for the clean code. I tested it and the applied displacement has been applied correctly at a fixed time. I had the dynamic variational problem and the visualization seems wrong again.

I print of the displacement solution of the proc 0 and 1 in the terminal and it is seems correctly computed. This is what I get at the increment 49. The applied load appears at the displacement on the rank 1

Increment: 49
Time: 0.03675919 ;  Load : 0.11026257
Rank 1:  u.vector : [ 2.22463137e-08 -4.56305723e-11  9.04849466e-07  1.14482092e-11
  9.04849466e-07  4.78782817e-11  4.78714371e-05  4.08429742e-14
  4.78714373e-05 -2.18447761e-20  1.47089391e-03 -5.55755765e-17
  1.47089391e-03 -1.85608437e-16  2.17134529e-02 -1.26787152e-19
  2.17134529e-02 -2.40108036e-19  1.05335932e-01  1.95414713e-19
  1.05335932e-01  2.09389543e-18]
Rank 0:  u.vector : [ 2.50441322e-20  1.95124597e-22  2.50457404e-20  5.48809500e-22
  1.11681805e-17 -1.16766058e-19  1.15227192e-17 -1.09761603e-21
  2.95801975e-15 -5.50284948e-17  2.95660160e-15 -1.84826155e-16
  6.83987142e-13  4.10658847e-14  5.31994411e-13  7.39301326e-16
  9.54581249e-11  1.12852789e-11  9.60660916e-11  4.78797603e-11
  2.22475296e-08 -1.91516823e-10]

But his associated visualization on paraview don’t show it.
Screenshot from 2023-02-14 16-36-36

The problem seems to come from the xdmf results. I think the xdmf shows us only the result of the proc 0.

To check it , I tried to apply the load on the left side and I get this with 2 proc :
Screenshot from 2023-02-14 16-34-37

The result should be gathered automatically, is it correct ? Or I have to gather manually ?

Thank for your advice,

Lamia

As I am not sure what your current code looks like, and your example is a bit far of being minimal, it is hard for me to pinpoint where you have gone wrong.

Please make sure that you present a clean and simple code illustrating where things go wrong.
I’ve already put quite some effort to try to isolate the issues for you.

As you know something is going wrong in the loop, simply do a single time step, and remove line by line, saving the relevant vectors to XDMF, and compare them with a serial and parallel run.

Hello,
I have finally changed the library dolfinx with the new version 0.6. And I replace these few lines :

with io.XDMFFile(msh.comm, "u_bc.xdmf", "w") as xdmf:
    xdmf.write_mesh(msh)
    xdmf.write_function(uh,t)

with

xdmf = io.XDMFFile(msh.comm, "u_bc.xdmf", "w")
xdmf.write_mesh(msh)  
xdmf.write_function(uh, t)

The visualization issue seems to be solved ! I had an error with the version 0.4 when I tried the second way.

I have just a small question since I change the version. I got an issue with the command cpp.mesh.h(msh,2,[0]) talling me an incompatible function arguments.

In [16]: cpp.mesh.h(msh,2,[0])[1]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-39e2447279ba> in <module>
----> 1 cpp.mesh.h(msh,2,[0])[1]

TypeError: h(): incompatible function arguments. The following argument types are supported:
    1. (mesh: dolfinx::mesh::Mesh, dim: int, entities: numpy.ndarray[numpy.int32]) -> numpy.ndarray[numpy.float64]

Invoked with: <dolfinx.mesh.Mesh object at 0x7f7e64abbcd0>, 2, [0]

But I don’t know what’s wrong with the arguments I put :

msh = mesh.create_rectangle(comm, [[0., 0.], [Lx, Ly]], [
                            10, 1], mesh.CellType.quadrilateral, ghost_mode=mesh.GhostMode.none)
ndim = msh.topology.dim

Thank you for the help,

Lamia

See:

and in particular:

Thus you can call
msh.h(2, [0])

Oh, ok. I understand.
Thank you very much !