dolfinx ver 0.7.1
Exemplary code:
import typing
import dolfinx.fem
import dolfinx.fem.petsc
import dolfinx.io
import gmsh
from mpi4py import MPI
import numpy as np
import petsc4py.PETSc
import ufl
import viskex
from dolfinx.mesh import locate_entities
from dolfinx.io import gmshio, XDMFFile, VTXWriter
from dolfinx import plot
import dolfinx
import numpy as np
from dolfinx import plot, default_scalar_type
import basix.ufl
import multiphenicsx
from petsc4py import PETSc
from dolfinx import fem
import multiphenicsx.fem
import multiphenicsx.fem.petsc
g = 0.2
R = 1
gdim = 2
c_x, c_y = 1, 4
r = 0.5
# GMSH class
def create_mesh_with_gmsh(gdim=gdim):
gmsh.initialize()
gmsh.model.add("three_squares")
# Define points for the squares and lines
# Outer loop
p1 = gmsh.model.occ.addPoint(0, 0, 0)
p2 = gmsh.model.occ.addPoint(1, 0, 0)
p3 = gmsh.model.occ.addPoint(1, 3, 0)
p4 = gmsh.model.occ.addPoint(3, 3, 0)
p5 = gmsh.model.occ.addPoint(3, 3 + g, 0)
p6 = gmsh.model.occ.addPoint(2 + np.sqrt(g*(2*R-g)), 3 + g, 0)
p7 = gmsh.model.occ.addPoint(2, 3 + 2*R, 0)
p8 = gmsh.model.occ.addPoint(1, 3 + 2*R, 0)
p9 = gmsh.model.occ.addPoint(1, 7, 0)
p10 = gmsh.model.occ.addPoint(0, 7, 0)
# Inner loop
p11 = gmsh.model.occ.addPoint(1, 3 + g, 0)
p12 = gmsh.model.occ.addPoint(2, 3 + g, 0)
p13 = gmsh.model.occ.addPoint(2, 3 + 2*R - g, 0)
p14 = gmsh.model.occ.addPoint(1, 3 + 2*R - g, 0)
c = gmsh.model.occ.addPoint(2, 3 + R, 0)
obstacle = gmsh.model.occ.addDisk(c_x, c_y, 0, r, r)
# Create the squares
l1 = gmsh.model.occ.addLine(p1, p2) # --> Inlet
l2 = gmsh.model.occ.addLine(p2, p3)
l3 = gmsh.model.occ.addLine(p3, p4)
l4 = gmsh.model.occ.addLine(p4, p5)
l5 = gmsh.model.occ.addLine(p5, p6)
l6_arc1 = gmsh.model.occ.addCircleArc(p6, c, p7)
l7 = gmsh.model.occ.addLine(p7, p8)
l8 = gmsh.model.occ.addLine(p8, p9)
l9 = gmsh.model.occ.addLine(p9, p10) # --> Outlet
l10 = gmsh.model.occ.addLine(p10, p1)
loop1 = gmsh.model.occ.addCurveLoop([l1, l2, l3, l4, l5, l6_arc1, l7, l8, l9, l10])
shape1 = gmsh.model.occ.addPlaneSurface([loop1])
Omega_fluid = gmsh.model.occ.cut([(gdim, shape1)], [(gdim, obstacle)])
gmsh.model.occ.synchronize()
# Add physical groups
fluid_marker = 1
volumes = gmsh.model.getEntities(dim=gdim)
assert (len(volumes) == 1)
gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]], fluid_marker)
gmsh.model.setPhysicalName(volumes[0][0], fluid_marker, "Fluid")
gmsh.model.addPhysicalGroup(1, [l1], 101) # --> Inlet
gmsh.model.addPhysicalGroup(1, [l9], 102) # --> Outlet
gmsh.model.addPhysicalGroup(1, [l2, l3, l4, l5, l6_arc1, l7, l8, l10], 201) # --> Walls
# # gmsh.model.addPhysicalGroup(1, [l11, l12_arc2, l13, l14], 202) # --> InnerWalls
obstacle = []
boundaries = gmsh.model.getBoundary(volumes, oriented=False)
for boundary in boundaries:
center_of_mass = gmsh.model.occ.getCenterOfMass(boundary[0], boundary[1])
if np.allclose(center_of_mass, [c_x, c_y, 0]):
obstacle.append(boundary[1])
obstacle_marker = 202
gmsh.model.addPhysicalGroup(1, obstacle, obstacle_marker)
gmsh.model.setPhysicalName(1, obstacle_marker, "Obstacle")
# Meshing options
gmsh.option.setNumber("Mesh.ElementOrder", 2)
gmsh.option.setNumber("Mesh.CharacteristicLengthMin", 0.2)
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", 0.2)
gmsh.model.mesh.generate(2)
# Convert Gmsh model to DOLFINx mesh
gdim = 2
gmsh_model_rank = 0
mesh_comm = MPI.COMM_WORLD
mesh, cell_tags, facet_tags = gmshio.model_to_mesh(gmsh.model, mesh_comm, gmsh_model_rank, gdim=gdim)
mesh.topology.create_entities(mesh.topology.dim - 1)
mesh.topology.create_connectivity(mesh.topology.dim, mesh.topology.dim - 1)
gmsh.finalize()
return mesh, cell_tags, facet_tags
# Create and get the dolfinx mesh
mesh, subdomains, boundaries = create_mesh_with_gmsh()
cells_Omega1 = subdomains.indices[subdomains.values == 1]
dx = ufl.Measure("dx")(subdomain_data=subdomains)
# %% ############################################################
# < Define function spaces >
Vns_element = basix.ufl.element("Lagrange", mesh.basix_cell(), 2, shape=(mesh.geometry.dim, ))
Qns_element = basix.ufl.element("Lagrange", mesh.basix_cell(), 1)
Vns = dolfinx.fem.functionspace(mesh, Vns_element)
Qns = dolfinx.fem.functionspace(mesh, Qns_element)
(v, q) = (ufl.TestFunction(Vns), ufl.TestFunction(Qns))
(du, dp) = (ufl.TrialFunction(Vns), ufl.TrialFunction(Qns))
(u, p) = (dolfinx.fem.Function(Vns), dolfinx.fem.Function(Qns))
u_n = dolfinx.fem.Function(Vns) # Previous time step velocity
# Define restrictions
dofs_Vns_Omega1 = dolfinx.fem.locate_dofs_topological(Vns, subdomains.dim, cells_Omega1)
restriction_Vns_Omega1 = multiphenicsx.fem.DofMapRestriction(Vns.dofmap, dofs_Vns_Omega1)
dofs_Qns_Omega1 = dolfinx.fem.locate_dofs_topological(Qns, subdomains.dim, cells_Omega1)
restriction_Qns_Omega1 = multiphenicsx.fem.DofMapRestriction(Qns.dofmap, dofs_Qns_Omega1)
restriction = [restriction_Vns_Omega1, restriction_Qns_Omega1] #, restriction_M_Gamma
# %% ############################################################
# Parameters
t = 0
T = 8 # Final time
dt=0.1
num_steps = int(np.floor(T-t) / dt)
nu = 0.01
# Blocked form
F = [(1/dt) * ufl.inner(u - u_n, v) * dx(1) + nu * ufl.inner(ufl.grad(u), ufl.grad(v)) * dx(1) +
ufl.inner(ufl.grad(u) * u, v) * dx(1) - ufl.inner(p, ufl.div(v)) * dx(1),
ufl.inner(ufl.div(u), q) * dx(1)]
# Jacobian
J = [[ufl.derivative(F[0], u, du), ufl.derivative(F[0], p, dp)],
[ufl.derivative(F[1], u, du), ufl.derivative(F[1], p, dp)]]
# %% ################################################
# < Define boundary conditions >
gdim=2
class InletVelocity():
def __init__(self, t):
self.t = t
def __call__(self, x):
values = np.zeros((2, x.shape[1]), dtype=PETSc.ScalarType)
values[1] = 25*x[0]*(1-x[0])/(1**2) #, x[1]-x[1]
return values
# Inlet
u_inlet = dolfinx.fem.Function(Vns)
inlet_velocity = InletVelocity(t)
u_inlet.interpolate(inlet_velocity)
# u_inlet.interpolate(u_in_eval)
selected_tags_inlet = [101]
boundaries_inlet = boundaries.indices[np.isin(boundaries.values, selected_tags_inlet)]
bcu_inflow = dolfinx.fem.dirichletbc(u_inlet, dolfinx.fem.locate_dofs_topological(Vns, mesh.topology.dim - 1, boundaries_inlet))
# OuterWalls
u_nonslip = np.array((0,) * gdim, dtype=PETSc.ScalarType)
selected_tags_walls_outer = [201]
boundaries_walls_outer = boundaries.indices[np.isin(boundaries.values, selected_tags_walls_outer)]
bcu_walls_outer = dolfinx.fem.dirichletbc(u_nonslip, dolfinx.fem.locate_dofs_topological(Vns, mesh.topology.dim - 1, boundaries_walls_outer), Vns)
# InnerWalls
selected_tags_walls = [202]
boundaries_walls = boundaries.indices[np.isin(boundaries.values, selected_tags_walls)]
bcu_walls_inner = dolfinx.fem.dirichletbc(u_nonslip, dolfinx.fem.locate_dofs_topological(Vns, mesh.topology.dim - 1, boundaries_walls), Vns)
# # velocity outlet
selected_tags_pressureoutlet = [102]
boundaries_veloutlet = boundaries.indices[np.isin(boundaries.values, selected_tags_pressureoutlet)]
bdofs_V_out = dolfinx.fem.locate_dofs_topological(Vns, mesh.topology.dim - 1, boundaries_veloutlet)
bcu_outlet = dolfinx.fem.dirichletbc(u_inlet, bdofs_V_out)
bcu = [bcu_inflow, bcu_walls_outer, bcu_walls_inner, bcu_outlet]
# %% ############################################################
class NonlinearProblemV1V2(object):
"""Define a nonlinear problem, interfacing with SNES."""
def __init__( # type: ignore[no-any-unimported]
self, F: typing.List[ufl.Form], J: typing.List[typing.List[ufl.Form]],
solutions: typing.Tuple[dolfinx.fem.Function, dolfinx.fem.Function],
bcs: typing.List[dolfinx.fem.DirichletBC],
V1: dolfinx.fem.FunctionSpace, V2: dolfinx.fem.FunctionSpace,
restriction: typing.List[multiphenicsx.fem.DofMapRestriction],
P: typing.Optional[typing.List[typing.List[ufl.Form]]] = None
) -> None:
self._F = dolfinx.fem.form(F)
self._J = dolfinx.fem.form(J)
self.V1 = V1
self.V2 = V2
self.restriction = restriction
self._obj_vec = multiphenicsx.fem.petsc.create_vector_block(self._F, self.restriction)
self._solutions = solutions
self._bcs = bcs
self._P = P
def create_snes_solution(self) -> petsc4py.PETSc.Vec: # type: ignore[no-any-unimported]
"""
Create a petsc4py.PETSc.Vec to be passed to petsc4py.PETSc.SNES.solve.
The returned vector will be initialized with the initial guesses provided in `self._solutions`,
properly stacked together and restricted in a single block vector.
"""
x = multiphenicsx.fem.petsc.create_vector_block(self._F, restriction=self.restriction)
with multiphenicsx.fem.petsc.BlockVecSubVectorWrapper(x, [self.V1.dofmap, self.V2.dofmap], restriction) as x_wrapper:
for x_wrapper_local, sub_solution in zip(x_wrapper, self._solutions):
with sub_solution.vector.localForm() as sub_solution_local:
x_wrapper_local[:] = sub_solution_local
return x
def update_solutions(self, x: petsc4py.PETSc.Vec) -> None: # type: ignore[no-any-unimported]
"""Update `self._solutions` with data in `x`."""
x.ghostUpdate(addv=petsc4py.PETSc.InsertMode.INSERT, mode=petsc4py.PETSc.ScatterMode.FORWARD)
with multiphenicsx.fem.petsc.BlockVecSubVectorWrapper(x, [self.V1.dofmap, self.V2.dofmap], restriction) as x_wrapper:
for x_wrapper_local, sub_solution in zip(x_wrapper, self._solutions):
with sub_solution.vector.localForm() as sub_solution_local:
sub_solution_local[:] = x_wrapper_local
def obj( # type: ignore[no-any-unimported]
self, snes: petsc4py.PETSc.SNES, x: petsc4py.PETSc.Vec
) -> np.float64:
"""Compute the norm of the residual."""
self.F(snes, x, self._obj_vec)
return self._obj_vec.norm() # type: ignore[no-any-return]
def F( # type: ignore[no-any-unimported]
self, snes: petsc4py.PETSc.SNES, x: petsc4py.PETSc.Vec, F_vec: petsc4py.PETSc.Vec
) -> None:
"""Assemble the residual."""
self.update_solutions(x)
with F_vec.localForm() as F_vec_local:
F_vec_local.set(0.0)
multiphenicsx.fem.petsc.assemble_vector_block( # type: ignore[misc]
F_vec, self._F, self._J, self._bcs, x0=x, scale=-1.0,
restriction=self.restriction, restriction_x0=self.restriction)
def J( # type: ignore[no-any-unimported]
self, snes: petsc4py.PETSc.SNES, x: petsc4py.PETSc.Vec, J_mat: petsc4py.PETSc.Mat,
P_mat: petsc4py.PETSc.Mat
) -> None:
"""Assemble the jacobian."""
J_mat.zeroEntries()
multiphenicsx.fem.petsc.assemble_matrix_block(
J_mat, self._J, self._bcs, diagonal=1.0, # type: ignore[arg-type]
restriction=(self.restriction, self.restriction))
J_mat.assemble()
if self._P is not None:
P_mat.zeroEntries()
multiphenicsx.fem.petsc.assemble_matrix_block(
P_mat, self._P, self._bcs, diagonal=1.0, # type: ignore[arg-type]
restriction=(self.restriction, self.restriction))
P_mat.assemble()
# %% ############################################################
# Create a problem
# problem = NonlinearProblemV1V2M(F, J, (u1, u2, Rb), bcs)
problem = NonlinearProblemV1V2(F, J, (u, p), bcu, Vns, Qns, restriction)
F_vec = multiphenicsx.fem.petsc.create_vector_block(problem._F, restriction=restriction)
J_mat = multiphenicsx.fem.petsc.create_matrix_block(problem._J, restriction=(restriction, restriction))
snes = petsc4py.PETSc.SNES().create(mesh.comm)
snes.setTolerances(max_it=100)
snes.getKSP().setType("preonly")
snes.getKSP().getPC().setType("lu")
snes.getKSP().getPC().setFactorSolverType("mumps")
snes.setObjective(problem.obj)
snes.setFunction(problem.F, F_vec)
snes.setJacobian(problem.J, J=J_mat, P=None)
solution = problem.create_snes_solution()
# < Solve system >
from tqdm import tqdm
for i in tqdm(range(num_steps)):
t += dt
snes.solve(None, solution)
u_n.x.array[:] = u.x.array[:]
# Finalize
solution.destroy()
snes.destroy()