Skip to content

Commit 782b2b1

Browse files
gertjanvanzwietenuekermanIshaanDesaiMakisH
authored
partitioned-heat-conduction: direct mesh access (#299)
Co-authored-by: Gertjan van Zwieten <git@gjvz.nl> Co-authored-by: Benjamin Uekermann <benjamin.uekermann@ipvs.uni-stuttgart.de> Co-authored-by: Benjamin Uekermann <benjamin.uekermann@gmail.com> Co-authored-by: Ishaan Desai <ishaan.desai@ipvs.uni-stuttgart.de> Co-authored-by: Gerasimos Chourdakis <chourdak@in.tum.de>
1 parent 304356e commit 782b2b1

7 files changed

Lines changed: 297 additions & 2 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: Partitioned heat conduction (direct access setup)
3+
permalink: tutorials-partitioned-heat-conduction-direct.html
4+
keywords: Nutils, Heat conduction, Direct mesh access
5+
summary: This tutorial is a modified version of the "partitioned heat conduction" tutorial showcasing direct mesh access.
6+
---
7+
8+
{% note %}
9+
Get the [case files of this tutorial](https://github.com/precice/tutorials/tree/master/partitioned-heat-conduction-direct). Read how in the [tutorials introduction](https://www.precice.org/tutorials.html).
10+
{% endnote %}
11+
12+
## Setup
13+
14+
This case is a modified version of the [partitioned heat conduction tutorial](tutorials-partitioned-heat-conduction.html). Main modification is that we here use the [direct mesh access feature](couple-your-code-direct-access.html) to let the solvers compute the data mapping and not preCICE.
15+
16+
Further minor modifications:
17+
18+
- We use a parallel coupling scheme instead of a serial one to prevent running into the problem where we are trying to add a zero column to the quasi-Newton matrix. For serial coupling, this happens here because one data field converges much faster than the other.
19+
20+
## Available solvers
21+
22+
Currently only `nutils` is provided as a solver. The data mapping is computed by directly sampling the FEM function representation at the inquired locations.
23+
24+
## Running the simulation
25+
26+
Open two terminals and run:
27+
28+
```bash
29+
cd nutils
30+
./run.sh -d
31+
```
32+
33+
and
34+
35+
```bash
36+
cd nutils
37+
./run.sh -n
38+
```
39+
40+
See the [partitioned heat conduction tutorial](tutorials-partitioned-heat-conduction.html).
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+
# shellcheck disable=SC1091
5+
. ../tools/cleaning-tools.sh
6+
7+
clean_tutorial .
8+
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_nutils .
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#! /usr/bin/env python3
2+
3+
from nutils import cli, mesh, function, solver, export
4+
import functools
5+
import treelog
6+
import numpy as np
7+
import precice
8+
9+
10+
def main(side='Dirichlet', n=10, degree=1, timestep=.1, alpha=3., beta=1.3):
11+
12+
if side == 'Dirichlet':
13+
x_grid = np.linspace(0, 1, n)
14+
elif side == 'Neumann':
15+
x_grid = np.linspace(1, 2, n)
16+
else:
17+
raise Exception('invalid side {!r}'.format(side))
18+
y_grid = np.linspace(0, 1, n)
19+
20+
# define the Nutils mesh
21+
domain, geom = mesh.rectilinear([x_grid, y_grid])
22+
coupling_boundary = domain.boundary['right' if side == 'Dirichlet' else 'left']
23+
read_sample = coupling_boundary.sample('gauss', degree=degree * 2)
24+
25+
# Nutils namespace
26+
ns = function.Namespace()
27+
ns.x = geom
28+
ns.basis = domain.basis('std', degree=degree)
29+
ns.alpha = alpha # parameter of problem
30+
ns.beta = beta # parameter of problem
31+
ns.u = 'basis_n ?lhs_n' # solution
32+
ns.dudt = 'basis_n (?lhs_n - ?lhs0_n) / ?dt' # time derivative
33+
ns.flux = 'basis_n ?fluxdofs_n' # heat flux
34+
ns.f = 'beta - 2 - 2 alpha' # rhs
35+
ns.uexact = '1 + x_0 x_0 + alpha x_1 x_1 + beta ?t' # analytical solution
36+
ns.readbasis = read_sample.basis()
37+
ns.readfunc = 'readbasis_n ?readdata_n'
38+
39+
# define the weak form
40+
res = domain.integral('(basis_n dudt - basis_n f + basis_n,i u_,i) d:x' @ ns, degree=degree * 2)
41+
42+
# set boundary conditions at non-coupling boundaries
43+
# top and bottom boundary are non-coupling for both sides
44+
sqr = domain.boundary['top,bottom,left' if side == 'Dirichlet'
45+
else 'top,bottom,right'].integral('(u - uexact)^2 d:x' @ ns, degree=degree * 2)
46+
47+
if side == 'Dirichlet':
48+
sqr += read_sample.integral('(u - readfunc)^2 d:x' @ ns)
49+
else:
50+
res += read_sample.integral('basis_n readfunc d:x' @ ns)
51+
52+
# preCICE setup
53+
interface = precice.Interface(side, "../precice-config.xml", 0, 1)
54+
55+
mesh_id_read = interface.get_mesh_id("Dirichlet-Mesh" if side == "Dirichlet" else "Neumann-Mesh")
56+
mesh_id_write = interface.get_mesh_id("Neumann-Mesh" if side == "Dirichlet" else "Dirichlet-Mesh")
57+
58+
vertex_ids_read = interface.set_mesh_vertices(mesh_id_read, read_sample.eval(ns.x))
59+
interface.set_mesh_access_region(mesh_id_write, [.9, 1.1, -.1, 1.1])
60+
61+
precice_dt = interface.initialize()
62+
63+
vertex_ids_write, coords = interface.get_mesh_vertices_and_ids(mesh_id_write)
64+
write_sample = domain.locate(ns.x, coords, eps=1e-10, tol=1e-10)
65+
precice_write = functools.partial(interface.write_block_scalar_data, interface.get_data_id(
66+
"Heat-Flux" if side == "Dirichlet" else "Temperature", mesh_id_write), vertex_ids_write)
67+
precice_read = functools.partial(interface.read_block_scalar_data, interface.get_data_id(
68+
"Temperature" if side == "Dirichlet" else "Heat-Flux", mesh_id_read), vertex_ids_read)
69+
70+
# helper functions to project heat flux to coupling boundary
71+
if side == 'Dirichlet':
72+
# To communicate the flux to the Neumann side we should not simply
73+
# evaluate u_,i n_i as this is an unbounded term leading to suboptimal
74+
# convergence. Instead we project ∀ v: ∫_Γ v flux = ∫_Γ v u_,i n_i and
75+
# evaluate flux. While the right-hand-side contains the same unbounded
76+
# term, we can use the strong identity du/dt - u_,ii = f to rewrite it
77+
# to ∫_Ω [v (du/dt - f) + v_,i u_,i] - ∫_∂Ω\Γ v u_,k n_k, in which we
78+
# recognize the residual and an integral over the exterior boundary.
79+
# While the latter still contains the problematic unbounded term, we
80+
# can use the fact that the flux is a known value at the top and bottom
81+
# via the Dirichlet boundary condition, and impose it as constraints.
82+
right_sqr = domain.boundary['right'].integral('flux^2 d:x' @ ns, degree=degree * 2)
83+
right_cons = solver.optimize('fluxdofs', right_sqr, droptol=1e-10)
84+
# right_cons is NaN in dofs that are NOT supported on the right boundary
85+
flux_sqr = domain.boundary['right'].boundary['top,bottom'].integral(
86+
'(flux - uexact_,0)^2 d:x' @ ns, degree=degree * 2)
87+
flux_cons = solver.optimize('fluxdofs', flux_sqr, droptol=1e-10,
88+
constrain=np.choose(np.isnan(right_cons), [np.nan, 0.]))
89+
# flux_cons is NaN in dofs that are supported on ONLY the right boundary
90+
flux_res = read_sample.integral('basis_n flux d:x' @ ns) - res
91+
92+
# write initial data
93+
if interface.is_action_required(precice.action_write_initial_data()):
94+
precice_write(write_sample.eval(0.))
95+
interface.mark_action_fulfilled(precice.action_write_initial_data())
96+
97+
interface.initialize_data()
98+
99+
t = 0.
100+
istep = 0
101+
102+
# initial condition
103+
sqr0 = domain.integral('(u - uexact)^2' @ ns, degree=degree * 2)
104+
lhs = solver.optimize('lhs', sqr0, arguments=dict(t=t))
105+
bezier = domain.sample('bezier', degree * 2)
106+
107+
while interface.is_coupling_ongoing():
108+
109+
# save checkpoint
110+
if interface.is_action_required(precice.action_write_iteration_checkpoint()):
111+
checkpoint = lhs, t, istep
112+
interface.mark_action_fulfilled(precice.action_write_iteration_checkpoint())
113+
114+
# read data from interface
115+
read_data = precice_read()
116+
117+
# prepare next timestep
118+
lhs0 = lhs
119+
istep += 1
120+
dt = min(timestep, precice_dt)
121+
t += dt
122+
123+
# update (time-dependent) boundary condition
124+
cons = solver.optimize('lhs', sqr, droptol=1e-15, arguments=dict(t=t, readdata=read_data))
125+
126+
# solve nutils timestep
127+
lhs = solver.solve_linear('lhs', res, constrain=cons, arguments=dict(lhs0=lhs0, dt=dt, t=t, readdata=read_data))
128+
129+
# write data to interface
130+
if side == 'Dirichlet':
131+
fluxdofs = solver.solve_linear(
132+
'fluxdofs', flux_res, arguments=dict(
133+
lhs0=lhs0, lhs=lhs, dt=dt, t=t), constrain=flux_cons)
134+
write_data = write_sample.eval('flux' @ ns, fluxdofs=fluxdofs)
135+
else:
136+
write_data = write_sample.eval('u' @ ns, lhs=lhs)
137+
precice_write(write_data)
138+
139+
# do the coupling
140+
precice_dt = interface.advance(dt)
141+
142+
# read checkpoint if required
143+
if interface.is_action_required(precice.action_read_iteration_checkpoint()):
144+
lhs, t, istep = checkpoint
145+
interface.mark_action_fulfilled(precice.action_read_iteration_checkpoint())
146+
else:
147+
# generate output
148+
x, u, uexact = bezier.eval(['x_i', 'u', 'uexact'] @ ns, lhs=lhs, t=t)
149+
with treelog.add(treelog.DataLog()):
150+
export.vtk(side + "-" + str(istep), bezier.tri, x, Temperature=u, reference=uexact)
151+
152+
interface.finalize()
153+
154+
155+
if __name__ == '__main__':
156+
cli.run(main)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/sh
2+
set -e -u
3+
4+
usage() { echo "Usage: cmd [-d] [-n]" 1>&2; exit 1; }
5+
6+
# Check if no input argument was provided
7+
if [ -z "$*" ] ; then
8+
usage
9+
fi
10+
11+
while getopts ":dn" opt; do
12+
case ${opt} in
13+
d)
14+
rm -rf Dirichlet-*.vtk
15+
NUTILS_RICHOUTPUT=no python3 heat.py --side=Dirichlet
16+
;;
17+
n)
18+
rm -rf Neumann-*.vtk
19+
NUTILS_RICHOUTPUT=no python3 heat.py --side=Neumann
20+
;;
21+
*)
22+
usage
23+
;;
24+
esac
25+
done
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<precice-configuration>
3+
<log>
4+
<sink
5+
filter="%Severity% > debug and %Rank% = 0"
6+
format="---[precice] %ColorizedSeverity% %Message%"
7+
enabled="true" />
8+
</log>
9+
10+
<solver-interface dimensions="2" experimental="yes">
11+
<data:scalar name="Temperature" />
12+
<data:scalar name="Heat-Flux" />
13+
14+
<mesh name="Dirichlet-Mesh">
15+
<use-data name="Temperature" />
16+
<use-data name="Heat-Flux" />
17+
</mesh>
18+
19+
<mesh name="Neumann-Mesh">
20+
<use-data name="Temperature" />
21+
<use-data name="Heat-Flux" />
22+
</mesh>
23+
24+
<participant name="Dirichlet">
25+
<use-mesh name="Dirichlet-Mesh" provide="yes" />
26+
<use-mesh name="Neumann-Mesh" from="Neumann" direct-access="true" />
27+
<write-data name="Heat-Flux" mesh="Neumann-Mesh" />
28+
<read-data name="Temperature" mesh="Dirichlet-Mesh" />
29+
</participant>
30+
31+
<participant name="Neumann">
32+
<use-mesh name="Neumann-Mesh" provide="yes" />
33+
<use-mesh name="Dirichlet-Mesh" from="Dirichlet" direct-access="true" />
34+
<write-data name="Temperature" mesh="Dirichlet-Mesh" />
35+
<read-data name="Heat-Flux" mesh="Neumann-Mesh" />
36+
</participant>
37+
38+
<m2n:sockets from="Dirichlet" to="Neumann" exchange-directory=".." />
39+
40+
<coupling-scheme:parallel-implicit>
41+
<participants first="Dirichlet" second="Neumann" />
42+
<max-time value="1.0" />
43+
<time-window-size value="0.1" />
44+
<max-iterations value="100" />
45+
<exchange data="Heat-Flux" mesh="Neumann-Mesh" from="Dirichlet" to="Neumann" />
46+
<exchange data="Temperature" mesh="Dirichlet-Mesh" from="Neumann" to="Dirichlet" />
47+
<relative-convergence-measure data="Heat-Flux" mesh="Neumann-Mesh" limit="1e-5" />
48+
<relative-convergence-measure data="Temperature" mesh="Dirichlet-Mesh" limit="1e-5" />
49+
<acceleration:IQN-ILS>
50+
<data name="Temperature" mesh="Dirichlet-Mesh" />
51+
<data name="Heat-Flux" mesh="Neumann-Mesh" />
52+
<initial-relaxation value="0.1" />
53+
<max-used-iterations value="10" />
54+
<time-windows-reused value="5" />
55+
<preconditioner type="residual-sum" />
56+
<filter type="QR2" limit="1e-3" />
57+
</acceleration:IQN-ILS>
58+
</coupling-scheme:parallel-implicit>
59+
</solver-interface>
60+
</precice-configuration>

partitioned-heat-conduction/nutils/run.sh

100644100755
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ while getopts ":dn" opt; do
1212
case ${opt} in
1313
d)
1414
rm -rf Dirichlet-*.vtk
15-
NUTILS_RICHOUTPUT=no python3 heat.py side=Dirichlet
15+
NUTILS_RICHOUTPUT=no python3 heat.py --side=Dirichlet
1616
;;
1717
n)
1818
rm -rf Neumann-*.vtk
19-
NUTILS_RICHOUTPUT=no python3 heat.py side=Neumann
19+
NUTILS_RICHOUTPUT=no python3 heat.py --side=Neumann
2020
;;
2121
*)
2222
usage

0 commit comments

Comments
 (0)