Solve Poisson equation with non-linear boundary conditions on a submesh

Hello,

I’m trying to solve a poisson equation with non-linear boundary conditions in one subdomain of a domain.

First, I tried solving the poisson equation in the full domain to check if it is working. The code works fine.

But when i try to solve it only in a subdomain using a submesh, i’m not able to get it to work. There is even no change in the geometry of the domains in the first and second case. I’m making some mistake while integrating the poisson solver with the submesh functionality.

I have attached the working code in a single domain first, and then the code to solve in in only one subdomain (not working).

Thanks a lot for taking time to look into this !

Solving on a whole domain (Working)

from __future__ import print_function
from dolfin import *
import matplotlib.pyplot as plt
# from __future__ import print_function
from fenics import *
from mshr import *
import numpy as np
from ufl import nabla_div
from ufl import sinh
import random


domain = Rectangle(Point(0,0), Point(100e-6, 200e-6))
mesh = generate_mesh(domain,100)
V = FunctionSpace(mesh, 'P', 1)

f = Constant(0.0)

i0 = 12.6
r = Constant(2*i0)

iApp = -100
g = iApp

F = 96485.33
R = 8.314
T = 300
C = Constant(F/(2*R*T))

kappa = 1


class BottomBoundary(SubDomain):
    def inside(self, x, on_boundary):
        tol = 1E-14
        return on_boundary and abs(x[1]) < tol

class TopBoundary(SubDomain):
    def inside(self, x, on_boundary):
        tol = 1E-14
        return on_boundary and abs(x[1] - 200e-6) < tol

bottom_boundary = BottomBoundary()        
top_boundary = TopBoundary()   

boundary_parts = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_parts.set_all(0)
bottom_boundary.mark(boundary_parts, 1)
top_boundary.mark(boundary_parts, 2)
ds = Measure("ds")[boundary_parts]


u = TrialFunction(V)
v = TestFunction(V)

u = Function(V)

bcs = []

F = kappa*inner(nabla_grad(u), nabla_grad(v))*dx + g*v*ds(2) + r*sinh(C*u)*v*ds(1) - f*v*dx
J = derivative(F, u)
problem = NonlinearVariationalProblem(F, u, bcs, J)
solver  = NonlinearVariationalSolver(problem)
solver.solve()

import matplotlib.pyplot as plt
p = plot(u, mode='color')
plt.colorbar(p)
plt.title(r"$\Phi$",fontsize=26)
plt.show()

Solving on a submesh (Not Working)

from __future__ import print_function
from dolfin import *
import matplotlib.pyplot as plt
# from __future__ import print_function
from fenics import *
from mshr import *
import numpy as np
from ufl import nabla_div
from ufl import sinh
import random


f = Constant(0.0)

i0 = 12.6
r = Constant(2*i0)

iApp = -100
g = iApp

F = 96485.33
R = 8.314
T = 300
C = Constant(F/(2*R*T))

kappa = 1


# define regions and boundaries
domain = Rectangle(Point(0,0), Point(100e-6, 400e-6))
mesh = generate_mesh(domain,100)

regions = MeshFunction('size_t', mesh, mesh.topology().dim())
boundaries = MeshFunction('size_t', mesh, mesh.topology().dim() - 1)

class A(SubDomain):
def inside(self, x, on_boundary):
    return x[1] <= 200e-6
a = A()

class B(SubDomain):
def inside(self, x, on_boundary):
    return x[1] >= 200e-6
b = B()

class BottomBoundary(SubDomain):
def inside(self, x, on_boundary):
    return near(x[1], 0.0)
bottom = BottomBoundary()

class TopBoundary(SubDomain):
def inside(self, x, on_boundary):
    return near(x[1], 200e-6)
top = TopBoundary()

regions.set_all(0)
b.mark(regions, 1)

boundaries.set_all(0)
bottom.mark(boundaries, 1)
top.mark(boundaries, 2)
# plot(regions, title="Regions")
# plot(boundaries, title="Boundaries")

# # define a submesh, composed of region 0 and 1
new_region = MeshFunction('size_t', mesh, mesh.topology().dim())
new_region.set_all(0)
new_region.array()[regions.array() == 0] = 1
submesh = SubMesh(mesh, new_region, 1)

# # define new meshfunctions on this submesh with the same values as their original mesh
submesh_regions = MeshFunction('size_t', submesh, submesh.topology().dim())
submesh_regions.set_all(0)
submesh_boundaries = MeshFunction('size_t', submesh, submesh.topology().dim() - 1)
submesh_boundaries.set_all(0)

# plot(submesh_regions, title="Submesh regions")
# plot(submesh_boundaries, title="Submesh boundaries")

bottom.mark(submesh_boundaries, 1)
top.mark(submesh_boundaries, 2)

ds = Measure("ds")[submesh_boundaries]

Vsub = FunctionSpace(submesh, 'P', 1)
u = TrialFunction(Vsub)
v = TestFunction(Vsub)
u = Function(Vsub)
dx = Measure("dx")[submesh_regions]

bcs = []


F = kappa*inner(nabla_grad(u), nabla_grad(v))*dx + g*v*ds(2) + r*sinh(C*u)*v*ds(1) - f*v*dx

J = derivative(F, u)
problem = NonlinearVariationalProblem(F, u, bcs, J)
solver  = NonlinearVariationalSolver(problem)
solver.solve()

import matplotlib.pyplot as plt
p = plot(u, mode='color')
plt.colorbar(p)
plt.title(r"$\Phi$",fontsize=26)
plt.show()

The problem is that you are not setting mesh markers properly on your submesh. This can be seen by visualizing them after you have marked them.
For this particular case, the issue is that you are using an unstructured mesh, which means that the submesh boundary is not a straight line.

I’ve added a code using built in meshes, that gives the correct and expected results.

import matplotlib.pyplot as plt
from fenics import *
import numpy as np
from ufl import nabla_div
from ufl import sinh


f = Constant(0.0)

i0 = 12.6
r = Constant(2*i0)

iApp = -100
g = iApp

F = 96485.33
R = 8.314
T = 300
C = Constant(F/(2*R*T))

kappa = 1

mesh = RectangleMesh(Point(0,0), Point(100e-6, 400e-6), 100,100)
# from mshr import *
# domain = Rectangle(Point(0,0), Point(100e-6, 400e-6))
# mesh = generate_mesh(domain,100)

regions = MeshFunction('size_t', mesh, mesh.topology().dim())
boundaries = MeshFunction('size_t', mesh, mesh.topology().dim() - 1)

class A(SubDomain):
    def inside(self, x, on_boundary):
        return x[1] <= 200e-6
a = A()

class B(SubDomain):
    def inside(self, x, on_boundary):
        return x[1] >= 200e-6
b = B()

class BottomBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return near(x[1], 0.0)
bottom = BottomBoundary()

class TopBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return near(x[1], 200e-6)
top = TopBoundary()

regions.set_all(0)
b.mark(regions, 1)

boundaries.set_all(0)
bottom.mark(boundaries, 1)
top.mark(boundaries, 2)
# plot(regions, title="Regions")
# plot(boundaries, title="Boundaries")

# define a submesh, composed of region 0 and 1
new_region = MeshFunction('size_t', mesh, mesh.topology().dim())
new_region.set_all(0)
new_region.array()[regions.array() == 0] = 1
submesh = SubMesh(mesh, new_region, 1)

# # define new meshfunctions on this submesh with the same values as their original mesh
submesh_regions = MeshFunction('size_t', submesh, submesh.topology().dim())
submesh_regions.set_all(0)
submesh_boundaries = MeshFunction('size_t', submesh, submesh.topology().dim() - 1)
submesh_boundaries.set_all(0)

# plot(submesh_regions, title="Submesh regions")
# plot(submesh_boundaries, title="Submesh boundaries")

bottom.mark(submesh_boundaries, 1)
top.mark(submesh_boundaries, 2)
File("submesh_boundaries.pvd") << submesh_boundaries
ds = Measure("ds")[submesh_boundaries]

Vsub = FunctionSpace(submesh, 'P', 1)
u = TrialFunction(Vsub)
v = TestFunction(Vsub)
u = Function(Vsub)
dx = Measure("dx")[submesh_regions]

bcs = []


F = kappa*inner(nabla_grad(u), nabla_grad(v))*dx + g*v*ds(2) + r*sinh(C*u)*v*ds(1) - f*v*dx

J = derivative(F, u)
problem = NonlinearVariationalProblem(F, u, bcs, J)
solver  = NonlinearVariationalSolver(problem)
solver.solve()

File("u.pvd").write(u)
2 Likes

@dokken

Many thanks for resolving the problem and sharing the modified code !

Hi @dokken,

I have one more question regarding the use of an inbuilt mesh and defining subdomains properly.

I am working on solving the stress inside a similar domain that I posted above, based on the elasto-plastic tutorial. Only the demarcation of the domains are a bit different. I have changed the boundary conditions based on the problem. I have attached below the code I’m working with.

I’m stuck with 2 things.
If I use lines 16, 17 for mesh generation, I’m getting a different plot than if I use line 19 for a built in mesh function.
Also while defining the subdomains (lines 85-91), I’m getting a different plot if I remove the tolerances.

Can you please help me identify where I’m going wrong ? Feels like it is related to the same issue like before and I’m not able to deal with defining the subdomains properly.

Thanks in advance !

from __future__ import print_function
from dolfin import *
import matplotlib.pyplot as plt
from fenics import *
from mshr import *
import numpy as np
from ufl import nabla_div
import random
# from fenicstools import interpolate_nonmatching_mesh_any

parameters["form_compiler"]["representation"] = 'quadrature'
import warnings
from ffc.quadrature.deprecation import QuadratureRepresentationDeprecationWarning
warnings.simplefilter("once", QuadratureRepresentationDeprecationWarning)

# domain = Rectangle(Point(0,0), Point(100e-6, 200e-6))
# mesh = generate_mesh(domain,20)

mesh = RectangleMesh(Point(0,0), Point(100e-6, 200e-6), 100, 100)

deg_u = 2
deg_stress = 2
V = VectorFunctionSpace(mesh, "CG", deg_u)
We = VectorElement("Quadrature", mesh.ufl_cell(), degree=deg_stress, dim=4, quad_scheme='default')
W = FunctionSpace(mesh, We)
W0e = FiniteElement("Quadrature", mesh.ufl_cell(), degree=deg_stress, quad_scheme='default')
W0 = FunctionSpace(mesh, W0e)

# plot(mesh)
# plt.show()

class BottomBoundary(SubDomain):
    def inside(self, x, on_boundary):
        # tol = 1E-14
        return on_boundary and near(x[1],0)

class TopBoundary(SubDomain):
    def inside(self, x, on_boundary):
        # tol = 1E-14
        return on_boundary and near(x[1], 200e-6)

bottom_boundary = BottomBoundary()        
top_boundary = TopBoundary()        

boundary_parts = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_parts.set_all(0)
bottom_boundary.mark(boundary_parts, 1)
top_boundary.mark(boundary_parts, 2)
ds = Measure("ds")[boundary_parts]


# Define dirichlet boundary conditions
tol = 1E-14

#Left boundary
def left_boundary(x, on_boundary):
    return on_boundary and near(x[0],0)
bcL = DirichletBC(V.sub(0), Constant(0.), left_boundary)


#Right boundary
def right_boundary(x, on_boundary):
    return on_boundary and near(x[0],100e-6)
bcR = DirichletBC(V.sub(0), Constant(0.), right_boundary)

bcs = [bcL, bcR]


#Top layer properties
E0 = Constant(10e3) #Young's modulus in MPa
nu0 = Constant(0.2)
mu_0 = E0/2/(1+nu0)
lambda_0 = E0*nu0/(1+nu0)/(1-2*nu0)
sig0_0 = 4000

#Bottom layer properties
E1 = Constant(50e3) #Young's Modulus in MPa
nu1 = Constant(0.3)
mu_1 = E1/2/(1+nu1)
lambda_1 = E1*nu1/(1+nu1)/(1-2*nu1)
sig0_1 = 4000

tol = 1E-14

class Omega_0(SubDomain):
  def inside(self, x, on_boundary):
    return x[1] >= 0.0001 + 0.00002*sin(5e4*x[0]) - tol

class Omega_1(SubDomain):
  def inside(self, x, on_boundary):
    return x[1] <= 0.0001 + 0.00002*sin(5e4*x[0]) + tol


# materials = CellFunction('size_t', mesh)
materials = MeshFunction('size_t',mesh,2)

subdomain_0 = Omega_0()
subdomain_1 = Omega_1()
subdomain_0.mark(materials, 0)
subdomain_1.mark(materials, 1)

# plot(materials)
# plt.show()

class M(UserExpression):
    def __init__(self, materials, mu_0, mu_1, **kwargs):
       super().__init__(**kwargs)
       self.materials = materials
       self.mu_0 = mu_0
       self.mu_1 = mu_1

    def eval_cell(self, values, x, cell):
        if self.materials[cell.index] == 0:
            values[0] = self.mu_0

        else:
            values[0] = self.mu_1
    
mu = M(materials, mu_0, mu_1, degree=0)


class L(UserExpression):
    def __init__(self, materials, lambda_0, lambda_1, **kwargs):
       super().__init__(**kwargs)
       self.materials = materials
       self.lambda_0 = lambda_0
       self.lambda_1 = lambda_1

    def eval_cell(self, values, x, cell):
        if self.materials[cell.index] == 0:
            values[0] = self.lambda_0

        else:
            values[0] = self.lambda_1

lambda_ = L(materials, lambda_0, lambda_1, degree=0)


class Y(UserExpression):
    def __init__(self, materials, sig0_0, sig0_1, **kwargs):
       super().__init__(**kwargs)
       self.materials = materials
       self.sig0_0 = sig0_0
       self.sig0_1 = sig0_1

    def eval_cell(self, values, x, cell):
        if self.materials[cell.index] == 0:
            values[0] = self.sig0_0

        else:
            values[0] = self.sig0_1

sig0 = Y(materials, sig0_0, sig0_1, degree=0)     #Yield Strength

class El(UserExpression):
    def __init__(self, materials, E0, E1, **kwargs):
       super().__init__(**kwargs)
       self.materials = materials
       self.E0 = E0
       self.E1 = E1

    def eval_cell(self, values, x, cell):
        if self.materials[cell.index] == 0:
            values[0] = self.E0

        else:
            values[0] = self.E1

E = Y(materials, E0, E1, degree=0)

Et = E/100.        #Tangent Modulus

H = E*Et/(E-Et)    #Hardening Modulus

def eps(v):
    e = sym(grad(v))
    return as_tensor([[e[0, 0], e[0, 1], 0],
                      [e[0, 1], e[1, 1], 0],
                      [0, 0, 0]])
def sigma(eps_el):
    return lambda_*tr(eps_el)*Identity(3) + 2*mu*eps_el
def as_3D_tensor(X):
    return as_tensor([[X[0], X[3], 0],
                      [X[3], X[1], 0],
                      [0, 0, X[2]]])

#Required Functions
sig = Function(W)
sig_old = Function(W)
n_elas = Function(W)
beta = Function(W0)
p = Function(W0, name="Cumulative plastic strain")
u = Function(V, name="Total displacement")
du = Function(V, name="Iteration correction")
Du = Function(V, name="Current increment")
v = TrialFunction(V)
u_ = TestFunction(V)

ppos = lambda x: (x+abs(x))/2.

def proj_sig(deps, old_sig, old_p):
    sig_n = as_3D_tensor(old_sig)
    sig_elas = sig_n + sigma(deps)
    s = dev(sig_elas)
    sig_eq = sqrt(3/2.*inner(s, s))
    f_elas = sig_eq - sig0 - H*old_p
    dp = ppos(f_elas)/(3*mu+H)
    n_elas = s/sig_eq*ppos(f_elas)/f_elas
    beta = 3*mu*dp/sig_eq
    new_sig = sig_elas-beta*s
    return as_vector([new_sig[0, 0], new_sig[1, 1], new_sig[2, 2], new_sig[0, 1]]), \
           as_vector([n_elas[0, 0], n_elas[1, 1], n_elas[2, 2], n_elas[0, 1]]), \
           beta, dp

def sigma_tang(e):
    N_elas = as_3D_tensor(n_elas)
    return sigma(e) - 3*mu*(3*mu/(3*mu+H)-beta)*inner(N_elas, e)*N_elas-2*mu*beta*dev(e)

metadata = {"quadrature_degree": deg_stress, "quadrature_scheme": "default"}
dxm = dx(metadata=metadata)

n = FacetNormal(mesh)

d = u.geometric_dimension()
# pressureTop = Constant((0, 1000000.0))
# pressureBottom = Constant((0, 1000000.0))

#External Pressure Condition
loading1 = -100.0 #stress in MPa on top surface
loading2 = -100.0 #stress in MPa on bottom surface

def F_ext_1(v):
    return loading1*dot(n, v)*ds(1)

def F_ext_2(v):
    return loading2*dot(n, v)*ds(2)

a_Newton = inner(eps(v), sigma_tang(eps(u_)))*dxm
res = -inner(eps(u_), as_3D_tensor(sig))*dxm + F_ext_1(u_) + F_ext_2(u_)

def local_project(v, V, u=None):
    dv = TrialFunction(V)
    v_ = TestFunction(V)
    a_proj = inner(dv, v_)*dxm
    b_proj = inner(v, v_)*dxm
    solver = LocalSolver(a_proj, b_proj)
    solver.factorize()
    if u is None:
        u = Function(V)
        solver.solve_local_rhs(u)
        return u
    else:
        solver.solve_local_rhs(u)
        return

file_results = XDMFFile("plasticity_results.xdmf")
file_results.parameters["flush_output"] = True
file_results.parameters["functions_share_mesh"] = True
P0 = FunctionSpace(mesh, "DG", 0)
p_avg = Function(P0, name="Plastic strain")
sig_avg = Function(P0, name="Stress")


Nitermax, tol = 20, 1e-8  # parameters of the Newton-Raphson procedure
Nincr = 20


A, Res = assemble_system(a_Newton, res, bcs)
nRes0 = Res.norm("l2")
nRes = nRes0
Du.interpolate(Constant((0.0, 0.0)))
niter = 0

print(nRes)

while nRes/nRes0 > tol and niter < Nitermax:
# while niter < Nitermax:
        solve(A, du.vector(), Res, "mumps")
        Du.assign(Du+du)
        deps = eps(Du)
        sig_, n_elas_, beta_, dp_ = proj_sig(deps, sig_old, p)
        local_project(sig_, W, sig)
        local_project(n_elas_, W, n_elas)
        local_project(beta_, W0, beta)
        A, Res = assemble_system(a_Newton, res, bcs)
        nRes = Res.norm("l2")
        print("    Residual:", nRes)
        niter += 1
u.assign(u+Du)
sig_old.assign(sig)
p.assign(p+local_project(dp_, W0))


Vstr = VectorFunctionSpace(mesh, "DG", 1, dim=4)
strDG = local_project(sig, Vstr)

# For Plotting
import matplotlib.pyplot as plt
p = plot(strDG[1], mode='color')
plt.colorbar(p)
plt.title(r"$\sigma_{yy}$",fontsize=26)
plt.show()

This code is way to big for me to review it. Make a minimal example following Read before posting: How do I get my question answered?

If you have issues with marking the unstructured meshes, i would suggest using Gmsh and the BooleanFragments operator to make sure that the interface between the two domains is resolved. To load gmsh meshes into dolfin, you can use for instance: Converter from GMSH to XDMF (with physical groups). There are many examples on how to use gmsh in this thread Transitioning from mesh.xml to mesh.xdmf, from dolfin-convert to meshio (this post might be of special interest as it uses boolean fragments)

2 Likes

Apologies for the long code.

The problem I’m facing is in marking two subdomains. I have a sinusoidal interface that separates the two subdomains. I want to learn a way to mark the interface and the two subdomains properly. For my problem, I getting different values if I use a built in mesh (line 6) than if use lines (3-4).

Also, should I use a tolerance or not (line 10-16) while defining the subdomains ?

Are there any straightforward ways or workarounds of marking the interface and the two subdomains.
Im still at marking the subdomains.

Thank you !

from dolfin import *

# domain = Rectangle(Point(0,0), Point(100e-6, 200e-6))
# mesh = generate_mesh(domain,20)

mesh = RectangleMesh(Point(0,0), Point(100e-6, 200e-6), 100, 100)

tol = 1e-14

class Omega_0(SubDomain):
  def inside(self, x, on_boundary):
    return x[1] >= 0.0001 + 0.00002*sin(5e4*x[0]) - tol

class Omega_1(SubDomain):
  def inside(self, x, on_boundary):
    return x[1] <= 0.0001 + 0.00002*sin(5e4*x[0]) + tol 

As your discretization does not align with the interface you would like to mark, it makes it more problematic to use subdomain markers, and different meshes (discretizations) will yield different results. However, let us assume that you are able to make two submeshes. Then you can use a technique where you start by marking all boundaries with one marker, and eliminate all other (simple) boundaries by marking them.

class all_boundaries(SubDomain):
  def inside(self, x, on_boundary):
    return on_boundary
boundaries = MeshFunction("size_t", mesh, mesh.topology().dim()-1,0)
on_boundary.mark(boundaries, 6)
left.mark(boundaries,1)
right.mark(boundaries,2)
bottom.mark(boundaries,3)
top.mark(boundaries,4)

Then, the only boundary that has the marker 6 is the internal boundary.

HOWEVER: I would still suggest properly resolving the boundary in the mesh, as described in my previous post.

1 Like

Thanks a lot for the technique.

In case the interface need not be included and I just want to divide my total domain into two subdomains in order to assign different material properties to them, is this code correct ?

Should I use a tolerance or not ?


from dolfin import *

mesh = RectangleMesh(Point(0,0), Point(100e-6, 200e-6), 100, 100)

tol = 1e-14

class Omega_0(SubDomain):
  def inside(self, x, on_boundary):
    return x[1] >= 0.0001 + 0.00002*sin(5e4*x[0]) - tol

class Omega_1(SubDomain):
  def inside(self, x, on_boundary):
    return x[1] <= 0.0001 + 0.00002*sin(5e4*x[0]) + tol 
 

I would use smaller or equal on one side, and bigger on the other side. Then you should not Need to use the tolerance.

1 Like

Thanks a ton ! Got it.

In this case use of a built in mesh or a different mesh shouldn’t make a difference right ?

Well, the results will differ, and i cant say what is best

1 Like

Hii @dokken

I tried one more thing to create a sinusoidal mesh based on a previously answered question using the code below. Though this creates a sinusoidal mesh, I was wondering if there any way to make sure these points can be forced to be co-ordinates of a bigger mesh I’m creating.

I want to create a rectangle with these points on the sine curve as mesh co-ordinates. Is it possible to add two sine meshes and form a rectangle.

mesh (line21) generates the lower part of my mesh and mesh2 (line23) generates the upper part of my mesh. Is it possible to add these meshes making sure the co-ordinates of the sine are preserved, so that I can easily identify the sine interface ?

from dolfin import *
from mshr import *
import matplotlib.pyplot as plt

# Create mesh
domain_n_points = 32
domain_points = list()
for n in range(domain_n_points + 1):
    x = n*100e-6/domain_n_points
    domain_points.append( Point(x, 0.0001 + 0.00002*sin(5e4*x)) )
domain_points.append( Point(100e-6, 0.) )
domain_points.append( Point(0., 0.) )

domain_points = domain_points[::-1] # Polygon vertices must be given in counter clockwise order.
domain = Polygon(domain_points)

domain1 = Rectangle(Point(0,0), Point(100e-6, 200e-6))

domain2 = domain1 - domain

mesh = generate_mesh(domain, 42)

mesh2 = generate_mesh(domain2, 42)

# mesh = generate_mesh(domain + domain2, 42)

plot(mesh2)
plt.show()

As i am not a regular user of mshr (i am using pygmsh and Gmsh) I cant say of the top of my how to do it. But i can point you to this mshr demo on subdomains.

1 Like

Many thanks for the direction