Real Functionspace is not working

Dear everyone:

To constrain the electric potential equal on the top surface, I defined the submesh based on the top surface, and set Real functionspace for this part submesh for the reason I thought the Real functionspace would only have one constant in its region. And then I used langrange multiplier to tie the
dof in submesh of the top surface to the main solid mesh.
But the potential result of the top surface is still not uniform and is the same as the result without any constraint. Is there anything wrong with what I am thinking about R0 functionspace?

I attached my result of potential contour and my code:


from __future__ import print_function
from fenics import *
from dolfin import *
from ufl import nabla_div
import numpy

# Scaled variables
# L = 1
# W = 0.2  # L is length; W is width

rho = 5550
g = 9.8

# define elasticity tensor C
c11 = 226e9
c12 = 125e9
c13 = 124e9
c33 = 216e9
c44 = 44.2e9  # * 2
c66 = (c11 - c12) / 2  # * 2
# 6x6
C = as_matrix([[c11, c12, c13, 0.0, 0.0, 0.0],
               [c12, c11, c13, 0.0, 0.0, 0.0],
               [c13, c13, c33, 0.0, 0.0, 0.0],
               [0.0, 0.0, 0.0, c44, 0.0, 0.0],
               [0.0, 0.0, 0.0, 0.0, c44, 0.0],
               [0.0, 0.0, 0.0, 0.0, 0.0, c66]])

# define piezoelectricity tensor e
e15 = 5.8
e31 = -2.2
e33 = 9.3
# 6x3
e1 = as_matrix([[0.0, 0.0, e31],
                [0.0, 0.0, e31],
                [0.0, 0.0, e33],
                [0.0, e15, 0.0],
                [e15, 0.0, 0.0],
                [0.0, 0.0, 0.0]])

# define permittivity tensor p
p11 = 5.64e-9
p33 = 6.35e-9
# 3x3
p = as_matrix([[p11, 0.0, 0.0],
               [0.0, p11, 0.0],
               [0.0, 0.0, p33]])


# rewrite the tensor into a vector using Voigt notation
def as_voigt_vector(ten):
    # FEniCS does not know anything about Voigt notation,
    # so one need to access the components directly as eps[0, 0] etc.
    # return as_vector([ten[0, 0], ten[1, 1], ten[2, 2], ten[1, 2], ten[0, 2], ten[0, 1]])
    return as_vector([ten[0, 0], ten[1, 1], ten[2, 2], 2 * ten[1, 2], 2 * ten[0, 2], 2 * ten[0, 1]])


def as_voigt_vector2(ten):
    # FEniCS does not know anything about Voigt notation,
    # so one need to access the components directly as eps[0, 0] etc.
    return as_vector([ten[0], ten[1], ten[2]])


# rewrite a vector into a tensor using Voigt notation
def from_voigt_vector(vec):
    # FEniCS does not know anything about Voigt notation,
    # so one need to rewrite the vector by hand into a tensor.
    return as_tensor([[vec[0], vec[5], vec[4]],
                      [vec[5], vec[1], vec[3]],
                      [vec[4], vec[3], vec[2]]])


def from_voigt_vector2(vec):
    # FEniCS does not know anything about Voigt notation,
    # so one need to rewrite the vector by hand into a tensor.
    return as_tensor([vec[0], vec[1], vec[2]])


# first define the function for the strain tensor in fenics notation
def epsilon(u):
    return 0.5 * (nabla_grad(u) + nabla_grad(u).T)
    # return sym(nabla_grad(u))


# then define the function for the static electric field in fenics notation
def e_field(phi):
    return -nabla_grad(phi)


# now define the stress tensor in fencis notation
def sigma(u, phi):
    # calculate sigma with C-matrix in Voigt notation
    sig = C * (as_voigt_vector(epsilon(u))) - e1 * as_voigt_vector2(e_field(phi))
    # return sigma as symmetric tensor
    return from_voigt_vector(sig)


# then define the function for the electric displacement in fenics notation
def e_dis(u, phi):
    ed = e1.T * (as_voigt_vector(epsilon(u))) + p * as_voigt_vector2(e_field(phi))
    return from_voigt_vector2(ed)


# Create mesh and define function space
mesh = BoxMesh(Point(0, 0, 0), Point(1, 0.2, 0.22), 50, 10, 11)


class UpperBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[2], 0.22)


#Marking facets that are on RightBoundary and creating the corresponding submesh
boundary_marker = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_marker.set_all(0)
UpperBoundary().mark(boundary_marker, 1)
submesh = MeshView.create(boundary_marker, 1)
ds = Measure("ds", subdomain_data=boundary_marker)

V1 = VectorFunctionSpace(mesh, 'CG', 1) #For the displacement in main part
V2 = VectorFunctionSpace(submesh, 'CG', 1) #For the displacement in electrode part
Q1 = FunctionSpace(mesh, 'CG', 1) #For the potential in main part
Q2 = FunctionSpace(submesh, 'R', 0) #For the potential in electrode part
L_M1 = FunctionSpace(submesh, 'CG', 1) #For the Lagrange multiplier
L_M2 = VectorFunctionSpace(submesh, 'CG', 1) #For the Lagrange multiplier

#V displacement, Q1 electric potential, Q2 electric potential on top surface
Z = MixedFunctionSpace(V1, V2, Q1, Q2, L_M1, L_M2)
sol = Function(Z)

(u1, u2, phi1, phi2, LM1, LM2) = TrialFunctions(Z)
(v1, v2, q1  , q2  , lm1, lm2) = TestFunctions(Z)

# Define boundary condition
tol = 1E-14


def clamped_boundary(x, on_boundary):
    return on_boundary and x[0] < tol


def potential(x, on_boundary):
    return on_boundary and x[2] < tol


bc1 = DirichletBC(V1, Constant((0, 0, 0)), clamped_boundary)
bc2 = DirichletBC(Q1, Constant(0), potential)
bc = [bc1, bc2]


# Define variational problem
dV = Measure("dx", domain=Z.sub_space(0).mesh())
dL = Measure("dx", domain=Z.sub_space(3).mesh())

d = u1.geometric_dimension()  # space dimension

f = Constant((0, 0, -rho * g))
T = Constant((0, 0, 0))
a =  inner(sigma(u1, phi1), epsilon(v1)) * dx - dot(e_dis(u1, phi1), e_field(q1)) * dx
#a += inner(sigma(u2, phi2), epsilon(v2)) * ds(1) - dot(e_dis(u2, phi2), e_field(q2)) * ds(1)
#a += LM1*(phi1-phi2) * ds(1) + lm1*(q1-q2) * ds(1)
#a += dot(LM2,(u1-u2))* ds(1) + dot(lm2, (v1-v2))*ds(1)
a += inner(sigma(u2, phi2), epsilon(v2)) * dL - dot(e_dis(u2, phi2), e_field(q2)) * dL
a += LM1*(phi1-phi2) * dL + lm1*(q1-q2) * dL
a += dot(LM2,(u1-u2))* dL + dot(lm2, (v1-v2))* dL
L = dot(f, v1) * dV + dot(T, v1) * ds

#solve(a == L, sol, bc)
solve(a == L, sol, bc, solver_parameters={"linear_solver": "direct"})
# solve(a == L, sol, bc, solver_parameters={"linear_solver":"minres"})
u = sol.sub(0)
phi1 = sol.sub(2)
phi2 = sol.sub(3)
L_M_p = sol.sub(4)

# Plot solution
import matplotlib.pyplot as plt

# plot(u, title='Displacement', mode='displacement')


# Plot stress
s = sigma(u, phi1) - (1. / 3) * tr(sigma(u, phi1)) * Identity(d)  # deviatoric stress
von_Mises = sqrt(3. / 2 * inner(s, s))
V = FunctionSpace(mesh, 'P', 1)
von_Mises = project(von_Mises, V)
# plot(von_Mises, title='Stress intensity')

# Compute magnitude of displacement
u_magnitude = sqrt(dot(u, u))
u_magnitude = project(u_magnitude, V)
# plot(u_magnitude, title='Displacement magnitude')
# print('min/max u:',
#      u_magnitude.vector().array().min(),
#      u_magnitude.vector().array().max())

# Compute Electric field
M = VectorFunctionSpace(mesh, 'P', 1)
ef = project(e_field(phi1), M)


# Save solution to file in VTK format
File('R0_test1/R0_test1_Electrod_displacement.pvd') << u
# File('right100v/polar_von_mises.pvd') << von_Mises
# File('right100v/polar_magnitude.pvd') << u_magnitude
File('R0_test1/R0_test1_Electrod_electric_potential.pvd') << phi1
File('R0_test1/R0_test1_Electrod_electric_field.pvd') << ef
File('R0_test1/R0_test1_Electrod_L_M_p.pvd') << L_M_p

I think the problem is:

def potential(x, on_boundary):
    return on_boundary and x[2] < tol

This subdomain is the bottom of the body. You can try:

def potential(x, on_boundary):
    return on_boundary and near(x[2] ,0.22)

Dear @Karlosjusus

Actually the def potential here is used to defined the zero potential boundary on the bottom surface.

def clamped_boundary(x, on_boundary):
    return on_boundary and x[0] < tol


def potential(x, on_boundary):
    return on_boundary and x[2] < tol


bc1 = DirichletBC(V1, Constant((0, 0, 0)), clamped_boundary)
bc2 = DirichletBC(Q1, Constant(0), potential)
bc = [bc1, bc2]

And in here, I defined the upper surface for constant floating potential:

class UpperBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[2], 0.22)


#Marking facets that are on RightBoundary and creating the corresponding submesh
boundary_marker = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_marker.set_all(0)
UpperBoundary().mark(boundary_marker, 1)
submesh = MeshView.create(boundary_marker, 1)

But on the boudary conditions, you only impose at the bottom. I think that’s why the top part is not uniform.

bc2 = DirichletBC(Q1, Constant(0), potential)

Yes, because I don’t want the top surface to have a specific constant value potential, I only want to make a constraint condition for the top surface to make sure the potentials are equal on the top surface.
The potential on the top surface is still unknown.
That’s why I defined a R0 functionspace based on the submesh of the top surface and used the langrange multiplier to constrain the R0 functionspce to the CG1 functionspace in order to tell fenics I want the unknown potentials on the top surface is equal.

If I define the top surface with the DirichletBC, the potentials on the top surface will be zero, that’s not what I want.

Ahh, sorry… My bad. I understand now. I don’t know if the weak formulation of the Lagrange multipliers is correct. If you could write it in LaTeX it would help me. But, if what you want is that the potential is constant at the top, without fixing a value, the boundary conditions you can try are

\nabla u\cdot e_{x} = 0
\nabla u\cdot e_{y} = 0

on the top. The weak formulation:

\int (\nabla u\cdot e_{y})q d\Gamma + \int (\nabla u\cdot e_{x})q d\Gamma=0

where q is a scalar TestFunction, and \Gamma is the top. Not all the gradient has to be 0 on the surface, only on its tangent vectors, so that there is an evolution in the z-direction.

I understand what you mean: if the potentials on the top surface are uniform, then their gradients on directions x and y will be zero. Are ex and ey unit vectors on direction-x and y? And could you tell me how to express ex and ey inside fenics?

Or if I should write nabla_grad(phi)[0] to represent (∇u⋅ex)?
Then the whole weak formulation would be:

Q   = FunctionSpace(mesh, 'CG', 1) 
phi = TrialFunctions(Q)
q    = TestFunctions(Q)
a = nabla_grad(phi)[0] *q*dsup + nabla_grad(phi)[1] *q*dsup 

Yes, these should works.
To express ex and ey:

ex = Constant((1,0,0))
ey = Constant((0,1,0))

and nabla_grad(phi)[0] = \nabla \phi \cdot e_{x}.
This only works in this geometry, if you want to do the same with a complex geometry, you have to define the tangent vectors starting from the normal vector, with a 90º turn and a cross product of the normal and the previous one.

Dear @Karlosjusus

I tried the method you proposed. I think it worked! The full code is:

from __future__ import print_function
from fenics import *
from dolfin import *
from ufl import nabla_div
import numpy

# Scaled variables
L = 1
W = 0.22  # L is length; W is width

rho = 5550
g = 9.8

# define elasticity tensor C
c11 = 226e9
c12 = 125e9
c13 = 124e9
c33 = 216e9
c44 = 44.2e9  # * 2
c66 = (c11 - c12) / 2  # * 2
# 6x6
C = as_matrix([[c11, c12, c13, 0.0, 0.0, 0.0],
               [c12, c11, c13, 0.0, 0.0, 0.0],
               [c13, c13, c33, 0.0, 0.0, 0.0],
               [0.0, 0.0, 0.0, c44, 0.0, 0.0],
               [0.0, 0.0, 0.0, 0.0, c44, 0.0],
               [0.0, 0.0, 0.0, 0.0, 0.0, c66]])

# define piezoelectricity tensor e
e15 = 5.8
e31 = -2.2
e33 = 9.3
# 6x3
e1 = as_matrix([[0.0, 0.0, e31],
                [0.0, 0.0, e31],
                [0.0, 0.0, e33],
                [0.0, e15, 0.0],
                [e15, 0.0, 0.0],
                [0.0, 0.0, 0.0]])


# define permittivity tensor p
p11 = 5.64e-9
p33 = 6.35e-9
# 3x3
p = as_matrix([[p11, 0.0, 0.0],
               [0.0, p11, 0.0],
               [0.0, 0.0, p33]])


# rewrite the tensor into a vector using Voigt notation
def as_voigt_vector(ten):
    # FEniCS does not know anything about Voigt notation,
    # so one need to access the components directly as eps[0, 0] etc.
    # return as_vector([ten[0, 0], ten[1, 1], ten[2, 2], ten[1, 2], ten[0, 2], ten[0, 1]])
    return as_vector([ten[0, 0], ten[1, 1], ten[2, 2], 2 * ten[1, 2], 2 * ten[0, 2], 2 * ten[0, 1]])


def as_voigt_vector2(ten):
    # FEniCS does not know anything about Voigt notation,
    # so one need to access the components directly as eps[0, 0] etc.
    return as_vector([ten[0], ten[1], ten[2]])


# rewrite a vector into a tensor using Voigt notation
def from_voigt_vector(vec):
    # FEniCS does not know anything about Voigt notation,
    # so one need to rewrite the vector by hand into a tensor.
    return as_tensor([[vec[0], vec[5], vec[4]],
                      [vec[5], vec[1], vec[3]],
                      [vec[4], vec[3], vec[2]]])


def from_voigt_vector2(vec):
    # FEniCS does not know anything about Voigt notation,
    # so one need to rewrite the vector by hand into a tensor.
    return as_tensor([vec[0], vec[1], vec[2]])


# first define the function for the strain tensor in fenics notation
def epsilon(u):
    return 0.5 * (nabla_grad(u) + nabla_grad(u).T)
    # return sym(nabla_grad(u))


# then define the function for the static electric field in fenics notation
def e_field(phi):
    return -nabla_grad(phi)


# now define the stress tensor in fencis notation
def sigma(u, phi):
    # calculate sigma with C-matrix in Voigt notation
    sig = C * (as_voigt_vector(epsilon(u))) - e1 * as_voigt_vector2(e_field(phi))
    # return sigma as symmetric tensor
    return from_voigt_vector(sig)


# then define the function for the electric displacement in fenics notation
def e_dis(u, phi):
    ed = e1.T * (as_voigt_vector(epsilon(u))) + p * as_voigt_vector2(e_field(phi))
    return from_voigt_vector2(ed)


# Create mesh and define function space
mesh = BoxMesh(Point(0, 0, 0), Point(L, 0.2, 0.22), 50, 10, 11)


class UpperBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[2], 0.22)


#Marking facets that are on RightBoundary and creating the corresponding submesh
boundary_marker = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_marker.set_all(0)
UpperBoundary().mark(boundary_marker, 1)
ds = Measure("ds", subdomain_data=boundary_marker)

V = VectorFunctionSpace(mesh, 'CG', 1)
Q = FunctionSpace(mesh, 'CG', 1)
Mix_element = MixedElement([V.ufl_element(), Q.ufl_element()])
Z_space = FunctionSpace(mesh, Mix_element)
z = Function(Z_space)

# Define boundary condition
tol = 1E-14


def clamped_boundary(x, on_boundary):
    return on_boundary and x[0] < tol


def potential(x, on_boundary):
    return on_boundary and x[2] < tol


bc1 = DirichletBC(Z_space.sub(0), Constant((0, 0, 0)), clamped_boundary)
bc2 = DirichletBC(Z_space.sub(1), Constant(0), potential)
bc = [bc1, bc2]


# Define variational problem
(u, phi) = TrialFunctions(Z_space)
(v, q) = TestFunctions(Z_space)
d = u.geometric_dimension()  # space dimension

f = Constant((0, 0, -rho * g))
T = Constant((0, 0, 0))
a = inner(sigma(u, phi), epsilon(v)) * dx - dot(e_dis(u, phi), e_field(q)) * dx
a+= nabla_grad(phi)[0] * q * ds(1) + nabla_grad(phi)[1] * q * ds(1)
L = dot(f, v) * dx + dot(T, v) * ds


solve(a == L, z, bc)
(u, phi) = z.split()

# Compute Electric field
M1 = VectorFunctionSpace(mesh, 'P', 1)
ef = project(e_field(phi), M1)


# Save solution to file in VTK format
File('gradienttest/gradient_displacement.pvd') << u
File('gradienttest/gradient_electric_potentialtest.pvd') << phi
File('gradienttest/gradient_electric_field.pvd') << ef

Its result is nice and really interesting because it is very different from the potential result without constraining the upper surface, including the range and distribution pattern.
And from the potential gradient contour in x and y directions we can see it is not exactly zero on the upper surface but with strange stripe. Do you think it is normal?

And the potential contour and potential gradient contour in x and y directions are:



The potential contour without upper surface constraint:

Dear @Karlosjusus
I am curious about one thing, if the potential gradient satisfies the relations
∇ϕ⋅ex=0 ∇ϕ⋅ey=0

Then if it would be the same for
100*(∇ϕ⋅ex)=0 100*(∇ϕ⋅ey)=0

And: ∫100(∇ϕ⋅ey)qdΓ+∫100(∇ϕ⋅ex)qdΓ=0

Why will I get different results for using

a = inner(sigma(u, phi), epsilon(v)) * dx - dot(e_dis(u, phi), e_field(q)) * dx
a+= nabla_grad(phi)[0] * q * ds(1) + nabla_grad(phi)[1] * q * ds(1)

and

a = inner(sigma(u, phi), epsilon(v)) * dx - dot(e_dis(u, phi), e_field(q)) * dx
a+= 100*nabla_grad(phi)[0] * q * ds(1) + 100*nabla_grad(phi)[1] * q * ds(1)

Definitely not normal. I’m going to suggest a change. I’m not sure what the problem is, I can’t check right now. When you define ‘ds’, ‘ds’ already exists in fenics. I would rename it to ds_top, just in case. And the second change then

a+= nabla_grad(phi)[0] * q * ds_top + nabla_grad(phi)[1] * q * ds_top

Check if is the same

a+= dot(nabla_grad(phi),ex)* q * ds_top + dot(nabla_grad(phi) ,ey)* q * ds_top

Can you send the mathematical formulation of the problem?

Sure I modified the code with your advice.

from __future__ import print_function
from fenics import *
from dolfin import *
from ufl import nabla_div
import numpy

# Scaled variables
Length = 1
Width = 0.22  # L is length; W is width

rho = 5550
g = 9.8

# define elasticity tensor C
c11 = 226e9
c12 = 125e9
c13 = 124e9
c33 = 216e9
c44 = 44.2e9  # * 2
c66 = (c11 - c12) / 2  # * 2
# 6x6
C = as_matrix([[c11, c12, c13, 0.0, 0.0, 0.0],
               [c12, c11, c13, 0.0, 0.0, 0.0],
               [c13, c13, c33, 0.0, 0.0, 0.0],
               [0.0, 0.0, 0.0, c44, 0.0, 0.0],
               [0.0, 0.0, 0.0, 0.0, c44, 0.0],
               [0.0, 0.0, 0.0, 0.0, 0.0, c66]])

# define piezoelectricity tensor e
e15 = 5.8
e31 = -2.2
e33 = 9.3
# 6x3
e1 = as_matrix([[0.0, 0.0, e31],
                [0.0, 0.0, e31],
                [0.0, 0.0, e33],
                [0.0, e15, 0.0],
                [e15, 0.0, 0.0],
                [0.0, 0.0, 0.0]])


# define permittivity tensor p
p11 = 5.64e-9
p33 = 6.35e-9
# 3x3
p = as_matrix([[p11, 0.0, 0.0],
               [0.0, p11, 0.0],
               [0.0, 0.0, p33]])


# rewrite the tensor into a vector using Voigt notation
def as_voigt_vector(ten):
    # FEniCS does not know anything about Voigt notation,
    # so one need to access the components directly as eps[0, 0] etc.
    # return as_vector([ten[0, 0], ten[1, 1], ten[2, 2], ten[1, 2], ten[0, 2], ten[0, 1]])
    return as_vector([ten[0, 0], ten[1, 1], ten[2, 2], 2 * ten[1, 2], 2 * ten[0, 2], 2 * ten[0, 1]])


def as_voigt_vector2(ten):
    # FEniCS does not know anything about Voigt notation,
    # so one need to access the components directly as eps[0, 0] etc.
    return as_vector([ten[0], ten[1], ten[2]])


# rewrite a vector into a tensor using Voigt notation
def from_voigt_vector(vec):
    # FEniCS does not know anything about Voigt notation,
    # so one need to rewrite the vector by hand into a tensor.
    return as_tensor([[vec[0], vec[5], vec[4]],
                      [vec[5], vec[1], vec[3]],
                      [vec[4], vec[3], vec[2]]])


def from_voigt_vector2(vec):
    # FEniCS does not know anything about Voigt notation,
    # so one need to rewrite the vector by hand into a tensor.
    return as_tensor([vec[0], vec[1], vec[2]])


# first define the function for the strain tensor in fenics notation
def epsilon(u):
    return 0.5 * (nabla_grad(u) + nabla_grad(u).T)
    # return sym(nabla_grad(u))


# then define the function for the static electric field in fenics notation
def e_field(phi):
    return -nabla_grad(phi)


# now define the stress tensor in fencis notation
def sigma(u, phi):
    # calculate sigma with C-matrix in Voigt notation
    sig = C * (as_voigt_vector(epsilon(u))) - e1 * as_voigt_vector2(e_field(phi))
    # return sigma as symmetric tensor
    return from_voigt_vector(sig)


# then define the function for the electric displacement in fenics notation
def e_dis(u, phi):
    ed = e1.T * (as_voigt_vector(epsilon(u))) + p * as_voigt_vector2(e_field(phi))
    return from_voigt_vector2(ed)


# Create mesh and define function space
mesh = BoxMesh(Point(0, 0, 0), Point(1, 0.2, 0.22), 50, 10, 11)


class UpperBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[2], 0.22)

class rightBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[0], 1)


#Marking facets that are on RightBoundary and creating the corresponding submesh
boundary_marker = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_marker.set_all(0)
UpperBoundary().mark(boundary_marker, 1)
rightBoundary().mark(boundary_marker, 2)
ds_all = Measure("ds", subdomain_data=boundary_marker)
ds_top = ds_all(1)
ds_right = ds_all(2)

V = VectorFunctionSpace(mesh, 'CG', 1)
Q = FunctionSpace(mesh, 'CG', 1)
Mix_element = MixedElement([V.ufl_element(), Q.ufl_element()])
Z_space = FunctionSpace(mesh, Mix_element)
z = Function(Z_space)

# Define boundary condition
tol = 1E-14


def clamped_boundary(x, on_boundary):
    return on_boundary and x[0] < tol


def potential(x, on_boundary):
    return on_boundary and x[2] < tol


bc1 = DirichletBC(Z_space.sub(0), Constant((0, 0, 0)), clamped_boundary)
bc2 = DirichletBC(Z_space.sub(1), Constant(0), potential)
bc = [bc1, bc2]


# Define variational problem
(u, phi) = TrialFunctions(Z_space)
(v, q) = TestFunctions(Z_space)
d = u.geometric_dimension()  # space dimension

f = Constant((0, 0, -rho * g))
T = Constant((0, 0, 0)) #1e9
ex= Constant((1, 0, 0))
ey= Constant((0, 1, 0))
ez= Constant((0, 0, 1))
a = inner(sigma(u, phi), epsilon(v)) * dx - dot(e_dis(u, phi), e_field(q)) * dx
a+= 1*(dot(nabla_grad(phi), ex) * q * ds_top + dot(nabla_grad(phi), ey) * q * ds_top)
#a+= nabla_grad(phi)[0] * q * ds_top + nabla_grad(phi)[1] * q * ds_top

L = dot(f, v) * dx + dot(T, v) * ds_right


solve(a == L, z, bc)
(u, phi) = z.split()

# Compute Electric field
M1 = VectorFunctionSpace(mesh, 'P', 1)
ef = project(e_field(phi), M1)


# Save solution to file in VTK format
File('gradienttest_desk_with_constrain/gradient_displacement_desk_with_constrain.pvd') << u
File('gradienttest_desk_with_constrain/gradient_electric_potential_desk_with_constrain.pvd') << phi
File('gradienttest_desk_with_constrain/gradient_electric_field_desk_with_constrain.pvd') << ef

The results are the same using

a+= nabla_grad(phi)[0] * q * ds_top + nabla_grad(phi)[1] * q * ds_top

and

a+= dot(nabla_grad(phi),ex)* q * ds_top + dot(nabla_grad(phi) ,ey)* q * ds_top

the mathematical formulation of the problem is:

First, I don’t see \int (\sigma \cdot n)\delta u d\Gamma in the code.
Mmm its possible that the problem is the weak formulation of the new boundary contidion. I see
\nabla \phi \cdot e_{x} = -\nabla \phi \cdot e_{y}, because you define

\int (\nabla \phi \cdot e_{x})q d\Gamma + \int (\nabla \phi \cdot e_{y})q d\Gamma = \int (\nabla \phi \cdot e_{x} +\nabla \phi \cdot e_{y} )q d\Gamma = 0

Can you define two TestFuntction qx, qy and try

\int (\nabla \phi \cdot e_{x})q_{x} d\Gamma + \int (\nabla \phi \cdot e_{y})q_{y} d\Gamma

and see what happens?

1 Like

For the ∫(σ⋅n)δudΓ, the (σ⋅n) denotes surface force and is defined with dot(T, v) * ds_right. Also this term is not so important in this case.

I think I write 1*(dot(nabla_grad(phi), ex) * q * ds_top + dot(nabla_grad(phi), ey) * q * ds_top) to represent ∫(∇ϕ⋅ex)qdΓ+∫(∇ϕ⋅ey)qdΓ. Unless I understand wrong.
Here I used brackets to include the two separate integrations together to multiply a constant, I want to test whether the constant influences the results. Results are the same between

a+= 1*(dot(nabla_grad(phi), ex) * q * ds_top + dot(nabla_grad(phi), ey) * q * ds_top)

and

a+= dot(nabla_grad(phi), ex) * q * ds_top + dot(nabla_grad(phi), ey) * q * ds_top

As for two TestFuntction qx, qy
I am confused because I think the testfunction means a variation of variable. If the testfunction has a one-to-one correspondence with trialfunction, how can I define qx, qy with one phi? And phi is a scale function in this case.

I dont understand why you multiply by 1. But, the TestFunctions ARE NOT a variation of the variables. The test functions are a numerical representation of functions in \mathcal{D}(\Omega). The weak formulation means:
f is a weak solution of F(f)=g if and only if \int F(f)vd\Omega = \int gvd\Omega \forall v \in \mathcal{D}(\Omega).

Dear @Karlosjusus

As every term in my a==L equation has the physical meaning of energy.
I think if I want to add other terms into the equation, they should be energy too.
Then I tried this weak form:


That means I add an extremely large electric field energy in x and y directions on the top surface, forcing the electric field to be zero in these two directions.
And the electric field has the formulation:
image
I posted my code here:

from __future__ import print_function
from fenics import *
from dolfin import *
from ufl import nabla_div
import numpy

# Scaled variables
Length = 1
Width = 0.22  # L is length; W is width

rho = 5550
g = 9.8

# define elasticity tensor C
c11 = 226e9
c12 = 125e9
c13 = 124e9
c33 = 216e9
c44 = 44.2e9  # * 2
c66 = (c11 - c12) / 2  # * 2
# 6x6
C = as_matrix([[c11, c12, c13, 0.0, 0.0, 0.0],
               [c12, c11, c13, 0.0, 0.0, 0.0],
               [c13, c13, c33, 0.0, 0.0, 0.0],
               [0.0, 0.0, 0.0, c44, 0.0, 0.0],
               [0.0, 0.0, 0.0, 0.0, c44, 0.0],
               [0.0, 0.0, 0.0, 0.0, 0.0, c66]])

# define piezoelectricity tensor e
e15 = 5.8
e31 = -2.2
e33 = 9.3
# 6x3
e1 = as_matrix([[0.0, 0.0, e31],
                [0.0, 0.0, e31],
                [0.0, 0.0, e33],
                [0.0, e15, 0.0],
                [e15, 0.0, 0.0],
                [0.0, 0.0, 0.0]])


# define permittivity tensor p
p11 = 5.64e-9
p33 = 6.35e-9
# 3x3
p = as_matrix([[p11, 0.0, 0.0],
               [0.0, p11, 0.0],
               [0.0, 0.0, p33]])


# rewrite the tensor into a vector using Voigt notation
def as_voigt_vector(ten):
    # FEniCS does not know anything about Voigt notation,
    # so one need to access the components directly as eps[0, 0] etc.
    # return as_vector([ten[0, 0], ten[1, 1], ten[2, 2], ten[1, 2], ten[0, 2], ten[0, 1]])
    return as_vector([ten[0, 0], ten[1, 1], ten[2, 2], 2 * ten[1, 2], 2 * ten[0, 2], 2 * ten[0, 1]])


def as_voigt_vector2(ten):
    # FEniCS does not know anything about Voigt notation,
    # so one need to access the components directly as eps[0, 0] etc.
    return as_vector([ten[0], ten[1], ten[2]])


# rewrite a vector into a tensor using Voigt notation
def from_voigt_vector(vec):
    # FEniCS does not know anything about Voigt notation,
    # so one need to rewrite the vector by hand into a tensor.
    return as_tensor([[vec[0], vec[5], vec[4]],
                      [vec[5], vec[1], vec[3]],
                      [vec[4], vec[3], vec[2]]])


def from_voigt_vector2(vec):
    # FEniCS does not know anything about Voigt notation,
    # so one need to rewrite the vector by hand into a tensor.
    return as_tensor([vec[0], vec[1], vec[2]])


# first define the function for the strain tensor in fenics notation
def epsilon(u):
    return 0.5 * (nabla_grad(u) + nabla_grad(u).T)
    # return sym(nabla_grad(u))


# then define the function for the static electric field in fenics notation
def e_field(phi):
    return -nabla_grad(phi)


# now define the stress tensor in fencis notation
def sigma(u, phi):
    # calculate sigma with C-matrix in Voigt notation
    sig = C * (as_voigt_vector(epsilon(u))) - e1 * as_voigt_vector2(e_field(phi))
    # return sigma as symmetric tensor
    return from_voigt_vector(sig)


# then define the function for the electric displacement in fenics notation
def e_dis(u, phi):
    ed = e1.T * (as_voigt_vector(epsilon(u))) + p * as_voigt_vector2(e_field(phi))
    return from_voigt_vector2(ed)

def e_dis1(phi):
    ed = p * as_voigt_vector2(e_field(phi))
    return from_voigt_vector2(ed)


# Create mesh and define function space
mesh = BoxMesh(Point(0, 0, 0), Point(1, 0.2, 0.22), 50, 10, 11)


class UpperBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[2], 0.22)

class rightBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[0], 1)


#Marking facets that are on RightBoundary and creating the corresponding submesh
boundary_marker = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_marker.set_all(0)
UpperBoundary().mark(boundary_marker, 1)
rightBoundary().mark(boundary_marker, 2)
ds_all = Measure("ds", subdomain_data=boundary_marker)
ds_top = ds_all(1)
ds_right = ds_all(2)

V = VectorFunctionSpace(mesh, 'CG', 1)
Q = FunctionSpace(mesh, 'CG', 1)
Mix_element = MixedElement([V.ufl_element(), Q.ufl_element()])
Z_space = FunctionSpace(mesh, Mix_element)
z = Function(Z_space)

# Define boundary condition
tol = 1E-14


def clamped_boundary(x, on_boundary):
    return on_boundary and x[0] < tol


def potential(x, on_boundary):
    return on_boundary and x[2] < tol


bc1 = DirichletBC(Z_space.sub(0), Constant((0, 0, 0)), clamped_boundary)
bc2 = DirichletBC(Z_space.sub(1), Constant(0), potential)
bc = [bc1, bc2]


# Define variational problem
(u, phi) = TrialFunctions(Z_space)
(v, q) = TestFunctions(Z_space)
d = u.geometric_dimension()  # space dimension

f = Constant((0, 0, -rho * g))
T = Constant((0, 0, 0)) #1e9
ex= Constant((1, 0, 0))
ey= Constant((0, 1, 0))
ez= Constant((0, 0, 1))
a = inner(sigma(u, phi), epsilon(v)) * dx - dot(e_dis(u, phi), e_field(q)) * dx
#a+= dot(nabla_grad(phi), ex) * q * ds_top
#a+= dot(nabla_grad(phi), ey) * q * ds_top
#a+= nabla_grad(phi)[0] * q * ds_top + nabla_grad(phi)[1] * q * ds_top
#a+= -1000000*nabla_grad(phi)[0] * e_dis1(q)[0] * ds_top
#a+= -1000000*nabla_grad(phi)[1] * e_dis1(q)[1] * ds_top
a+= -1000000*nabla_grad(phi)[0] * e_dis(v,q)[0] * ds_top
a+= -1000000*nabla_grad(phi)[1] * e_dis(v,q)[1] * ds_top


L = dot(f, v) * dx + dot(T, v) * ds_right


solve(a == L, z, bc)
(u, phi) = z.split()

# Compute Electric field
M1 = VectorFunctionSpace(mesh, 'P', 1)
ef = project(e_field(phi), M1)


# Save solution to file in VTK format
File('gradienttest_with_constrain/gradient_displacement_with_constrain.pvd') << u
File('gradienttest_with_constrain/gradient_electric_potential_with_constrain.pvd') << phi
File('gradienttest_with_constrain/gradient_electric_field_with_constrain.pvd') << ef

The results without and with constrain for the top surface are:

Dear @RunzeZHANG
I don’t understand the new term… I don’t know if the results is correct. How do you obtain this term?

Dear @Karlosjusus

The new term is evolved from your idea.
You proposed to add
a+= dot(nabla_grad(phi),ex)* q * ds_top + dot(nabla_grad(phi) ,ey)* q * ds_top
But dot(nabla_grad(phi),ex)* q , which is (∇ϕ⋅ex)q, has no physical meaning.

And I recall ∇ϕ⋅ey has the physical meaning of negative electric field in x direction, if I want to make the term with physical meaning to present energy, I need to multiply the (∇ϕ⋅ex) with electric displacement. Then comes the new term nabla_grad(phi)[0] * e_dis(v,q)[0].
After multiplying a large constant, e.g. 1000000, it looked like the method of multiplication by a large number in solid mechanics.
I guess (because I also didn’t find others using this way) this will lead to the electric energy in x and y directions on the top surface being zero, which means the electric field in x and y is zero. In other words, the gradients of electric potentials on the top surface are zero in x and y directions.

What do you think? If you find somewhere not correct, please tell me.
As for the results, I think the only way is to compare them with a benchmark case.

They do not have to have the same physical meaning, since one is the equation and the other is a boundary condition.
As if you want to solve:

\Delta u =f
u = 0 \text{ on }\Gamma

each equation have a different physical meaning. Weak formulation is a mathecatical tool.

Dear @Karlosjusus

Then I am confused; how will you do if you directly made the gradients of variables to be zero?