Combining the forces and moments compartments to form a solution vector U for a structural problem

‘’’
dolfinx: 0.8.0
‘’’

import numpy as np

# Define the 3x3 force and rotation matrices
K_fx = np.array([[K_fxfx, K_fxfy, K_fxfz],
                 [K_fyfx, K_fyfy, K_fyfz],
                 [K_fzfx, K_fzfy, K_fzfz]])

K_mx = np.array([[K_mxfx, K_mxfy, K_mxfz],
                 [K_myfx, K_myfy, K_myfz],
                 [K_mzfx, K_mzfy, K_mzfz]])

# Initialize a 6x6 zero matrix
K_combined = np.zeros((6, 6))

# Place the 3x3 matrices in the appropriate positions
K_combined[:3, :3] = K_fx
K_combined[3:, 3:] = K_mx

print(K_combined)

\[
\begin{bmatrix}
K_{fxfx} & K_{fxfy} & K_{fxfz} & K_{fxmx} & K_{fxmy} & K_{fxmz} \\
K_{fyfx} & K_{fyfy} & K_{fyfz} & K_{fymx} & K_{fymy} & K_{fymz} \\
K_{fzfx} & K_{fzfy} & K_{fzfz} & K_{fzmx} & K_{fzmy} & K_{fzmz} \\
K_{mxfx} & K_{mxfy} & K_{mxfz} & K_{mxmx} & K_{mxmy} & K_{mxmz} \\
K_{myfx} & K_{myfy} & K_{myfz} & K_{mymx} & K_{mymy} & K_{mymz} \\
K_{mzfx} & K_{mzfy} & K_{mzfz} & K_{mzmx} & K_{mzmy} & K_{mzmz}
\end{bmatrix}
\]

So then, I have this formula, which is supposed to be a description of how to combine the forces and moments compartments, and I am really not sure if this is the correct way yet of doing things however it does look like it has a chance to work so far in my assesment…

Is it possible to do this type of formulation with dolfinx?

So far I made an attempt at doing a similar type of thing but there seems to be a difficulty at solution time. I’m not sure what went wrong here yet, so far the ufl system is still a bit mysterious to me and I have the gist of what those computations mean but there must be something thats causing a congruency and or shape issue at solution time?

Could I get some help with this?


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

from dolfinx.fem import dirichletbc, locate_dofs_geometrical


L = 72
W = 4.0

# Define the mesh with tetrahedral cells
domain = mesh.create_box(MPI.COMM_WORLD, [np.array([0, 0, 0]), np.array([L, W, W])],
                         [20, 6, 6], cell_type=mesh.CellType.tetrahedron)

# Define elements for displacements and rotations
element_disp = basix.ufl.element("Lagrange", domain.topology.cell_name(), 1, shape=(3,))
element_rot = basix.ufl.element("Lagrange", domain.topology.cell_name(), 1, shape=(3,))

# Create function spaces for displacements and rotations
V_disp = fem.functionspace(domain, element_disp)
V_rot = fem.functionspace(domain, element_rot)

# Create functions to hold the force vectors at each node
force_disp = fem.Function(V_disp)
force_rot = fem.Function(V_rot)

# Initialize the force vectors to zero
force_disp.vector.set(0.3)
force_rot.vector.set(0.0)

# Define functions for displacement and rotation
u_disp = fem.Function(V_disp)
u_rot = fem.Function(V_rot)
v_disp = fem.Function(V_disp)
v_rot = fem.Function(V_rot)

# Define UFL expressions for the displacement and rotation components
grad_u_disp = ufl.grad(u_disp)  # Gradient of displacement u
grad_v_disp = ufl.grad(v_disp)  # Gradient of displacement v

# Symmetric part of the gradient (strain tensor) for displacements
epsilon_u = ufl.sym(grad_u_disp)
epsilon_v = ufl.sym(grad_v_disp)

# Bilinear form for displacements
a_disp = fem.form(ufl.inner(epsilon_u, epsilon_v) * ufl.dx)

# (Example) Bilinear form for rotations, assuming small rotations
a_rot = fem.form(ufl.inner(u_rot, v_rot) * ufl.dx)

# Total bilinear form (sum of displacement and rotation contributions)
a = fem.form(ufl.inner(epsilon_u, epsilon_v) * ufl.dx + ufl.inner(u_rot, v_rot) * ufl.dx)



# Define the linear form (force vector)
L_disp = fem.form(ufl.dot(force_disp, v_disp) * ufl.dx)
L_rot = fem.form(ufl.dot(force_rot, v_rot) * ufl.dx)

# Define the total linear form
L = fem.form(ufl.dot(force_disp, v_disp) * ufl.dx + ufl.dot(force_rot, v_rot) * ufl.dx)



# Define the boundary condition
def boundary(x):
    return np.isclose(x[0], 0)

# Define the Dirichlet boundary value for vector-valued spaces
zero_disp = fem.Constant(domain, PETSc.ScalarType((0.0, 0.0, 0.0)))
zero_rot = fem.Constant(domain, PETSc.ScalarType((0.0, 0.0, 0.0)))

# Apply Dirichlet boundary conditions to the displacements and rotations
bc_disp = dirichletbc(zero_disp, locate_dofs_geometrical(V_disp, boundary), V_disp)
bc_rot = dirichletbc(zero_rot, locate_dofs_geometrical(V_rot, boundary), V_rot)




# Combine boundary conditions
bcs = [bc_disp, bc_rot]

# Define the linear problem
problem = LinearProblem(a, L, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu"})

# Solve the linear problem
u_sol = problem.solve()

# Split the solution into displacement and rotation components
u_disp, u_rot = u_sol.split()

# Output the results
print(f"Displacement:\n{u_disp.x.array}")
print(f"Rotation:\n{u_rot.x.array}")

print(f'dolfinx version: {dolfinx.__version__}')

To create a linear form, you need a test function and a trial function. Right now, your u_disp u_rot v_disp v_rot are all Function. The latter two should be test and the former trial.

It looks like you can achieve what you want to achieve by using a mixed formulation: you create two function spaces, one for the u_disp & v_disppair of trial/test functions, and one for the u_rot & v_rot pair. “Mixing” those together into one mixed function space, tells Fenics to create the bigger matrix.

Take a look at: Mixed formulation for the Poisson equation — DOLFINx 0.9.0 documentation

1 Like

Please also write the equations you would like to solve. This seems like a Cosserat model to me but I don’t see any elastic constitutive equations etc. Maybe the closer to your needs is the following plate demo: Reissner-Mindlin plates — Computational Mechanics Numerical Tours with FEniCSx
Note that we use a mixed function space as mentioned by @Stein to solve both displacements and rotations in a single problem.

2 Likes

OK… I have a mixed problem set up and some form of elasticity equations set up to run with. So far the difficulty I am having is with the set up of Dirichlet boundary conditions. There is a traceback related to setting up the boundary conditions this way. I tried my best to follow the mixed poissons example in terms of setting boundary conditions so far. See code:


import dolfinx
from dolfinx import mesh, fem
from mpi4py import MPI
import basix
import numpy as np
from petsc4py import PETSc
import ufl
from dolfinx.fem.petsc import LinearProblem
from dolfinx.fem import dirichletbc, locate_dofs_topological
from ufl import Measure, SpatialCoordinate, TestFunctions, TrialFunctions, div, inner, dot

# Material properties
E = 2000000.0  # Young's modulus in PSI (for structural wood)
nu = 0.299999  # Poisson's ratio (for structural wood)
L = 72
W = 4.0

# Define the mesh with tetrahedral cells
domain = mesh.create_box(MPI.COMM_WORLD, [np.array([0, 0, 0]), np.array([L, W, W])],
                         [20, 6, 6], cell_type=mesh.CellType.tetrahedron)

# Define elements for displacements (3 DOF) and rotations (3 DOF)
elem_disp = basix.ufl.element("Lagrange", domain.topology.cell_name(), 1, shape=(3,))
elem_rot = basix.ufl.element("Lagrange", domain.topology.cell_name(), 1, shape=(3,))
elem_mixed = basix.ufl.mixed_element([elem_disp, elem_rot])

V = fem.functionspace(domain, elem_mixed)

# Strain tensor as a symmetric gradient of the displacement field
def epsilon(u):
    return 0.5 * (ufl.grad(u) + ufl.grad(u).T)

# Stress tensor derived from Hooke's law
def sigma(u):
    lambda_ = E * nu / ((1 + nu) * (1 - 2 * nu))  # First Lamé parameter
    mu = E / (2 * (1 + nu))  # Shear modulus (second Lamé parameter)
    return lambda_ * ufl.tr(epsilon(u)) * ufl.Identity(3) + 2 * mu * epsilon(u)

# Define trial and test functions for displacements (u) and rotations (theta)
(u, theta) = TrialFunctions(V)
(v, tau) = TestFunctions(V)

# Weak form for the elasticity problem (displacement part)
a_disp = inner(sigma(u), epsilon(v)) * ufl.dx

# Rotational-related terms (rotations theta and tau)
a_rot = inner(theta, tau) * ufl.dx

# Coupling terms between displacement and rotation (optional)
a_coupling = inner(theta, v) * ufl.dx + inner(u, tau) * ufl.dx

# Final variational form: combining displacements, rotations, and coupling
a = a_disp + a_rot + a_coupling

# Define body forces (e.g., gravity or applied loads)
f = fem.Constant(domain, PETSc.ScalarType((0, 0, -10.0)))  # Example: downward force (gravity)
l = inner(f, v) * ufl.dx  # RHS corresponds to the body force acting on displacements

# Get subspace of V
V0 = V.sub(0)
Q, _ = V0.collapse()
fdim = domain.topology.dim - 1

# Modify the location to find the left boundary (x = 0)
def locate_left_boundary(domain):
    fdim = 2
    # Locate the entities where x=0 (left boundary)
    return mesh.locate_entities_boundary(domain, fdim, lambda x: np.isclose(x[0], 0.0))

f_h1 = fem.Function(Q)
f_h1.interpolate(locate_left_boundary)

# Apply boundary condition at the left boundary where x = 0
#facets_left = locate_left_boundary(domain)
#dofs_left = fem.locate_dofs_topological((V0, Q), fdim, facets_left)
# Apply the boundary conditions (only displacement fixed)
#problem = LinearProblem(a, l, bcs=[bc_top_disp])

print('done...')


Traceback (most recent call last):
  File "/home/prusso/spack/var/spack/environments/fenicsx-env/.spack-env/view/lib/python3.10/site-packages/dolfinx/fem/function.py", line 492, in interpolate
    _interpolate(u, cells)
  File "/home/prusso/spack/opt/spack/linux-ubuntu22.04-skylake/gcc-11.4.0/python-3.10.0-fpeegr4yc3u64wwhfac5ljsna5dbz6km/lib/python3.10/functools.py", line 878, in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
  File "/home/prusso/spack/var/spack/environments/fenicsx-env/.spack-env/view/lib/python3.10/site-packages/dolfinx/fem/function.py", line 455, in _interpolate
    self._cpp_object.interpolate(u, cells, nmm_interpolation_data)  # type: ignore
TypeError: interpolate(): incompatible function arguments. The following argument types are supported:
    1. interpolate(self, f: ndarray[dtype=float64, writable=False, shape=(*), order='C'], cells: ndarray[dtype=int32, writable=False, shape=(*), order='C']) -> None
    2. interpolate(self, f: ndarray[dtype=float64, writable=False, shape=(*, *), order='C'], cells: ndarray[dtype=int32, writable=False, shape=(*), order='C']) -> None
    3. interpolate(self, u: dolfinx.cpp.fem.Function_float64, cells: ndarray[dtype=int32, writable=False, shape=(*), order='C'], cell_map: ndarray[dtype=int32, writable=False, shape=(*), order='C'], nmm_interpolation_data: tuple[ndarray[dtype=int32, writable=False, shape=(*), order='C'], ndarray[dtype=int32, writable=False, shape=(*), order='C'], ndarray[dtype=float64, writable=False, shape=(*), order='C'], ndarray[dtype=int32, writable=False, shape=(*), order='C']]) -> None
    4. interpolate(self, expr: dolfinx.cpp.fem.Expression_float64, cells: ndarray[dtype=int32, writable=False, order='C'], expr_mesh: dolfinx.cpp.mesh.Mesh_float64, cell_map: ndarray[dtype=int32, writable=False, order='C']) -> None

Invoked with types: dolfinx.cpp.fem.Function_float64, function, ndarray, dolfinx.fem.function.PointOwnershipData

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/prusso/dolfinx-beam_w/phils_take009.py", line 71, in <module>
    f_h1.interpolate(locate_left_boundary)
  File "/home/prusso/spack/var/spack/environments/fenicsx-env/.spack-env/view/lib/python3.10/site-packages/dolfinx/fem/function.py", line 497, in interpolate
    self._cpp_object.interpolate(np.asarray(u(x), dtype=self.dtype), cells)  # type: ignore
  File "/home/prusso/dolfinx-beam_w/phils_take009.py", line 68, in locate_left_boundary
    return mesh.locate_entities_boundary(domain, fdim, lambda x: np.isclose(x[0], 0.0))
  File "/home/prusso/spack/var/spack/environments/fenicsx-env/.spack-env/view/lib/python3.10/site-packages/dolfinx/mesh.py", line 264, in locate_entities_boundary
    return _cpp.mesh.locate_entities_boundary(mesh._cpp_object, dim, marker)
AttributeError: 'numpy.ndarray' object has no attribute '_cpp_object'

Seems to be a missing _cpp object, which I get sometimes if there is a syntactical error. Really I’m not sure what to make out this yet or how much I have correct here and if this will work. So far the strain and stress tensor functions seem reasonable enough but in order for me to know anything I need to start to look at how they look and also where to start to find formula sheets of exactly what those should be for this type of 3D problem.

It looks like so far if I can get past the traceback setting up Dirichlets a solution can maybe be formed from this. So far I would like a simply supported Dirichlet in other words translational forces should be fixed and rotational forces should travel through.

What is wrong with the Dirichlet code and why this traceback? Could I get some idea what I may have wrong here other than that in terms of obtaining a solution?

Just FYI as an aside. There does seem to be a way to get past the Dirichlet problem by use of a function to declare it. So then, now I have a different issue that’s throwing exception at solve time that has to do with the inner workings of MPI. I guess I should also post this since I have no way of solving it at this time and the messaging is nothing I am familiar with a whole lot:


import dolfinx
from dolfinx import mesh, fem
from mpi4py import MPI
import basix
import numpy as np
from petsc4py import PETSc
import ufl
from dolfinx.fem.petsc import LinearProblem
from dolfinx.fem import dirichletbc, locate_dofs_topological
from ufl import Measure, SpatialCoordinate, TestFunctions, TrialFunctions, div, inner, dot

# Material properties
E = 2000000.0  # Young's modulus in PSI (for structural wood)
nu = 0.299999  # Poisson's ratio (for structural wood)
L = 72
W = 4.0

# Define the mesh with tetrahedral cells
domain = mesh.create_box(MPI.COMM_WORLD, [np.array([0, 0, 0]), np.array([L, W, W])],
                         [20, 6, 6], cell_type=mesh.CellType.tetrahedron)

# Define elements for displacements (3 DOF) and rotations (3 DOF)
elem_disp = basix.ufl.element("Lagrange", domain.topology.cell_name(), 1, shape=(3,))
elem_rot = basix.ufl.element("Lagrange", domain.topology.cell_name(), 1, shape=(3,))
elem_mixed = basix.ufl.mixed_element([elem_disp, elem_rot])

V = fem.functionspace(domain, elem_mixed)

# Strain tensor as a symmetric gradient of the displacement field
def epsilon(u):
    return 0.5 * (ufl.grad(u) + ufl.grad(u).T)

# Stress tensor derived from Hooke's law
def sigma(u):
    lambda_ = E * nu / ((1 + nu) * (1 - 2 * nu))  # First Lamé parameter
    mu = E / (2 * (1 + nu))  # Shear modulus (second Lamé parameter)
    return lambda_ * ufl.tr(epsilon(u)) * ufl.Identity(3) + 2 * mu * epsilon(u)

# Define trial and test functions for displacements (u) and rotations (theta)
(u, theta) = TrialFunctions(V)
(v, tau) = TestFunctions(V)

# Weak form for the elasticity problem (displacement part)
a_disp = inner(sigma(u), epsilon(v)) * ufl.dx

# Rotational-related terms (rotations theta and tau)
a_rot = inner(theta, tau) * ufl.dx

# Coupling terms between displacement and rotation (optional)
a_coupling = inner(theta, v) * ufl.dx + inner(u, tau) * ufl.dx

# Final variational form: combining displacements, rotations, and coupling
a = a_disp + a_rot + a_coupling

# Define body forces (e.g., gravity or applied loads)
f = fem.Constant(domain, PETSc.ScalarType((0, 0, -10.0)))  # Example: downward force (gravity)
l = inner(f, v) * ufl.dx  # RHS corresponds to the body force acting on displacements

# Get subspace of V
V0 = V.sub(0)
Q, _ = V0.collapse()
fdim = domain.topology.dim - 1

# Locate the entities where x=0 (left boundary)
def locate_left_boundary(x):
    return np.isclose(x[0], 0.0)

facets_left = mesh.locate_entities_boundary(domain, fdim, locate_left_boundary)

# Apply boundary condition at the left boundary where x = 0
dofs_left = locate_dofs_topological(V0, fdim, facets_left)

# Create a function for the boundary condition
u_left = fem.Function(Q)
with u_left.vector.localForm() as loc:
    loc.set(0.0)  # Zero displacement in all directions

bc_left = dirichletbc(u_left, dofs_left)

# Create the linear problem and solve it
problem = LinearProblem(a, l, bcs=[bc_left])
uh = problem.solve()

print('done...')

# Finalize MPI
MPI.Finalize()


*** The MPI_Comm_get_attr() function was called after MPI_FINALIZE was invoked.
*** This is disallowed by the MPI standard.
*** Your MPI job will now abort.
[dragon:17509] Local abort after MPI_FINALIZE started completed successfully, but am not able to aggregate error messages, and not able to guarantee that all other processes were killed!

So I looked this over so far in some cursory way, and so far I see a situation of hope and nothing really struck me yet as very awry to my eye yet but that could change…

Oh… Sorry about that… It looks like what needs to happen there is just a removal of MPI.Finalize()…