Skip to content

Commit e5fffa6

Browse files
NiklasKotarskyNilleuekermanMakisH
authored
Add Dune-Fem Python solid solver to flow over heated plate (precice#274)
Co-authored-by: Nille <niklas.kotarsky@math.lu.se> Co-authored-by: Benjamin Uekermann <benjamin.uekermann@gmail.com> Co-authored-by: Gerasimos Chourdakis <chourdak@in.tum.de>
1 parent 5651e14 commit e5fffa6

File tree

6 files changed

+154
-0
lines changed

6 files changed

+154
-0
lines changed

flow-over-heated-plate/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Solid participant:
3535

3636
* Nutils. For more information, have a look at the [Nutils adapter documentation](https://precice.org/adapter-nutils.html).
3737

38+
* Dune-Fem. For more information, have a look at the [official documentation of Dune-Fem](https://www.dune-project.org/sphinx/dune-fem/). The solver can be installed through [PyPI](https://pypi.org/project/dune-fem/). Make sure that you are in a Python virtual environment first, which you can create inside the `solid-dune` directory and load again before running (you may need to install some tools again in this environment). Please note that Dune-Fem uses just-in-time compilation: The first time you run the solver script, it will take some time.
39+
3840
## Running the Simulation
3941

4042
All listed solvers can be used in order to run the simulation. Open two separate terminals and start the desired fluid and solid participant by calling the respective run script `run.sh` located in the participant directory. For example:
Loading
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
set -e -u
3+
4+
. ../../tools/cleaning-tools.sh
5+
6+
clean_dune .
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
set -e -u
3+
4+
python3 solid.py
5+
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env python3
2+
3+
import numpy as np
4+
import precice
5+
import os
6+
from scipy.interpolate import interp1d
7+
8+
import ufl
9+
from dune.fem.space import lagrange as solutionSpace
10+
from dune.ufl import DirichletBC, Constant
11+
from dune.fem.scheme import galerkin as solutionScheme
12+
from dune.fem.operator import galerkin
13+
from dune.fem.utility import Sampler
14+
from dune.grid import cartesianDomain
15+
from dune.alugrid import aluSimplexGrid
16+
from dune.fem.function import uflFunction, gridFunction
17+
from dune.ufl import expression2GF
18+
19+
if __name__ == '__main__':
20+
21+
# standard heat equation: u_t + k \Delta u = 0
22+
k = 100 # kg * m / s^3 / K, https://en.wikipedia.org/wiki/Thermal_conductivity
23+
24+
# Create mesh and define function space
25+
nx = 100
26+
ny = 25
27+
28+
dt_out = 0.2 # interval for writing VTK files
29+
y_top = 0
30+
y_bottom = y_top - .25
31+
x_left = 0
32+
x_right = x_left + 1
33+
34+
# preCICE setup
35+
interface = precice.Interface("Solid", "../precice-config.xml", 0, 1)
36+
37+
# define coupling mesh
38+
mesh_name = "Solid-Mesh"
39+
mesh_id = interface.get_mesh_id(mesh_name)
40+
interface_x_coordinates = np.linspace(0, 1, nx + 1) # meshpoints for interface values
41+
vertices = [[x0, 0] for x0 in interface_x_coordinates]
42+
vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
43+
temperature_id = interface.get_data_id("Temperature", mesh_id)
44+
flux_id = interface.get_data_id("Heat-Flux", mesh_id)
45+
46+
domain = cartesianDomain([x_left, y_bottom], [x_right, y_top], [nx, ny])
47+
mesh = aluSimplexGrid(domain, serial=True)
48+
space = solutionSpace(mesh, order=1)
49+
u = ufl.TrialFunction(space)
50+
v = ufl.TestFunction(space)
51+
x = ufl.SpatialCoordinate(ufl.triangle)
52+
n = ufl.FacetNormal(space)
53+
54+
u0 = uflFunction(mesh, name="u0", order=space.order,
55+
ufl=Constant(310))
56+
uold = space.interpolate(u0, name='uold')
57+
unew = space.interpolate(u0, name='unew')
58+
ucheckpoint = uold.copy(name='u_checkpoint')
59+
60+
# creating boundary condition with data from the fluid
61+
62+
ug_interp = interp1d(interface_x_coordinates, np.zeros(nx + 1))
63+
def ug_f(x): return ug_interp(x[0])
64+
65+
@gridFunction(mesh, name="u_gamma", order=1)
66+
def u_gamma(xg):
67+
return ug_f(xg)
68+
69+
eps = 1e-8
70+
bc_top = DirichletBC(space, u_gamma, x[1] > - eps)
71+
bc_bottom = DirichletBC(space, Constant(310.), x[1] < -0.25 + eps)
72+
bcs = [bc_bottom, bc_top]
73+
74+
k = Constant(k)
75+
dt = Constant(0.01, name='dt')
76+
77+
A = u * v * ufl.dx + dt * k * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx
78+
b = uold * v * ufl.dx
79+
80+
scheme = solutionScheme([A == b, *bcs], solver='cg')
81+
82+
# Weak form of the flux
83+
flux_expr = -(u - uold) * v / dt * ufl.dx - k * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx
84+
flux_expr_operator = galerkin(flux_expr)
85+
flux_sol = space.interpolate(u0, name='flux_sol')
86+
87+
# Sample the flux on the top edge
88+
flux_sol_expr = expression2GF(flux_sol.space.grid, flux_sol, flux_sol.space.order)
89+
sampler_weak_flux = Sampler(flux_sol_expr)
90+
def flux_f_weak(): return sampler_weak_flux.lineSample([0., 0.], [1., 0.], nx + 1)[1] * nx
91+
92+
if not os.path.exists("output"):
93+
os.makedirs("output")
94+
vtk = mesh.sequencedVTK("output/heat", pointdata=[unew])
95+
96+
precice_dt = interface.initialize()
97+
dt.assign(min(float(dt), precice_dt))
98+
99+
t = float(dt)
100+
101+
while interface.is_coupling_ongoing():
102+
103+
if interface.is_action_required(precice.action_write_iteration_checkpoint()): # write checkpoint
104+
t_check = t
105+
ucheckpoint.assign(uold)
106+
interface.mark_action_fulfilled(precice.action_write_iteration_checkpoint())
107+
108+
ug = interface.read_block_scalar_data(temperature_id, vertex_ids)
109+
ug_interp.y = ug
110+
111+
scheme.model.dt = dt
112+
scheme.solve(target=unew)
113+
114+
# compute flux, solution goes into flux_sol
115+
flux_expr_operator(unew, flux_sol)
116+
flux_values = flux_f_weak() # sample the flux function
117+
118+
interface.write_block_scalar_data(flux_id, vertex_ids, flux_values)
119+
120+
precice_dt = interface.advance(dt)
121+
dt.assign(min(float(dt), precice_dt))
122+
123+
if interface.is_action_required(precice.action_read_iteration_checkpoint()): # roll back to checkpoint
124+
t = t_check
125+
uold.assign(ucheckpoint)
126+
interface.mark_action_fulfilled(precice.action_read_iteration_checkpoint())
127+
128+
else: # update solution
129+
130+
uold.assign(unew)
131+
t += float(dt)
132+
133+
if interface.is_time_window_complete():
134+
tol = 10e-5 # we need some tolerance, since otherwise output might be skipped.
135+
if abs((t + tol) % dt_out) < 2 * tol: # output if t is a multiple of dt_out
136+
print("output vtk for time = {}".format(float(t)))
137+
vtk()
138+
139+
interface.finalize()

tools/cleaning-tools.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,11 @@ clean_dune() {
122122
set -e -u
123123
cd "$1"
124124
echo "--- Cleaning up DUNE case in $(pwd)"
125+
rm -fv ./dgfparser.log
125126
rm -fv ./*.pvd
126127
rm -fv ./*.vtu
127128
rm -rfv ./preCICE-output/
129+
rm -rfv ./output/
128130
clean_precice_logs .
129131
)
130132
}

0 commit comments

Comments
 (0)