Skip to content

Commit 6ed6b2a

Browse files
committed
Add flow over heated plate for fenicsx
1 parent d15fdc9 commit 6ed6b2a

File tree

4 files changed

+227
-0
lines changed

4 files changed

+227
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"participant_name": "Solid",
3+
"precice_config_file_path": "../precice-config.xml",
4+
"interfaces": [
5+
{
6+
"mesh_name": "Solid-Mesh",
7+
"write_data": [
8+
{
9+
"name": "Heat-Flux"
10+
}
11+
],
12+
"read_data": [
13+
{
14+
"name": "Temperature"
15+
}
16+
]
17+
}
18+
]
19+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
numpy
2+
fenicsxprecice
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
set -e -u
3+
4+
python3 -m venv --system-site-packages .venv
5+
. .venv/bin/activate
6+
pip install -r requirements.txt
7+
8+
python3 solid.py
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import numpy as np
2+
from mpi4py import MPI
3+
import basix.ufl
4+
from petsc4py import PETSc
5+
import ufl
6+
from dolfinx import fem, io, mesh as msh, default_scalar_type
7+
from dolfinx.fem.petsc import assemble_matrix, assemble_vector, apply_lifting, create_vector, set_bc, LinearProblem
8+
from dolfinx.mesh import create_rectangle
9+
import basix
10+
from fenicsxprecice import Adapter, CouplingMesh
11+
12+
13+
# geometry
14+
nx = 100
15+
ny = 25
16+
nz = 1
17+
18+
y_top = 0
19+
y_bottom = y_top - .25
20+
x_left = 0
21+
x_right = x_left + 1
22+
23+
fenics_dt = 0.01 # time step size
24+
25+
26+
def top_boundary(x):
27+
tol = 1E-14
28+
return np.isclose(x[1], y_top, tol)
29+
30+
31+
def bottom_boundary(x):
32+
tol = 1E-14
33+
return np.isclose(x[1], y_bottom, tol)
34+
35+
36+
class initial_value():
37+
def __init__(self, constant):
38+
self.constant = constant
39+
40+
def __call__(self, x):
41+
return np.full(x[0].shape, self.constant)
42+
43+
44+
class GradientSolver:
45+
"""
46+
compute flux following http://hplgit.github.io/INF5620/doc/pub/fenics_tutorial1.1/tu2.html#tut-poisson-gradu
47+
The solver has been changed since the original version from the link above introduces larger errors
48+
49+
:param V_g: Vector function space
50+
:param u: solution where gradient is to be determined
51+
"""
52+
53+
def __init__(self, domain, V_g):
54+
self.domain = domain,
55+
self.V_g = V_g
56+
57+
w = ufl.TrialFunction(V_g)
58+
self.v = ufl.TestFunction(V_g)
59+
a = fem.form(ufl.inner(w, self.v) * ufl.dx)
60+
self.A = assemble_matrix(a)
61+
self.A.assemble()
62+
63+
self.solver = PETSc.KSP().create(domain.comm)
64+
self.solver.setOperators(self.A)
65+
self.solver.setType(PETSc.KSP.Type.PREONLY)
66+
self.solver.getPC().setType(PETSc.PC.Type.LU)
67+
68+
self.returnValue = fem.Function(V_g)
69+
70+
def compute(self, u, k):
71+
L = fem.form(ufl.inner(-k * ufl.grad(u), self.v) * ufl.dx)
72+
b = create_vector(fem.extract_function_spaces(L))
73+
assemble_vector(b, L)
74+
b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
75+
self.solver.solve(b, self.returnValue.x.petsc_vec)
76+
return self.returnValue
77+
78+
79+
p0 = (x_left, y_bottom)
80+
p1 = (x_right, y_top)
81+
82+
mesh = create_rectangle(MPI.COMM_WORLD, [np.asarray(p0), np.asarray(p1)], [nx, ny], msh.CellType.triangle)
83+
V = fem.functionspace(mesh, ('P', 2))
84+
# for the vector function space
85+
element = basix.ufl.element("Lagrange", mesh.topology.cell_name(), 1, shape=(mesh.geometry.dim,))
86+
V_g = fem.functionspace(mesh, element)
87+
W, map_to_W = V_g.sub(1).collapse()
88+
89+
gradient_solver = GradientSolver(mesh, V_g)
90+
91+
alpha = 1 # m^2/s, https://en.wikipedia.org/wiki/Thermal_diffusivity
92+
k = 100 # kg * m / s^3 / K, https://en.wikipedia.org/wiki/Thermal_conductivity
93+
94+
# We will only exchange flux in y direction on coupling interface. No initialization necessary.
95+
flux_y = fem.Function(W)
96+
97+
# Define initial value
98+
u_n = fem.Function(V)
99+
u_n.interpolate(initial_value(310))
100+
101+
tdim = mesh.topology.dim
102+
fdim = tdim - 1
103+
mesh.topology.create_connectivity(fdim, tdim)
104+
dofs_coupling = fem.locate_dofs_geometrical(V, top_boundary)
105+
dofs_bottom = fem.locate_dofs_geometrical(V, bottom_boundary)
106+
107+
# Adapter definition and initialization
108+
precice = Adapter(adapter_config_filename="precice-adapter-config.json", mpi_comm=MPI.COMM_WORLD)
109+
110+
# top_boundary is coupling boundary
111+
coupling_mesh = CouplingMesh("Solid-Mesh", top_boundary, {"Temperature": V}, {"Heat-Flux": flux_y})
112+
precice.initialize([coupling_mesh])
113+
114+
# boundary function for the coupling interface
115+
coupling_function = fem.Function(V)
116+
117+
# Assigning appropriate dt
118+
precice_dt = precice.get_max_time_step_size()
119+
dt = np.min([fenics_dt, precice_dt])
120+
121+
# Define variational problem
122+
u = ufl.TrialFunction(V)
123+
v = ufl.TestFunction(V)
124+
F = u * v / dt * ufl.dx + alpha * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx - u_n * v / dt * ufl.dx
125+
126+
# apply constant Dirichlet boundary condition at bottom edge
127+
# apply Dirichlet boundary condition on coupling interface
128+
bcs = [fem.dirichletbc(coupling_function, dofs_coupling), fem.dirichletbc(default_scalar_type(310), dofs_bottom, V)]
129+
130+
a = fem.form(ufl.lhs(F))
131+
L = fem.form(ufl.rhs(F))
132+
133+
A = assemble_matrix(a, bcs=bcs)
134+
A.assemble()
135+
b = create_vector(fem.extract_function_spaces(L))
136+
uh = fem.Function(V)
137+
solver = PETSc.KSP().create(mesh.comm)
138+
solver.setOperators(A)
139+
solver.setType(PETSc.KSP.Type.PREONLY)
140+
solver.getPC().setType(PETSc.PC.Type.LU)
141+
142+
143+
# Time-stepping
144+
t = 0
145+
146+
vtxwriter = io.VTXWriter(MPI.COMM_WORLD, f"output_solid.bp", [u_n])
147+
vtxwriter.write(t)
148+
149+
n = 0
150+
151+
flux = fem.Function(V_g)
152+
153+
while precice.is_coupling_ongoing():
154+
155+
if precice.requires_writing_checkpoint(): # write checkpoint
156+
precice.store_checkpoint(u_n, t, 0)
157+
158+
precice_dt = precice.get_max_time_step_size()
159+
dt = np.min([fenics_dt, precice_dt])
160+
precice.read_data(coupling_mesh.get_name(), "Temperature", dt, coupling_function)
161+
162+
# Update the right hand side reusing the initial vector
163+
with b.localForm() as loc_b:
164+
loc_b.set(0)
165+
assemble_vector(b, L)
166+
167+
apply_lifting(b, [a], [bcs])
168+
set_bc(b, bcs)
169+
170+
# Solve linear problem
171+
solver.solve(b, uh.x.petsc_vec)
172+
173+
# Dirichlet problem obtains flux from solution and sends flux on boundary to Neumann problem
174+
flux = gradient_solver.compute(u_n, k)
175+
flux_y.interpolate(flux.sub(1))
176+
precice.write_data(coupling_mesh.get_name(), "Heat-Flux", flux_y)
177+
178+
precice.advance(dt)
179+
180+
if precice.requires_reading_checkpoint():
181+
u_cp, t_cp, _ = precice.retrieve_checkpoint()
182+
u_n.x.array[:] = u_cp.x.array
183+
t = t_cp
184+
else: # update solution
185+
# Update solution at previous time step (u_n)
186+
u_n.x.array[:] = uh.x.array
187+
t += float(dt)
188+
n += 1
189+
if n % 20 == 0:
190+
vtxwriter.write(t)
191+
192+
if precice.is_time_window_complete():
193+
# update boundary condition not necessary because it is constant
194+
pass
195+
196+
197+
precice.finalize()
198+
vtxwriter.close()

0 commit comments

Comments
 (0)