How to apply a force load at a known node number

Hi, is there a way of applying a force load for 3D linear elasticity if the node number (global) is known?

I am trying to modify the minimal 3D linear elasticity code below. I took out the self weight term in the variational formulation.

I remember there was a ‘pointwise’ option for Diritchlet BCs in the legacy FEnics, but it also required the use of a marker function to find the node. Is there a function I can use to apply the force at a node if the global node number is known? Maybe accessing the force vector of the linear system?

# Common libraries
import numpy as np

# Fenicsx FEA libraries
from dolfinx import mesh, fem, io, default_scalar_type
from dolfinx.fem.petsc import LinearProblem
from mpi4py import MPI
import ufl

mu = 1
lambda_ = 1.25

domain = mesh.create_unit_cube(MPI.COMM_WORLD, nx=1, ny=1, nz=1, cell_type=mesh.CellType.tetrahedron)
V = fem.functionspace(domain, ("Lagrange", 1, (domain.geometry.dim, )))


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

fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(domain, fdim, clamped_boundary)

u_D = np.array([0, 0, 0], dtype=default_scalar_type)
bc = fem.dirichletbc(u_D, fem.locate_dofs_topological(V, fdim, boundary_facets), V)

T = fem.Constant(domain, default_scalar_type((0, 0, 0)))

ds = ufl.Measure("ds", domain=domain)

def epsilon(u):
    return ufl.sym(ufl.grad(u))  # Equivalent to 0.5*(ufl.nabla_grad(u) + ufl.nabla_grad(u).T)

def sigma(u):
    return lambda_ * ufl.nabla_div(u) * ufl.Identity(len(u)) + 2 * mu * epsilon(u)

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = ufl.inner(sigma(u), epsilon(v)) * ufl.dx
L = ufl.dot(T, v) * ds

problem = LinearProblem(a, L, bcs=[bc], petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
uh = problem.solve()

with io.XDMFFile(domain.comm, "deformation.xdmf", "w") as xdmf:
    xdmf.write_mesh(domain)
    uh.name = "Deformation"
    xdmf.write_function(uh)

See for instance

There are various other ways to do this as well, for instance with locate_dofs_topological or geometrical, see for instance How to apply a pointwise force using dolfinx - #6 by lyunfei1211
How to apply component wise dirichlet boundary condition on a point - #2 by dokken
or by looking at a map from global input index to dof:
How to apply a boundary condition to a range of DOFs? - #7 by dokken

Hi Jorgen, thank you for the prompt reply.

I’m trying to assemble the linear system so I can use the following:

Based on your code here:
https://jsdokken.com/dolfinx-tutorial/chapter2/heat_code.html

And the answer given in:

I wrote the following code (below). However I get this solver error:
File ~/.local/lib/python3.10/site-packages/spyder_kernels/customize/utils.py:209 in exec_encapsulate_locals
exec_fun(compile(code_ast, filename, “exec”), globals)

File ~/files/fenics_point_loading_example.py:88
uh = solver.solve(b, u.x.petsc_vec)

File PETSc/KSP.pyx:395 in petsc4py.PETSc.KSP.solve

Error: error code 73
[0] KSPSolve() line 1085 in ./src/ksp/ksp/interface/itfunc.c
[0] KSPSolve_Private() line 850 in ./src/ksp/ksp/interface/itfunc.c
[0] KSPSetUp() line 406 in ./src/ksp/ksp/interface/itfunc.c
[0] PCSetUp() line 1017 in ./src/ksp/pc/interface/precon.c
[0] PCSetUp_LU() line 87 in ./src/ksp/pc/impls/factor/lu/lu.c
[0] MatGetOrdering() line 177 in ./src/mat/order/sorder.c
[0] Object is in wrong state
[0] Not for unassembled matrix

from petsc4py import PETSc
from mpi4py import MPI
import ufl
from dolfinx import mesh, fem, default_scalar_type, io
from dolfinx.fem.petsc import assemble_matrix, assemble_vector, apply_lifting, create_vector, set_bc
import numpy as np



mu = 1
lambda_ = 1.25

rho = 1
g = 10


domain = mesh.create_unit_cube(MPI.COMM_WORLD, nx=1, ny=1, nz=1, cell_type=mesh.CellType.tetrahedron)
V = fem.functionspace(domain, ("Lagrange", 1, (domain.geometry.dim, )))

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

fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(domain, fdim, clamped_boundary)

u_D = np.array([0, 0, 0], dtype=default_scalar_type)
bc = fem.dirichletbc(u_D, fem.locate_dofs_topological(V, fdim, boundary_facets), V)

T = fem.Constant(domain, default_scalar_type((0, 0, 0)))

ds = ufl.Measure("ds", domain=domain)


def epsilon(u):
    return ufl.sym(ufl.grad(u))  # Equivalent to 0.5*(ufl.nabla_grad(u) + ufl.nabla_grad(u).T)

def sigma(u):
    return lambda_ * ufl.nabla_div(u) * ufl.Identity(len(u)) + 2 * mu * epsilon(u)


u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = ufl.inner(sigma(u), epsilon(v)) * ufl.dx

f = fem.Constant(domain, default_scalar_type((0, 0, -rho * g)))
L = ufl.dot(f, v) * ufl.dx + ufl.dot(T, v) * ds


a_compiled = fem.form(a)
L_compiled = fem.form(L)


# Assemble system, applying boundary conditions
A = assemble_matrix(a_compiled, bcs=[bc])
#A.assemble()

b = assemble_vector(L_compiled)
apply_lifting(b, [a_compiled], bcs=[[bc]])
b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
set_bc(b, [bc])

global_node = 5       # Example
b.setValue(3*global_node,   1)
b.setValue(3*global_node+1, 1)
b.setValue(3*global_node+2, 1)


# Create solution function
u = fem.Function(V)


# Solver
solver = PETSc.KSP().create(domain.comm)
solver.setOperators(A)
solver.setType(PETSc.KSP.Type.PREONLY)
solver.getPC().setType(PETSc.PC.Type.LU)

# Compute solution
uh = solver.solve(b, u.x.petsc_vec)



with io.XDMFFile(domain.comm, "deformation.xdmf", "w") as xdmf:
    xdmf.write_mesh(domain)
    uh.name = "Deformation"
    xdmf.write_function(uh)

this is the mistake. uncomment this line.

I’ve also put point sources in an extension to dolfinx:
https://scientificcomputing.github.io/scifem/examples/point_source.html
which goes through the approach in detail.

Hi Francesco,

Thank you for the prompt reply, I fixed that error and now get the following:

File ~/.local/lib/python3.10/site-packages/spyder_kernels/customize/utils.py:209 in exec_encapsulate_locals
exec_fun(compile(code_ast, filename, “exec”), globals)

File ~/files/fenics_point_loading_example.py:94
uh.name = “Deformation”

AttributeError: ‘NoneType’ object has no attribute ‘name’

This is not correct.

You should just use u for everything later in your code.

Hi Jorgen,

Thank you for including the example online. What is V.sub(1) doing? How can I pass a 3D force?

Hi Jorgen,

I was able to write the code using the point source example and installed scifem but when I run the code I get that petsc4py.typing is not installed, even when the line “from petsc4py import PETSc” works.

Traceback (most recent call last):

File ~/.local/lib/python3.10/site-packages/spyder_kernels/customize/utils.py:209 in exec_encapsulate_locals
exec_fun(compile(code_ast, filename, “exec”), globals)

File ~/files/fenics_point_loading_example_2.py:19
import scifem

File ~/.local/lib/python3.10/site-packages/scifem/init.py:11
from .solvers import NewtonSolver

File ~/.local/lib/python3.10/site-packages/scifem/solvers.py:11
import petsc4py.typing

ModuleNotFoundError: No module named ‘petsc4py.typing’

The code is the following:

from petsc4py import PETSc
from mpi4py import MPI
import ufl
from dolfinx import mesh, fem, default_scalar_type, io, la
from dolfinx.fem import Function, form, petsc
from dolfinx.fem.petsc import assemble_matrix, apply_lifting
import numpy as np

import scifem



mu = 1
lambda_ = 1.25


domain = mesh.create_unit_cube(MPI.COMM_WORLD, nx=1, ny=1, nz=1, cell_type=mesh.CellType.tetrahedron)
V = fem.functionspace(domain, ("Lagrange", 1, (domain.geometry.dim, )))

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

fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(domain, fdim, clamped_boundary)

u_D = np.array([0, 0, 0], dtype=default_scalar_type)
bc = fem.dirichletbc(u_D, fem.locate_dofs_topological(V, fdim, boundary_facets), V)

T = fem.Constant(domain, default_scalar_type((0, 0, 0)))

ds = ufl.Measure("ds", domain=domain)


def epsilon(u):
    return ufl.sym(ufl.grad(u))  # Equivalent to 0.5*(ufl.nabla_grad(u) + ufl.nabla_grad(u).T)

def sigma(u):
    return lambda_ * ufl.nabla_div(u) * ufl.Identity(len(u)) + 2 * mu * epsilon(u)


u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = ufl.inner(sigma(u), epsilon(v)) * ufl.dx

L = ufl.dot(T, v) * ds






b = Function(V)
b.x.array[:] = 0


geom_dtype = domain.geometry.x.dtype
if domain.comm.rank == 0:
    points = np.array([[1.0, 1.0, 1.0]], dtype=geom_dtype)
else:
    points = np.zeros((0, 3), dtype=geom_dtype)



point_source = scifem.PointSource(V, points, magnitude=np.array([[1.0, 1.0, 1.0]], dtype=geom_dtype))
point_source.apply_to_vector(b)


a_compiled = form(a)
apply_lifting(b.vector, [a_compiled], [[bc]])
b.x.scatter_reverse(la.InsertMode.add)
petsc.set_bc(b.vector, [bc])
b.x.scatter_forward()


A = assemble_matrix(a_compiled, bcs=[bc])
A.assemble()


ksp = PETSc.KSP().create(domain.comm)
ksp.setOperators(A)
ksp.setType(PETSc.KSP.Type.PREONLY)
ksp.getPC().setType(PETSc.PC.Type.LU)
ksp.getPC().setFactorSolverType("mumps")


# Compute solution
uh = Function(V)
ksp.solve(b.vector, uh.vector)
uh.x.scatter_forward()




with io.XDMFFile(domain.comm, "deformation.xdmf", "w") as xdmf:
    xdmf.write_mesh(domain)
    uh.name = "Deformation"
    xdmf.write_function(uh)

Hi Jorgen,

Thank you I got the code running that updates b using the node number (below). I’m still not able to use PointSource(), I think the error has to do with scifem not installing correctly.

Two last questions:

What is the difference between using create_vector(b) and assemble_vector(b)?

In the code below I am assuming in the Ku = b linear system the u vector is arranged following the geometry node numbering with each dof like this:

node1.x
node1.y
node1.z
node2.x
node2.y
node2.z

Is this correct? Is this always the case? I know some solvers might change this ordering to modify K, is this not the case for linear solvers?

from petsc4py import PETSc
from mpi4py import MPI
import ufl
from dolfinx import mesh, fem, default_scalar_type, io
from dolfinx.fem.petsc import assemble_matrix, assemble_vector, apply_lifting, create_vector, set_bc
import numpy as np


mu = 1
lambda_ = 1.25


domain = mesh.create_unit_cube(MPI.COMM_WORLD, nx=1, ny=1, nz=1, cell_type=mesh.CellType.tetrahedron)
V = fem.functionspace(domain, ("Lagrange", 1, (domain.geometry.dim, )))


# Extract the coordinates of the nodes associated with the facets
node_coords = domain.geometry.x
print(str(node_coords) + '\n')



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

fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(domain, fdim, clamped_boundary)

u_D = np.array([0, 0, 0], dtype=default_scalar_type)
bc = fem.dirichletbc(u_D, fem.locate_dofs_topological(V, fdim, boundary_facets), V)

T = fem.Constant(domain, default_scalar_type((0, 0, 0)))

ds = ufl.Measure("ds", domain=domain)


def epsilon(u):
    return ufl.sym(ufl.grad(u))  # Equivalent to 0.5*(ufl.nabla_grad(u) + ufl.nabla_grad(u).T)

def sigma(u):
    return lambda_ * ufl.nabla_div(u) * ufl.Identity(len(u)) + 2 * mu * epsilon(u)


u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = ufl.inner(sigma(u), epsilon(v)) * ufl.dx

L = ufl.dot(T, v) * ds


a_compiled = fem.form(a)
L_compiled = fem.form(L)


# Assemble system, applying boundary conditions
A = assemble_matrix(a_compiled, bcs=[bc])
A.assemble()

b = assemble_vector(L_compiled)
apply_lifting(b, [a_compiled], bcs=[[bc]])
b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
set_bc(b, [bc])

global_node = 3   # Chosen based on printed node coordinates
b.setValue(3*global_node,   1.0)
b.setValue(3*global_node+1, 1.0)
b.setValue(3*global_node+2, 1.0)


# Create solution function
uh = fem.Function(V)


# Solver
solver = PETSc.KSP().create(domain.comm)
solver.setOperators(A)
solver.setType(PETSc.KSP.Type.PREONLY)
solver.getPC().setType(PETSc.PC.Type.LU)


# Compute solution
solver.solve(b, uh.x.petsc_vec)


with io.XDMFFile(domain.comm, "deformation.xdmf", "w") as xdmf:
    xdmf.write_mesh(domain)
    uh.name = "Deformation"
    xdmf.write_function(uh)

That means a force in y direction.

A 3D source would just be passing in V,
and the magnitude of the source. If you need different magnitudes in different direction, create one point source per dimension.

This seems to be due to some typing introduced by @Henrik_Nicolay_Finsb or @RemDelaporteMathurin when adding the Newton solver. We should probably revert this.

One allocates storage space for the rhs vector.
The other computes the integral of a form (not the same b).

No. You shouldn’t assume this. This is the whole point of the point source class of scifem.

Hi, why was this other Fenics user assuming this?

For a blocked space, i.e. created with a shape parameter, each degree of freedom will be ordered as dof0_x, dof0_y, dof0_z.

However, for general spaces this is not true.

I still don’t know why you are talking about manual modification of the vector/matrix, as I showed, you can use scifem for this.

You can even inspect the source code if you want to see how it applies the point source to a vector:scifem/src/scifem/point_source.py at main · scientificcomputing/scifem · GitHub

What version of dolfinx are you running and how did you install it?

I have created a PR here: Remove petsc4py.typing by finsberg · Pull Request #61 · scientificcomputing/scifem · GitHub to revert the change.

Would be good to submit this an an issue on GitHub next time rather than asking here on discourse :wink: