How to access the z-component of a vector function defined on a mixed function space?

I am in the process of learning two-field methods with the purpose of avoiding the issue of volumetric locking. I haven’t implemented a two-field method in FEniCS yet. This is my first try. I read a lot of posts and books and developed the following MWE with information collected from various posts.

In the following MWE, I have an annulus with inner radius 1 and outer radius 2. I assigned the physical tag 1 to the inner boundary and 2 to the outer boundary. I have assigned the physical tag 5 to the annular region. I created my geometries in Gmsh.

The MWE has two parts: (1) solves a hyperelastic problem using a single vector function space (works fine), (2) tries to solve the same problem using a mixed function space (spits errors).

The main feature of both the parts is that I am imposing a DirichletBC to make the third component of the displacement vector 0 to avoid any axial stretch. Thanks to @Kei_Yamamoto, @conpierce8, and @dokken, I am able to do it successfully for problem (1) with their suggestions in How to keep the third component of the displacement vector 0 while solving a hyperelasticity problem? - #11 by avishmj

However, for problem (2), I am not able to apply the same strategy.

Here is the MWE:

from dolfin import *     
import meshio
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from ufl import cofac, sqrt
from scipy import linalg as la
from ufl import *
from ufl.classes import *
import pandas as pd

# Optimization options for the form compiler
parameters["form_compiler"]["cpp_optimize"] = True
ffc_options = {"optimize": True, \
               "eliminate_zeros": True, \
               "precompute_basis_const": True, \
               "precompute_ip_const": True}

import numpy
def create_mesh(mesh, cell_type, prune_z=False):
    cells = mesh.get_cells_type(cell_type)
    cell_data = mesh.get_cell_data("gmsh:physical", cell_type)
    out_mesh = meshio.Mesh(points=mesh.points, cells={cell_type: cells}, cell_data={"name_to_read":[cell_data]})    
    return out_mesh
    
######################################################################################################################

msh = meshio.read("singleannulus.msh")

######################################################################################################################

triangle_mesh = create_mesh(msh, "triangle", True)
line_mesh = create_mesh(msh, "line", True)
meshio.write("mesh.xdmf", triangle_mesh)
meshio.write("mf.xdmf", line_mesh) 
from dolfin import *
mesh = Mesh()
xdmf = XDMFFile(mesh.mpi_comm(),"mesh.xdmf")
xdmf.read(mesh)
mvc = MeshValueCollection("size_t", mesh, mesh.topology().dim())
with XDMFFile("mesh.xdmf") as infile:
   infile.read(mvc, "name_to_read")
cf = cpp.mesh.MeshFunctionSizet(mesh, mvc)
xdmf.close()

mvc = MeshValueCollection("size_t", mesh, mesh.topology().dim()-1)
with XDMFFile("mf.xdmf") as infile:
    infile.read(mvc, "name_to_read")
mf = cpp.mesh.MeshFunctionSizet(mesh, mvc)

ds = Measure("dS", domain=mesh, subdomain_data=mf) # Defining global boundary measure, for global surface measure, plug in "dS" instead of "ds", which is used for subdomain                                                                                                              boundary measures
dx = Measure("dx", domain=mesh, subdomain_data=cf)

###################################### DEFING A SINGLE VECTOR FUNCTION SPACE ###########################################

V_ima = VectorFunctionSpace(mesh, 'P', 1)

###############################   DEFINING MATERIAL PARAMETERS ########################

nu1 = 0.4
mu1 = 27.9 

############################### (1): SOLVING FOR KINEMATICS USING THE SIMPLE VECTOR FUNCTION SPACE  #####################

u1 = Function(V_ima)
v1 = TestFunction(V_ima)

d = mesh.geometry().dim()
I = Identity(d)             # Identity tensor
F = I + grad(u1)             # Deformation gradient  u = (u1, u2, 0)
Fe = variable(F) 
Ce = Fe.T*Fe
Je = det(Fe)
I1 = tr(Ce)
M = cofac(F)

# Stored strain energy density (incompressible neo-Hookean model)
psi1 = (mu1/2)*(I1 - 3) + ((nu1)*(mu1)/(1-2*nu1))*(Je-1)**2 - mu1*(ln(Je))

TT_i = diff(psi1,Fe)

Pi = inner(TT_i,grad(v1))*dx(5)

all_domains = MeshFunction("size_t", mesh, mesh.topology().dim() - 1, 1)
bcs = DirichletBC(V_ima.sub(2), Constant(0.0), all_domains, 1)     # Making the Z-component of displacement 0 

solve(Pi == 0, u1 , bcs,
     solver_parameters={"newton_solver": {"relative_tolerance": 5e-9,   
     "absolute_tolerance": 5e-9,"maximum_iterations": 10}})
     
print(f"Solved IMA displacement using a single vector function space")

#############################  SETTING UP THE SAME KINEMATICS WITH MIXED FUNCTION SPACE #############################

############################### DEFINING A MIXED FUNCTION SPACE ######################

P22 = VectorElement("P", mesh.ufl_cell(), 4)
P11 = FiniteElement("P", mesh.ufl_cell(), 1)
TH = MixedElement([P22,P11])
W = FunctionSpace(mesh, TH)

(u2, p2) = TrialFunctions(W)
(v2, q2) = TestFunctions(W)

############################## (2): SOLVING FOR KINEMATICS USING MIXED FUNCTION SPACE ##########################

d = mesh.geometry().dim()
I = Identity(d)             # Identity tensor
F = I + grad(u2)             # Deformation gradient  u = (u1, u2, 0)
Fe = variable(F) 
Ce = Fe.T*Fe
Je = det(Fe)
I1 = tr(Ce)
M = cofac(F)

# Stored strain energy density (incompressible neo-Hookean model)
psi1 = (mu1/2)*(I1 - 3) + p2*(Je-1)**2 - mu1*(ln(Je))

TT_i = diff(psi1,Fe)

Pi = inner(TT_i,grad(v2))*dx(5)

U = Function(W)

u2, p2 = U.split()

all_domains = MeshFunction("size_t", mesh, mesh.topology().dim() - 1, 1)
bcs = DirichletBC(W.sub(2), Constant(0.0), all_domains, 1)       # The error says it cannot extract the subspace

solve(Pi == 0, U , bcs,
     solver_parameters={"newton_solver": {"relative_tolerance": 5e-9,   
     "absolute_tolerance": 5e-9,"maximum_iterations": 10}})
     
print(f"Solved IMA displacement using a mixed function space")

u2_, p2_ = U.split()
#############################################################################################################

The second problem (2) spits the following error:

 bcs = DirichletBC(W.sub(2), Constant(0.0), all_domains, 1)  
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfin/function/functionspace.py", line 119, in sub
    raise ValueError("Can only extract SubSpaces with i = 0 ... %d" %
ValueError: Can only extract SubSpaces with i = 0 ... 1

I tried W.sub(0), which gives the error
Error: Unable to create Dirichlet boundary condition. *** Reason: Expecting a vector-valued boundary value but given function is scalar.

I also tried W.sub(1), which gives the error
raise ArityMismatch("Adding expressions with non-matching form arguments {0} vs {1}.".format(_afmt(a), _afmt(b))) ufl.algorithms.check_arities.ArityMismatch: Adding expressions with non-matching form arguments () vs ('v_1',).

Since I am trying mixed function spaces for the first time, I am not sure why these errors happen and how to fix them.

Any suggestions would be greatly appreciated.

Thank you.
`

In case of mixed finite element, W.sub(i) is a subspace of the mixed function space, and hence subspace of P22 or P11. To get the component of the each subspace, you need to use W.sub(i).sub(i). For example, if you want to enforce u2 to have all zeros in the z-component, You have to use DirichletBC(W.sub(0).sub(2), Constant(0.0), all_domains, 1)

@Kei_Yamamoto ,

I tried that too. That raises the error

raise ArityMismatch("Adding expressions with non-matching form arguments {0} vs {1}.".format(_afmt(a), _afmt(b)))
ufl.algorithms.check_arities.ArityMismatch: Adding expressions with non-matching form arguments () vs ('v_1',).

with the code

P22 = VectorElement("P", mesh.ufl_cell(), 4)
P11 = FiniteElement("P", mesh.ufl_cell(), 1)
TH = MixedElement([P22,P11])
W = FunctionSpace(mesh, TH)

(u2, p2) = TrialFunctions(W)
(v2, q2) = TestFunctions(W)

############################## (2): SOLVING FOR KINEMATICS USING MIXED FUNCTION SPACE ##########################

d = mesh.geometry().dim()
I = Identity(d)             # Identity tensor
F = I + grad(u2)             # Deformation gradient  u = (u1, u2, 0)
Fe = variable(F) 
Ce = Fe.T*Fe
Je = det(Fe)
I1 = tr(Ce)
M = cofac(F)

# Stored strain energy density (incompressible neo-Hookean model)
psi1 = (mu1/2)*(I1 - 3) + p2*(Je-1)**2 - mu1*(ln(Je))

TT_i = diff(psi1,Fe)

Pi = inner(TT_i,grad(v2))*dx(5)

U = Function(W)

all_domains = MeshFunction("size_t", mesh, mesh.topology().dim() - 1, 1)
bcs = DirichletBC(W.sub(0).sub(2), Constant(0.0), all_domains, 1)       # The error says it cannot access the subspace

solve(Pi == 0, U , bcs,
     solver_parameters={"newton_solver": {"relative_tolerance": 5e-9,   
     "absolute_tolerance": 5e-9,"maximum_iterations": 10}})
     
print(f"Solved IMA displacement using a mixed function space")

Which part of the code gives the error message?

The solve(...) part gives the error. Here is the full error message (it’s long):

solve(Pi == 0, U , bcs,
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfin/fem/solving.py", line 233, in solve
    _solve_varproblem(*args, **kwargs)
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfin/fem/solving.py", line 308, in _solve_varproblem
    problem = NonlinearVariationalProblem(eq.lhs, u, bcs, J,
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfin/fem/problem.py", line 157, in __init__
    F = Form(F, form_compiler_parameters=form_compiler_parameters)
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfin/fem/form.py", line 43, in __init__
    ufc_form = ffc_jit(form, form_compiler_parameters=form_compiler_parameters,
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfin/jit/jit.py", line 50, in mpi_jit
    return local_jit(*args, **kwargs)
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfin/jit/jit.py", line 100, in ffc_jit
    return ffc.jit(ufl_form, parameters=p)
  File "/usr/lib/python3/dist-packages/ffc/jitcompiler.py", line 217, in jit
    module = jit_build(ufl_object, module_name, parameters)
  File "/usr/lib/python3/dist-packages/ffc/jitcompiler.py", line 130, in jit_build
    module, signature = dijitso.jit(jitable=ufl_object,
  File "/usr/lib/python3/dist-packages/dijitso/jit.py", line 165, in jit
    header, source, dependencies = generate(jitable, name, signature, params["generator"])
  File "/usr/lib/python3/dist-packages/ffc/jitcompiler.py", line 65, in jit_generate
    code_h, code_c, dependent_ufl_objects = compile_object(ufl_object,
  File "/usr/lib/python3/dist-packages/ffc/compiler.py", line 142, in compile_form
    return compile_ufl_objects(forms, "form", object_names,
  File "/usr/lib/python3/dist-packages/ffc/compiler.py", line 185, in compile_ufl_objects
    analysis = analyze_ufl_objects(ufl_objects, kind, parameters)
  File "/usr/lib/python3/dist-packages/ffc/analysis.py", line 89, in analyze_ufl_objects
    form_datas = tuple(_analyze_form(form, parameters)
  File "/usr/lib/python3/dist-packages/ffc/analysis.py", line 89, in <genexpr>
    form_datas = tuple(_analyze_form(form, parameters)
  File "/usr/lib/python3/dist-packages/ffc/analysis.py", line 169, in _analyze_form
    form_data = compute_form_data(form,
  File "/usr/lib/python3/dist-packages/ufl/algorithms/compute_form_data.py", line 407, in compute_form_data
    check_form_arity(preprocessed_form, self.original_form.arguments(), complex_mode)  # Currently testing how fast this is
  File "/usr/lib/python3/dist-packages/ufl/algorithms/check_arities.py", line 177, in check_form_arity
    check_integrand_arity(itg.integrand(), arguments, complex_mode)
  File "/usr/lib/python3/dist-packages/ufl/algorithms/check_arities.py", line 159, in check_integrand_arity
    arg_tuples = map_expr_dag(rules, expr, compress=False)
  File "/usr/lib/python3/dist-packages/ufl/corealg/map_dag.py", line 36, in map_expr_dag
    result, = map_expr_dags(function, [expression], compress=compress,
  File "/usr/lib/python3/dist-packages/ufl/corealg/map_dag.py", line 99, in map_expr_dags
    r = handlers[v._ufl_typecode_](v, *[vcache[u] for u in v.ufl_operands])
  File "/usr/lib/python3/dist-packages/ufl/algorithms/check_arities.py", line 48, in sum
    raise ArityMismatch("Adding expressions with non-matching form arguments {0} vs {1}.".format(_afmt(a), _afmt(b)))
ufl.algorithms.check_arities.ArityMismatch: Adding expressions with non-matching form arguments () vs ('v_1',).

Then I think there is something wrong with Pi and not the way you define bcs

@Kei_Yamamoto I think so too. But if you look at problem (1) that I solved in the post, the Pi is formulated in the same way and problem (1) works. I am not sure why I need to change the form when dealing with a mixed function space.

\Pi = \partial \Psi(\vec{u}) / \partial \mathrm{F} is nonlinear. You’ve formulated your problem as if it were linear with the unknown FE function as a ufl.TrialFunction. Consider the tutorial section on Nonlinear problems, or refer back to the hyperelasticity example.

1 Like

Hi @nate ,

Thanks for your reply. After reading the post, I reconfigured the MWE. It’s working now. I just want to make sure that I am doing things right. I would greatly appreciate if you could take a look at the below MWE and let me know if I am defining the function spaces and the functions correctly. I am not concerned about the form for \Pi but I want to make sure that I am using the correct test functions in the formulation of it.

############################### DEFINING A MIXED FUNCTION SPACE ######################

P22 = VectorElement("P", mesh.ufl_cell(), 2)
P11 = FiniteElement("P", mesh.ufl_cell(), 1)
TH = MixedElement([P22,P11])
W = FunctionSpace(mesh, TH)


N = FacetNormal(mesh)
############################## (2): SOLVING FOR KINEMATICS USING MIXED FUNCTION SPACE ##########################

UU = Function(W)
u2, p2 =split(UU)

v2, q2 = TestFunctions(W)

d = mesh.geometry().dim()

I = Identity(d)             # Identity tensor
F = I + grad(u2)           # Deformation gradient  u = (u1, u2, 0)
Fe = variable(F) 
Ce = Fe.T*Fe
Je = det(Fe)
I1 = tr(Ce)
M = cofac(F)
J = det(F)

# Stored strain energy density (incompressible neo-Hookean model)
psi1 = (mu1/2)*(I1 - 3) + p2*(Je-1)**2  - mu1*(ln(Je)) 

TT_i = diff(psi1,Fe)

Pi = (J*p2*div(v2) + inner(2*TT_i, div(v2)*Ce))*dx(5)

all_domains = MeshFunction("size_t", mesh, mesh.topology().dim() - 1, 1)
bcs = DirichletBC(W.sub(0).sub(2), Constant(0.0), all_domains, 1)       

solve(Pi == 0, UU , bcs,
   solver_parameters={"newton_solver": {"relative_tolerance": 5e-9,   
   "absolute_tolerance": 5e-9,"maximum_iterations": 10}})
   
print(f"Solved IMA displacement using a mixed function space")

u2, p2 = UU.split()

Please let me know if I am doing anything wrong here.