I am trying to run a simple simulation of Gauss’s law as it applies to a cylinder with the top and bottom ends held at a constant charge and the sides of the cylinder kept at 0 charge. I am using gmsh to create the simple mesh which I then convert into a DolfinX mesh using model_to_mesh
. I then visualise the solution using Paraview. Things appear to work till this point even though the electric field looks strange to me. However, when I try to add some objects to the simulation that have different permittivitys it looks like the simulation is completely ignoring these objects. I’m pretty sure the boundary conditions are set correctly as the electric potential looks right in Paraview but the electric field lines do not make sense and seem to ignore the objects of very high and very low permittivity. This is my example code.
from mpi4py import MPI
from dolfinx.io import gmshio
from dolfinx.io.utils import XDMFFile
import numpy as np
import gmsh
from ufl import TestFunction, TrialFunction, dot, dx, grad
from dolfinx.fem import (Constant, Function, FunctionSpace, Expression, VectorFunctionSpace,
dirichletbc, locate_dofs_topological)
from dolfinx.fem.petsc import LinearProblem
from petsc4py.PETSc import ScalarType
import matplotlib.pyplot as plt
# specified in mm
Tube_hight = 200
Tube_radius = 10
tube_perm = 1 # 4 * np.pi*1e-7 # Vacuum
obj1_perm = 100 # 1.26e-4 # Copper
obj2_perm = 0.001 # 1.26e-10 # plastic
gmsh.initialize()
# gmsh.model.add("cylinder_mesh")
gmsh.option.setNumber("Mesh.MeshSizeMin", 2)
gmsh.option.setNumber("Mesh.MeshSizeMax", 2)
# Create model
main_model = gmsh.model()
main_model.add('main_mesh')
main_model.setCurrent('main_mesh')
# Add the cylinder to the model
# disks for the bottom and top of the cylinder
bottom_disk = main_model.occ.addDisk(0, 0, 0, Tube_radius, Tube_radius, tag=1)
gmsh.model.occ.synchronize()
main_model.addPhysicalGroup(2, [bottom_disk], tag = 100, name="bottom_disk")
# extrude to make a cylinder
subdivision = [Tube_hight]
extrusion = gmsh.model.occ.extrude([(2, bottom_disk)], 0, 0, Tube_hight, subdivision)
# Generate the 3D geometry
main_model.occ.synchronize()
# We need to add physical surfaces for Fenicx
main_model.addPhysicalGroup(3, [extrusion[1][1]], tag=10, name="volume")
main_model.addPhysicalGroup(2, [extrusion[2][1]], tag = 101, name="side_surface")
main_model.addPhysicalGroup(2, [extrusion[0][1]], tag = 102, name="top_disk")
# Add an object with large permitivity
obj1 = main_model.occ.addSphere(0, 0, 150, 5, tag=103)
main_model.occ.synchronize()
main_model.addPhysicalGroup(3, [obj1], name="obj1")
# Add an object with small permitivity
obj2 = main_model.occ.addSphere(0, 0, 50, 5, tag=104)
main_model.occ.synchronize()
main_model.addPhysicalGroup(3, [obj2], name="obj2")
main_model.mesh.generate(3)
# main_model.mesh.refine()
gmsh_model_rank = 0
mesh_comm = MPI.COMM_WORLD
domain, ct, ft = gmshio.model_to_mesh(main_model, mesh_comm, gmsh_model_rank, gdim=3)
gmsh.finalize()
# Tags:
# 10 = tube interior
# 101 = tube sides
# 102 = top disk
# 100 = bottom disk
# 103 = obj1 (large perm)
# 104 = obj2 (small perm)
# Set up the functions
V = FunctionSpace(domain, ("CG", 1))
u = TrialFunction(V)
v = TestFunction(V)
# Define the material parameters
Q = FunctionSpace(domain, ("DG", 0))
material_tags = np.unique(ct.values)
eps = Function(Q)
# As we only set some values in eps, initialize all as vacuum
eps.x.array[:] = tube_perm
for tag in material_tags:
cells = ct.find(tag)
if tag == 103:
eps_ = obj1_perm
elif tag == 104:
eps_ = obj2_perm
else:
eps_ = tube_perm
eps.x.array[cells] = np.full_like(cells, eps_, dtype=ScalarType)
# Set the boundary conditions
tdim = domain.topology.dim
fdim = tdim - 1
# The top boundary is set to 10V
top_disk_facets = ft.indices[ft.values == 102]
top_disk_dofs = locate_dofs_topological(V, fdim, top_disk_facets)
u_T = Constant(domain, ScalarType(10))
top_bc = dirichletbc(u_T, top_disk_dofs, V)
# The bottom boundary is set to -10V
bottom_disk_facets = ft.indices[ft.values == 100]
bottom_disk_dofs = locate_dofs_topological(V, fdim, bottom_disk_facets)
u_B = Constant(domain, ScalarType(-10))
bottom_bc = dirichletbc(u_B, bottom_disk_dofs, V)
# The side boundary is set to 0V
side_disk_facets = ft.indices[ft.values == 101]
side_disk_dofs = locate_dofs_topological(V, fdim, side_disk_facets)
u_S = Constant(domain, ScalarType(0))
sides_bc = dirichletbc(u_S, side_disk_dofs, V)
bcs = [top_bc, bottom_bc, sides_bc]
# Construct the PDE and solve
a = dot(grad(u), grad(v)) * eps * dx
L = Constant(domain, ScalarType(1.e-16)) * v * dx
# Solve the linear problem
problem = LinearProblem(a, L, bcs=bcs)
uh = problem.solve()
# calculate the electric field
E_field = -grad(uh)
# project to mesh
V_E_field = VectorFunctionSpace(domain, ("CG", 2))
E_field_expr = Expression(E_field, V_E_field.element.interpolation_points())
E_field_projected = Function(V_E_field)
E_field_projected.interpolate(E_field_expr)
# Export solution
with XDMFFile(domain.comm, "output.xdmf", "w") as xdmf:
xdmf.write_mesh(domain)
uh.name = "Electric potential"
xdmf.write_function(uh)
E_field_projected.name = "Electric field"
xdmf.write_function(E_field_projected)
This is the electric potential which looks correct.
This is the field electric field. Note how the field abruptly changes in the center of the cylinder and how the objects inside the cylinder (highlighted) are ignored.