I have a two-phase system for FSI simulation. With everything built on a single big mesh, I need to compute variables on the solid phase only as shown in the attached figure.
(a) mask and velocity vector arrows, (b) velocity magnitude. (c) result of \omega=0.5\nabla \times u.
The test in the figure shows that when the velocity field u
is given, the rotation velocity curlU
of the solid is computed via projection of \omega=0.5\nabla \times u. Interpolation cannot do this as the velocity is continuous but not differentiable on the interface.
To do so, I have 2 scenarios:
- Do projection on the big mesh with the help of a function
mask
for solid and apply Dirichlet BC for fluid-only DOFs as
a = fem.form(ufl.inner(Pv, w) * mask * ufl.dx)
L = fem.form(ufl.inner( v, w) * mask * ufl.dx)
problem = LinearProblem(a, L, bcs, u=target_func,
petsc_options={"ksp_type": "preonly",
"pc_type": "lu",
"pc_factor_mat_solver_type": "mumps"})
problem.solve()
The full MWE is attached in the end.
- Create a submesh for solid with
submesh = mesh.create_submesh(big_mesh, tdim, solid_cells)[0]
. First, I interpolateu
from the big mesh to the submesh. Then, solve the projection on the submesh to getcurlU
. Finally, interpolatecurlU
to the function on the big mesh. The whole process involves 2 non-matching meshes interpolation.
In my application, I do two-phase FSI simulations in 3D with 2nd order iso-parametric meshes. Scenario 1 gets really slow as it involves too many unnecessary DOFs. Scenario 2 is fast but it often crashes with interpolation related GJK or Newton method failed to converge runtime errors. I sense the scenario 1 could be tuned for this problem, but still I am looking for a hand on this. Thanks.
MWE with DOLFINx-v0.8.0:
from dolfinx import mesh, io, fem
from dolfinx.fem.petsc import LinearProblem
from mpi4py import MPI
import numpy as np
import ufl
comm = MPI.COMM_WORLD
eps = 1e-8
def project(v, target_func, mask, bcs=[]) -> None:
V = target_func.function_space
w = ufl.TestFunction(V)
Pv = ufl.TrialFunction(V)
a = fem.form(ufl.inner(Pv, w) * mask * ufl.dx)
L = fem.form(ufl.inner( v, w) * mask * ufl.dx)
problem = LinearProblem(a, L, bcs, u=target_func,
petsc_options={"ksp_type": "preonly",
"pc_type": "lu",
"pc_factor_mat_solver_type": "mumps"})
problem.solve()
target_func.x.scatter_forward()
def solid_domain(x):
return np.logical_and(
np.logical_and(x[0] > 0.25 - eps, x[0] < 0.75 + eps),
np.logical_and(x[1] > 0.25 - eps, x[1] < 0.75 + eps))
_mesh = mesh.create_unit_square(comm, 32, 32)
tdim = _mesh.topology.dim
# Mask on a DG-0 function space
DG0 = fem.functionspace(_mesh, ("DG", 0))
mask = fem.Function(DG0)
_mesh.topology.create_connectivity(tdim, tdim)
solid_cells = mesh.locate_entities(_mesh, tdim, solid_domain)
solid_dofs = fem.locate_dofs_topological(DG0, tdim, solid_cells)
mask.x.array[solid_dofs] = 1.0
with io.XDMFFile(comm, "output/mask.xdmf", "w") as file:
file.write_mesh(_mesh)
file.write_function(mask)
# Build velocity field for 2 phases
omega = 1.0 # rotation velocity
center = ufl.as_vector([0.5, 0.5])
x = ufl.SpatialCoordinate(_mesh) - center
r = ufl.sqrt((x[0] - 0.5)**2 + (x[1] - 0.5)**2)
tri = ufl.as_vector([- x[1] / (r + eps), x[0] / (r + eps)])
V = fem.functionspace(_mesh, ("Lagrange", 1, (tdim,)))
Q = fem.functionspace(_mesh, ("Lagrange", 1))
u_fluid = fem.Function(V)
u_solid = fem.Function(V)
u = fem.Function(V)
mask_V = fem.Function(V)
solid_dofs_V = fem.locate_dofs_topological(V, tdim, solid_cells)
for d in range(tdim):
mask_V.x.array[solid_dofs_V * tdim + d] = 1.0
mask_V.x.scatter_forward()
expr = fem.Expression(omega * r * tri, V.element.interpolation_points())
u_solid.interpolate(expr)
u_solid.x.scatter_forward()
u_fluid.interpolate(lambda x: ((x[0] - 0.5), (x[1] - 0.5)))
u_fluid.x.scatter_forward()
u.x.array[:] = (u_solid.x.array[:] * mask_V.x.array[:] +
u_fluid.x.array[:] * (1 - mask_V.x.array[:]))
with io.XDMFFile(comm, "output/u.xdmf", "w") as file:
file.write_mesh(_mesh)
file.write_function(u)
curlU = fem.Function(Q)
total_dofs_Q = (Q.dofmap.index_map.size_local +
Q.dofmap.index_map.num_ghosts)
solid_dofs_Q = fem.locate_dofs_topological(Q, tdim, solid_cells)
fluid_only_dofs_Q = np.setdiff1d(np.arange(total_dofs_Q), solid_dofs_Q)
expr = 1. / 2. * ufl.curl(u)
f = fem.Function(Q)
bc_f = fem.dirichletbc(f, fluid_only_dofs_Q)
project(expr, curlU, mask, bcs=[bc_f])
with io.XDMFFile(comm, "output/omega.xdmf", "w") as file:
file.write_mesh(_mesh)
file.write_function(curlU)