Skip to content

Commit f5377d9

Browse files
authored
Fix inconsistenies in the Nutils participants of the partitioned-heat-conduction tutorial (#496)
* Move Nutils code into respective participant folders * Add logging to the run scripts of dirichlet- and neumann- Nutils
1 parent 3b07b1d commit f5377d9

File tree

9 files changed

+213
-84
lines changed

9 files changed

+213
-84
lines changed

partitioned-heat-conduction/nutils/heat.py partitioned-heat-conduction/dirichlet-nutils/heat.py

+41-55
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,14 @@
77
import precice
88

99

10-
def main(side='Dirichlet', n=10, degree=1, timestep=.1, alpha=3., beta=1.2):
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))
10+
def main(n=10, degree=1, timestep=.1, alpha=3., beta=1.2):
11+
12+
x_grid = np.linspace(0, 1, n)
1813
y_grid = np.linspace(0, 1, n)
1914

2015
# define the Nutils mesh
2116
domain, geom = mesh.rectilinear([x_grid, y_grid])
22-
coupling_boundary = domain.boundary['right' if side ==
23-
'Dirichlet' else 'left']
17+
coupling_boundary = domain.boundary['right']
2418
coupling_sample = coupling_boundary.sample('gauss', degree=degree * 2)
2519

2620
# Nutils namespace
@@ -44,56 +38,51 @@ def main(side='Dirichlet', n=10, degree=1, timestep=.1, alpha=3., beta=1.2):
4438

4539
# set boundary conditions at non-coupling boundaries
4640
# top and bottom boundary are non-coupling for both sides
47-
sqr = domain.boundary['top,bottom,left' if side == 'Dirichlet' else 'top,bottom,right'].integral(
48-
'(u - uexact)^2 d:x' @ ns, degree=degree * 2)
41+
sqr = domain.boundary['top,bottom,left'].integral('(u - uexact)^2 d:x' @ ns, degree=degree * 2)
4942

50-
if side == 'Dirichlet':
51-
sqr += coupling_sample.integral('(u - readfunc)^2 d:x' @ ns)
52-
else:
53-
res += coupling_sample.integral('basis_n readfunc d:x' @ ns)
43+
sqr += coupling_sample.integral('(u - readfunc)^2 d:x' @ ns)
5444

5545
# preCICE setup
56-
participant = precice.Participant(side, "../precice-config.xml", 0, 1)
57-
mesh_name = side + "-Mesh"
46+
participant = precice.Participant('Dirichlet', "../precice-config.xml", 0, 1)
47+
mesh_name = "Dirichlet-Mesh"
5848
vertex_ids = participant.set_mesh_vertices(
5949
mesh_name, coupling_sample.eval(ns.x))
6050
precice_write = functools.partial(
6151
participant.write_data,
6252
mesh_name,
63-
"Temperature" if side == "Neumann" else "Heat-Flux",
53+
"Heat-Flux",
6454
vertex_ids)
6555
precice_read = functools.partial(
6656
participant.read_data,
6757
mesh_name,
68-
"Heat-Flux" if side == "Neumann" else "Temperature",
58+
"Temperature",
6959
vertex_ids)
7060

7161
# helper functions to project heat flux to coupling boundary
72-
if side == 'Dirichlet':
73-
# To communicate the flux to the Neumann side we should not simply
74-
# evaluate u_,i n_i as this is an unbounded term leading to suboptimal
75-
# convergence. Instead we project ∀ v: ∫_Γ v flux = ∫_Γ v u_,i n_i and
76-
# evaluate flux. While the right-hand-side contains the same unbounded
77-
# term, we can use the strong identity du/dt - u_,ii = f to rewrite it
78-
# to ∫_Ω [v (du/dt - f) + v_,i u_,i] - ∫_∂Ω\Γ v u_,k n_k, in which we
79-
# recognize the residual and an integral over the exterior boundary.
80-
# While the latter still contains the problematic unbounded term, we
81-
# can use the fact that the flux is a known value at the top and bottom
82-
# via the Dirichlet boundary condition, and impose it as constraints.
83-
rightsqr = domain.boundary['right'].integral(
84-
'flux^2 d:x' @ ns, degree=degree * 2)
85-
rightcons = solver.optimize('fluxdofs', rightsqr, droptol=1e-10)
86-
# rightcons is NaN in dofs that are NOT supported on the right boundary
87-
fluxsqr = domain.boundary['right'].boundary['top,bottom'].integral(
88-
'(flux - uexact_,0)^2 d:x' @ ns, degree=degree * 2)
89-
fluxcons = solver.optimize('fluxdofs',
90-
fluxsqr,
91-
droptol=1e-10,
92-
constrain=np.choose(np.isnan(rightcons),
93-
[np.nan,
94-
0.]))
95-
# fluxcons is NaN in dofs that are supported on ONLY the right boundary
96-
fluxres = coupling_sample.integral('basis_n flux d:x' @ ns) - res
62+
# To communicate the flux to the Neumann side we should not simply
63+
# evaluate u_,i n_i as this is an unbounded term leading to suboptimal
64+
# convergence. Instead we project ∀ v: ∫_Γ v flux = ∫_Γ v u_,i n_i and
65+
# evaluate flux. While the right-hand-side contains the same unbounded
66+
# term, we can use the strong identity du/dt - u_,ii = f to rewrite it
67+
# to ∫_Ω [v (du/dt - f) + v_,i u_,i] - ∫_∂Ω\Γ v u_,k n_k, in which we
68+
# recognize the residual and an integral over the exterior boundary.
69+
# While the latter still contains the problematic unbounded term, we
70+
# can use the fact that the flux is a known value at the top and bottom
71+
# via the Dirichlet boundary condition, and impose it as constraints.
72+
rightsqr = domain.boundary['right'].integral(
73+
'flux^2 d:x' @ ns, degree=degree * 2)
74+
rightcons = solver.optimize('fluxdofs', rightsqr, droptol=1e-10)
75+
# rightcons is NaN in dofs that are NOT supported on the right boundary
76+
fluxsqr = domain.boundary['right'].boundary['top,bottom'].integral(
77+
'(flux - uexact_,0)^2 d:x' @ ns, degree=degree * 2)
78+
fluxcons = solver.optimize('fluxdofs',
79+
fluxsqr,
80+
droptol=1e-10,
81+
constrain=np.choose(np.isnan(rightcons),
82+
[np.nan,
83+
0.]))
84+
# fluxcons is NaN in dofs that are supported on ONLY the right boundary
85+
fluxres = coupling_sample.integral('basis_n flux d:x' @ ns) - res
9786

9887
# write initial data
9988
if participant.requires_initial_data():
@@ -118,8 +107,7 @@ def main(side='Dirichlet', n=10, degree=1, timestep=.1, alpha=3., beta=1.2):
118107
x, u, uexact = bezier.eval(['x_i', 'u', 'uexact'] @ ns, lhs=lhs, t=t)
119108
with treelog.add(treelog.DataLog()):
120109
export.vtk(
121-
side +
122-
"-" +
110+
"Dirichlet-" +
123111
str(istep),
124112
bezier.tri,
125113
x,
@@ -157,14 +145,12 @@ def main(side='Dirichlet', n=10, degree=1, timestep=.1, alpha=3., beta=1.2):
157145
lhs0=lhs0, dt=dt, t=t, readdata=readdata))
158146

159147
# write data to participant
160-
if side == 'Dirichlet':
161-
fluxdofs = solver.solve_linear(
162-
'fluxdofs', fluxres, arguments=dict(
163-
lhs0=lhs0, lhs=lhs, dt=dt, t=t), constrain=fluxcons)
164-
write_data = coupling_sample.eval(
165-
'flux' @ ns, fluxdofs=fluxdofs)
166-
else:
167-
write_data = coupling_sample.eval('u' @ ns, lhs=lhs)
148+
fluxdofs = solver.solve_linear(
149+
'fluxdofs', fluxres, arguments=dict(
150+
lhs0=lhs0, lhs=lhs, dt=dt, t=t), constrain=fluxcons)
151+
write_data = coupling_sample.eval(
152+
'flux' @ ns, fluxdofs=fluxdofs)
153+
168154
precice_write(write_data)
169155

170156
# do the coupling
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
set -e -u
3+
4+
. ../../tools/log.sh
5+
exec > >(tee --append "$LOGFILE") 2>&1
6+
7+
python3 -m venv .venv
8+
. .venv/bin/activate
9+
pip install -r requirements.txt
10+
11+
rm -rf Dirichlet-*.vtk
12+
NUTILS_RICHOUTPUT=no python3 heat.py
13+
14+
close_log
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 .
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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(n=10, degree=1, timestep=.1, alpha=3., beta=1.2):
11+
12+
x_grid = np.linspace(1, 2, n)
13+
y_grid = np.linspace(0, 1, n)
14+
15+
# define the Nutils mesh
16+
domain, geom = mesh.rectilinear([x_grid, y_grid])
17+
coupling_boundary = domain.boundary['left']
18+
coupling_sample = coupling_boundary.sample('gauss', degree=degree * 2)
19+
20+
# Nutils namespace
21+
ns = function.Namespace()
22+
ns.x = geom
23+
ns.basis = domain.basis('std', degree=degree)
24+
ns.alpha = alpha # parameter of problem
25+
ns.beta = beta # parameter of problem
26+
ns.u = 'basis_n ?lhs_n' # solution
27+
ns.dudt = 'basis_n (?lhs_n - ?lhs0_n) / ?dt' # time derivative
28+
ns.flux = 'basis_n ?fluxdofs_n' # heat flux
29+
ns.f = 'beta - 2 - 2 alpha' # rhs
30+
ns.uexact = '1 + x_0 x_0 + alpha x_1 x_1 + beta ?t' # analytical solution
31+
ns.readbasis = coupling_sample.basis()
32+
ns.readfunc = 'readbasis_n ?readdata_n'
33+
34+
# define the weak form
35+
res = domain.integral(
36+
'(basis_n dudt - basis_n f + basis_n,i u_,i) d:x' @ ns,
37+
degree=degree * 2)
38+
39+
# set boundary conditions at non-coupling boundaries
40+
# top and bottom boundary are non-coupling for both sides
41+
sqr = domain.boundary['top,bottom,right'].integral('(u - uexact)^2 d:x' @ ns, degree=degree * 2)
42+
43+
res += coupling_sample.integral('basis_n readfunc d:x' @ ns)
44+
45+
# preCICE setup
46+
participant = precice.Participant("Neumann", "../precice-config.xml", 0, 1)
47+
mesh_name = "Neumann-Mesh"
48+
vertex_ids = participant.set_mesh_vertices(
49+
mesh_name, coupling_sample.eval(ns.x))
50+
precice_write = functools.partial(
51+
participant.write_data,
52+
mesh_name,
53+
"Temperature",
54+
vertex_ids)
55+
precice_read = functools.partial(
56+
participant.read_data,
57+
mesh_name,
58+
"Heat-Flux",
59+
vertex_ids)
60+
61+
# write initial data
62+
if participant.requires_initial_data():
63+
precice_write(coupling_sample.eval(0.))
64+
65+
participant.initialize()
66+
precice_dt = participant.get_max_time_step_size()
67+
solver_dt = timestep
68+
dt = min(precice_dt, solver_dt)
69+
70+
t = 0.
71+
istep = 0
72+
73+
# initial condition
74+
sqr0 = domain.integral('(u - uexact)^2' @ ns, degree=degree * 2)
75+
lhs = solver.optimize('lhs', sqr0, arguments=dict(t=t))
76+
bezier = domain.sample('bezier', degree * 2)
77+
78+
while True:
79+
80+
# generate output
81+
x, u, uexact = bezier.eval(['x_i', 'u', 'uexact'] @ ns, lhs=lhs, t=t)
82+
with treelog.add(treelog.DataLog()):
83+
export.vtk(
84+
"Neumann-" +
85+
str(istep),
86+
bezier.tri,
87+
x,
88+
Temperature=u,
89+
reference=uexact)
90+
91+
if not participant.is_coupling_ongoing():
92+
break
93+
94+
# save checkpoint
95+
if participant.requires_writing_checkpoint():
96+
checkpoint = lhs, t, istep
97+
98+
# prepare next timestep
99+
precice_dt = participant.get_max_time_step_size()
100+
dt = min(solver_dt, precice_dt)
101+
lhs0 = lhs
102+
istep += 1
103+
# read data from participant
104+
readdata = precice_read(dt)
105+
t += dt
106+
107+
# update (time-dependent) boundary condition
108+
cons = solver.optimize(
109+
'lhs',
110+
sqr,
111+
droptol=1e-15,
112+
arguments=dict(
113+
t=t,
114+
readdata=readdata))
115+
116+
# solve nutils timestep
117+
lhs = solver.solve_linear(
118+
'lhs', res, constrain=cons, arguments=dict(
119+
lhs0=lhs0, dt=dt, t=t, readdata=readdata))
120+
121+
# write data to participant
122+
write_data = coupling_sample.eval('u' @ ns, lhs=lhs)
123+
precice_write(write_data)
124+
125+
# do the coupling
126+
participant.advance(dt)
127+
128+
# read checkpoint if required
129+
if participant.requires_reading_checkpoint():
130+
lhs, t, istep = checkpoint
131+
132+
participant.finalize()
133+
134+
135+
if __name__ == '__main__':
136+
cli.run(main)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
nutils==7
2+
pyprecice==3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
set -e -u
3+
4+
. ../../tools/log.sh
5+
exec > >(tee --append "$LOGFILE") 2>&1
6+
7+
python3 -m venv .venv
8+
. .venv/bin/activate
9+
pip install -r requirements.txt
10+
11+
rm -rf Neumann-*.vtk
12+
NUTILS_RICHOUTPUT=no python3 heat.py
13+
14+
close_log

partitioned-heat-conduction/nutils/run.sh

-29
This file was deleted.

0 commit comments

Comments
 (0)