Dear all,
I am working with periodic boundary conditions and this is a follow-up topic of this post. The aim is to set periodic BCs for large meshes, but the solution of my test cases are not periodic, unfortunately.
The following MWE consists of 4 test cases with simple linear Lagrange elements, and the thermal homogenization method is applied.
The result: Applying periodic BCs to a unit cube works (see the Figure;I also tested it with e.g. 200**3
elements on the HPC) and it also works for a box with the length of 20
in each direction, but not for 40
(see the Figure; the element size is 1
for each box mesh):
import dolfinx
import dolfinx_mpc
from dolfinx.fem import FunctionSpace, Function, Constant, assemble_scalar, form
from dolfinx.io import XDMFFile
from dolfinx.mesh import locate_entities_boundary, meshtags, locate_entities
from ufl import grad, dot, lhs, rhs, Measure, TrialFunction, TestFunction, FiniteElement
import ufl
from petsc4py import PETSc
import numpy as np
from mpi4py import MPI
# MPI initialization
comm = MPI.COMM_WORLD
rank = MPI.COMM_WORLD.rank
##################################################
## Mesh generation Start
##################################################
n_size_1 = 20
n_size_2 = 40
unit_cube20 = dolfinx.mesh.create_unit_cube(
MPI.COMM_WORLD, n_size_1, n_size_1, n_size_1, cell_type=dolfinx.mesh.CellType.hexahedron)
unit_cube40 = dolfinx.mesh.create_unit_cube(
MPI.COMM_WORLD, n_size_2, n_size_2, n_size_2, cell_type=dolfinx.mesh.CellType.hexahedron)
box20 = dolfinx.mesh.create_box(MPI.COMM_WORLD,
[np.array([0, 0, 0]), np.array([n_size_1, n_size_1, n_size_1])],
[n_size_1, n_size_1, n_size_1], cell_type=dolfinx.mesh.CellType.hexahedron)
box40 = dolfinx.mesh.create_box(MPI.COMM_WORLD,
[np.array([0, 0, 0]), np.array([n_size_2, n_size_2, n_size_2])],
[n_size_2, n_size_2, n_size_2], cell_type=dolfinx.mesh.CellType.hexahedron)
gdim = 3 # Geometric dimension of the mesh
fdim = gdim - 1 # facet dimension
def thermal_hom(domain, ij):
L_max = domain.geometry.x[:, 0].max()
L_min = domain.geometry.x[:, 0].min()
radius = L_max/10
midpoint = L_max/2
def cells_sphere(x):
return ((x[0]-midpoint)**2 + (x[1]-midpoint)**2 + (x[2]-midpoint)**2) <= radius**2
def cells_cube(x):
return ((x[0]-midpoint)**2 + (x[1]-midpoint)**2 + (x[2]-midpoint)**2) >= radius**2
cells_0 = locate_entities(domain, domain.topology.dim, cells_sphere)
cells_1 = locate_entities(domain, domain.topology.dim, cells_cube)
##################################################
## Mesh generation finished
##################################################
dx = Measure('dx', domain=domain)
x = ufl.SpatialCoordinate(domain)
# elements and funcionspace
###########################
P1 = FiniteElement("CG", domain.ufl_cell(), 1)
V = FunctionSpace(domain, P1)
# define function, trial function and test function
T_fluc = TrialFunction(V)
dT_fluc = TestFunction(V)
# define material parameter
S = FunctionSpace(domain, ("DG", 0))
kappa = Function(S)
kappa.x.array[:] = 2.5
kappa.x.array[cells_0] = np.full_like(cells_0, 5.0, dtype=PETSc.ScalarType)
kappa.x.array[cells_1] = np.full_like(cells_1, 1.0, dtype=PETSc.ScalarType)
kappa.x.scatter_forward()
# boundary conditions
#####################
bcs = []
## periodic boundary conditions
pbc_directions = []
pbc_slave_tags = []
pbc_is_slave = []
pbc_is_master = []
pbc_meshtags = []
pbc_slave_to_master_maps = []
mpc = dolfinx_mpc.MultiPointConstraint(V)
def generate_pbc_slave_to_master_map(i):
def pbc_slave_to_master_map(x):
out_x = x.copy()
out_x[i] = x[i] - L_max
return out_x
return pbc_slave_to_master_map
def generate_pbc_is_slave(i):
return lambda x: np.isclose(x[i], L_max)
def generate_pbc_is_master(i):
return lambda x: np.isclose(x[i], L_min)
for i in range(gdim):
pbc_directions.append(i)
pbc_slave_tags.append(i + 3)
pbc_is_slave.append(generate_pbc_is_slave(i))
pbc_is_master.append(generate_pbc_is_master(i))
pbc_slave_to_master_maps.append(generate_pbc_slave_to_master_map(i))
facets = locate_entities_boundary(domain, fdim, pbc_is_slave[-1])
arg_sort = np.argsort(facets)
pbc_meshtags.append(meshtags(domain,
fdim,
facets[arg_sort],
np.full(len(facets), pbc_slave_tags[-1], dtype=np.int32)))
N_pbc = len(pbc_directions)
for i in range(N_pbc):
# slave/master mapping of opposite surfaces (without slave-slave[-slave] intersections)
def pbc_slave_to_master_map(x):
out_x = pbc_slave_to_master_maps[i](x)
idx = np.logical_or(pbc_is_slave[(i + 1) % N_pbc](x),pbc_is_slave[(i + 2) % N_pbc](x))
out_x[i][idx] = np.nan
return out_x
mpc.create_periodic_constraint_topological(V, pbc_meshtags[i],
pbc_slave_tags[i],
pbc_slave_to_master_map,
bcs)
if len(pbc_directions) > 1:
def generate_pbc_slave_to_master_map_edges(dir_i, dir_j):
def pbc_slave_to_master_map_edges(x):
'''
Maps the slave edge dofs [intersection of slave_i, slave_j] (i=1,j=1) to
master corner dof [master_i, master_j] (i=0,j=0)
(1) map slave_x, slave_y to master_x, master_y: i=0, j=1
(2) map slave_x, slave_z to master_x, master_z: i=0, j=2
(3) map slave_y, slave_z to master_y, master_z: i=1, j=2
'''
out_x = x.copy()
out_x[dir_i] = x[dir_i] - L_max
out_x[dir_j] = x[dir_j] - L_max
idx = np.logical_and(pbc_is_slave[dir_i](x), pbc_is_slave[dir_j](x))
out_x[dir_i][~idx] = np.nan
out_x[dir_j][~idx] = np.nan
# remove corner DOFs at point (1,1,1)
idx_corner = np.logical_and(pbc_is_slave[0](x), np.logical_and(pbc_is_slave[1](x), pbc_is_slave[2](x)))
out_x[dir_i][idx_corner] = np.nan
out_x[dir_j][idx_corner] = np.nan
return out_x
return pbc_slave_to_master_map_edges
def pbc_slave_to_master_map_corner(x):
'''
Maps the slave corner dof [intersection of slave_x, slave_y, slave_z] (1,1,1) to
master corner dof [master_x, master_y, master_z] (0,0,0)
'''
out_x = x.copy()
out_x[0] = x[0] - L_max
out_x[1] = x[1] - L_max
out_x[2] = x[2] - L_max
idx_corner = np.logical_and(pbc_is_slave[0](x), np.logical_and(pbc_is_slave[1](x), pbc_is_slave[2](x)))
out_x[0][~idx_corner] = np.nan
out_x[1][~idx_corner] = np.nan
out_x[2][~idx_corner] = np.nan
return out_x
mapping_slave_intersections = [(0,1),(0,2),(1,2)] # pairs of slave intersections
# mapping slave intersection node (corner) to master intersection node (opposite corner)
mpc.create_periodic_constraint_topological(V, pbc_meshtags[0],
pbc_slave_tags[0],
pbc_slave_to_master_map_corner,
bcs)
for inters in mapping_slave_intersections:
# mapping slave intersections to opposite master intersections
mpc.create_periodic_constraint_topological(V, pbc_meshtags[inters[0]],
pbc_slave_tags[inters[0]],
generate_pbc_slave_to_master_map_edges(inters[0], inters[1]),
bcs)
mpc.finalize()
# Variational problem
#####################
T_gradient = Constant(domain, PETSc.ScalarType((1,0,0)))
## weak formulation
eqn = - kappa * dot(grad(dot(T_gradient , x) + T_fluc), grad(dT_fluc)) * dx
a_form = lhs(eqn)
L_form = rhs(eqn)
## solving
func_sol = Function(V)
problem = dolfinx_mpc.LinearProblem(a_form, L_form, mpc, bcs=bcs)
func_sol = problem.solve()
# write results to file
file_results1 = XDMFFile(domain.comm, f"results_3D_MWE_v7/T_fluc{ij}.xdmf", "w")
file_results1.write_mesh(domain)
file_results1.write_function(func_sol)
file_results1.close()
if __name__ == "__main__":
thermal_hom(unit_cube20, 0)
thermal_hom(unit_cube40, 1)
thermal_hom(box20, 2)
thermal_hom(box40, 3)
Does anyone have an idea why the mapping in test case 4 is wrong?
I am using version 0.7.2
and the calculations are done in serial.