Skip to content

Commit 3b07b1d

Browse files
FujikawasJun Chen
and
Jun Chen
authored
Restructure partitioned-heat-conduction-direct (#497)
Co-authored-by: Jun Chen <[email protected]>
1 parent b395993 commit 3b07b1d

File tree

11 files changed

+314
-190
lines changed

11 files changed

+314
-190
lines changed

partitioned-heat-conduction-direct/README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ Currently only `nutils` is provided as a solver. The data mapping is computed by
2626
Open two terminals and run:
2727

2828
```bash
29-
cd nutils
30-
./run.sh -d
29+
cd neumann-nutils
30+
./run.sh
3131
```
3232

3333
and
3434

3535
```bash
36-
cd nutils
37-
./run.sh -n
36+
cd dirichlet-nutils
37+
./run.sh
3838
```
3939

4040
See the [partitioned heat conduction tutorial](tutorials-partitioned-heat-conduction.html).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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(0, 1, 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['right']
18+
read_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 = read_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, degree=degree * 2)
37+
38+
# set boundary conditions at non-coupling boundaries
39+
# top and bottom boundary are non-coupling for both sides
40+
sqr = domain.boundary['top,bottom,left'].integral(
41+
'(u - uexact)^2 d:x' @ ns, degree=degree * 2)
42+
43+
sqr += read_sample.integral('(u - readfunc)^2 d:x' @ ns)
44+
45+
# preCICE setup
46+
participant = precice.Participant(
47+
"Dirichlet", "../precice-config.xml", 0, 1)
48+
49+
mesh_name_read = "Dirichlet-Mesh"
50+
mesh_name_write = "Neumann-Mesh"
51+
52+
vertex_ids_read = participant.set_mesh_vertices(
53+
mesh_name_read, read_sample.eval(ns.x))
54+
participant.set_mesh_access_region(mesh_name_write, [.9, 1.1, -.1, 1.1])
55+
56+
participant.initialize()
57+
precice_dt = participant.get_max_time_step_size()
58+
solver_dt = timestep
59+
dt = min(precice_dt, solver_dt)
60+
61+
vertex_ids_write, coords = participant.get_mesh_vertex_ids_and_coordinates(
62+
mesh_name_write)
63+
write_sample = domain.locate(ns.x, coords, eps=1e-10, tol=1e-10)
64+
65+
precice_write = functools.partial(
66+
participant.write_data, mesh_name_write, "Heat-Flux", vertex_ids_write)
67+
precice_read = functools.partial(
68+
participant.read_data, mesh_name_read, "Temperature", vertex_ids_read)
69+
70+
# helper functions to project heat flux to coupling boundary
71+
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(
83+
'flux^2 d:x' @ ns, degree=degree * 2)
84+
right_cons = solver.optimize('fluxdofs', right_sqr, droptol=1e-10)
85+
# right_cons is NaN in dofs that are NOT supported on the right boundary
86+
flux_sqr = domain.boundary['right'].boundary['top,bottom'].integral(
87+
'(flux - uexact_,0)^2 d:x' @ ns, degree=degree * 2)
88+
flux_cons = solver.optimize('fluxdofs', flux_sqr, droptol=1e-10,
89+
constrain=np.choose(np.isnan(right_cons), [np.nan, 0.]))
90+
# flux_cons is NaN in dofs that are supported on ONLY the right boundary
91+
flux_res = read_sample.integral('basis_n flux d:x' @ ns) - res
92+
93+
t = 0.
94+
istep = 0
95+
96+
# initial condition
97+
sqr0 = domain.integral('(u - uexact)^2' @ ns, degree=degree * 2)
98+
lhs = solver.optimize('lhs', sqr0, arguments=dict(t=t))
99+
bezier = domain.sample('bezier', degree * 2)
100+
101+
while participant.is_coupling_ongoing():
102+
103+
# save checkpoint
104+
if participant.requires_writing_checkpoint():
105+
checkpoint = lhs, t, istep
106+
107+
# prepare next timestep
108+
precice_dt = participant.get_max_time_step_size()
109+
dt = min(precice_dt, solver_dt)
110+
lhs0 = lhs
111+
istep += 1
112+
t += dt
113+
114+
# read data from participant
115+
read_data = precice_read(dt)
116+
117+
# update (time-dependent) boundary condition
118+
cons = solver.optimize('lhs', sqr, droptol=1e-15,
119+
arguments=dict(t=t, readdata=read_data))
120+
121+
# solve nutils timestep
122+
lhs = solver.solve_linear('lhs', res, constrain=cons, arguments=dict(
123+
lhs0=lhs0, dt=dt, t=t, readdata=read_data))
124+
125+
# write data to participant
126+
fluxdofs = solver.solve_linear(
127+
'fluxdofs', flux_res, arguments=dict(
128+
lhs0=lhs0, lhs=lhs, dt=dt, t=t), constrain=flux_cons)
129+
write_data = write_sample.eval('flux' @ ns, fluxdofs=fluxdofs)
130+
131+
precice_write(write_data)
132+
133+
# do the coupling
134+
participant.advance(dt)
135+
136+
# read checkpoint if required
137+
if participant.requires_reading_checkpoint():
138+
lhs, t, istep = checkpoint
139+
else:
140+
# generate output
141+
x, u, uexact = bezier.eval(
142+
['x_i', 'u', 'uexact'] @ ns, lhs=lhs, t=t)
143+
with treelog.add(treelog.DataLog()):
144+
export.vtk("Dirichlet" + "-" + str(istep), bezier.tri,
145+
x, Temperature=u, reference=uexact)
146+
147+
participant.finalize()
148+
149+
150+
if __name__ == '__main__':
151+
cli.run(main)
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,123 @@
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+
read_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 = read_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, degree=degree * 2)
37+
38+
# set boundary conditions at non-coupling boundaries
39+
# top and bottom boundary are non-coupling for both sides
40+
sqr = domain.boundary['top,bottom,right'].integral(
41+
'(u - uexact)^2 d:x' @ ns, degree=degree * 2)
42+
43+
res += read_sample.integral('basis_n readfunc d:x' @ ns)
44+
45+
# preCICE setup
46+
participant = precice.Participant("Neumann", "../precice-config.xml", 0, 1)
47+
48+
mesh_name_read = "Neumann-Mesh"
49+
mesh_name_write = "Dirichlet-Mesh"
50+
51+
vertex_ids_read = participant.set_mesh_vertices(
52+
mesh_name_read, read_sample.eval(ns.x))
53+
participant.set_mesh_access_region(mesh_name_write, [.9, 1.1, -.1, 1.1])
54+
55+
participant.initialize()
56+
precice_dt = participant.get_max_time_step_size()
57+
solver_dt = timestep
58+
dt = min(precice_dt, solver_dt)
59+
60+
vertex_ids_write, coords = participant.get_mesh_vertex_ids_and_coordinates(
61+
mesh_name_write)
62+
write_sample = domain.locate(ns.x, coords, eps=1e-10, tol=1e-10)
63+
64+
precice_write = functools.partial(
65+
participant.write_data, mesh_name_write, "Temperature", vertex_ids_write)
66+
precice_read = functools.partial(
67+
participant.read_data, mesh_name_read, "Heat-Flux", vertex_ids_read)
68+
69+
t = 0.
70+
istep = 0
71+
72+
# initial condition
73+
sqr0 = domain.integral('(u - uexact)^2' @ ns, degree=degree * 2)
74+
lhs = solver.optimize('lhs', sqr0, arguments=dict(t=t))
75+
bezier = domain.sample('bezier', degree * 2)
76+
77+
while participant.is_coupling_ongoing():
78+
79+
# save checkpoint
80+
if participant.requires_writing_checkpoint():
81+
checkpoint = lhs, t, istep
82+
83+
# prepare next timestep
84+
precice_dt = participant.get_max_time_step_size()
85+
dt = min(precice_dt, solver_dt)
86+
lhs0 = lhs
87+
istep += 1
88+
t += dt
89+
90+
# read data from participant
91+
read_data = precice_read(dt)
92+
93+
# update (time-dependent) boundary condition
94+
cons = solver.optimize('lhs', sqr, droptol=1e-15,
95+
arguments=dict(t=t, readdata=read_data))
96+
97+
# solve nutils timestep
98+
lhs = solver.solve_linear('lhs', res, constrain=cons, arguments=dict(
99+
lhs0=lhs0, dt=dt, t=t, readdata=read_data))
100+
101+
# write data to participant
102+
write_data = write_sample.eval('u' @ ns, lhs=lhs)
103+
precice_write(write_data)
104+
105+
# do the coupling
106+
participant.advance(dt)
107+
108+
# read checkpoint if required
109+
if participant.requires_reading_checkpoint():
110+
lhs, t, istep = checkpoint
111+
else:
112+
# generate output
113+
x, u, uexact = bezier.eval(
114+
['x_i', 'u', 'uexact'] @ ns, lhs=lhs, t=t)
115+
with treelog.add(treelog.DataLog()):
116+
export.vtk("Neumann" + "-" + str(istep), bezier.tri,
117+
x, Temperature=u, reference=uexact)
118+
119+
participant.finalize()
120+
121+
122+
if __name__ == '__main__':
123+
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

0 commit comments

Comments
 (0)