Skip to content

Commit 39f0c38

Browse files
committed
partitioned-heat-conduction subcycling test
1 parent b6e852b commit 39f0c38

File tree

54 files changed

+50749
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+50749
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env sh
2+
set -e -u
3+
4+
. ../../tools/cleaning-tools.sh
5+
6+
clean_nutils .
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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=.005, 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+
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,left'].integral('(u - uexact)^2 d:x' @ ns, degree=degree * 2)
42+
43+
sqr += coupling_sample.integral('(u - readfunc)^2 d:x' @ ns)
44+
45+
# preCICE setup
46+
participant = precice.Participant('Dirichlet', "../precice-config.xml", 0, 1)
47+
mesh_name = "Dirichlet-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+
"Heat-Flux",
54+
vertex_ids)
55+
precice_read = functools.partial(
56+
participant.read_data,
57+
mesh_name,
58+
"Temperature",
59+
vertex_ids)
60+
61+
# helper functions to project heat flux to coupling boundary
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
86+
87+
# write initial data
88+
if participant.requires_initial_data():
89+
precice_write(coupling_sample.eval(0.))
90+
91+
participant.initialize()
92+
precice_dt = participant.get_max_time_step_size()
93+
solver_dt = timestep
94+
dt = min(precice_dt, solver_dt)
95+
96+
t = 0.
97+
istep = 0
98+
99+
# initial condition
100+
sqr0 = domain.integral('(u - uexact)^2' @ ns, degree=degree * 2)
101+
lhs = solver.optimize('lhs', sqr0, arguments=dict(t=t))
102+
bezier = domain.sample('bezier', degree * 2)
103+
104+
while True:
105+
106+
# generate output
107+
x, u, uexact = bezier.eval(['x_i', 'u', 'uexact'] @ ns, lhs=lhs, t=t)
108+
with treelog.add(treelog.DataLog()):
109+
export.vtk(
110+
"Dirichlet-" +
111+
str(istep),
112+
bezier.tri,
113+
x,
114+
Temperature=u,
115+
reference=uexact)
116+
117+
if not participant.is_coupling_ongoing():
118+
break
119+
120+
# save checkpoint
121+
if participant.requires_writing_checkpoint():
122+
checkpoint = lhs, t, istep
123+
124+
# prepare next timestep
125+
precice_dt = participant.get_max_time_step_size()
126+
dt = min(solver_dt, precice_dt)
127+
lhs0 = lhs
128+
istep += 1
129+
# read data from participant
130+
readdata = precice_read(dt)
131+
t += dt
132+
133+
# update (time-dependent) boundary condition
134+
cons = solver.optimize(
135+
'lhs',
136+
sqr,
137+
droptol=1e-15,
138+
arguments=dict(
139+
t=t,
140+
readdata=readdata))
141+
142+
# solve nutils timestep
143+
lhs = solver.solve_linear(
144+
'lhs', res, constrain=cons, arguments=dict(
145+
lhs0=lhs0, dt=dt, t=t, readdata=readdata))
146+
147+
# write data to participant
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+
154+
precice_write(write_data)
155+
156+
# do the coupling
157+
participant.advance(dt)
158+
159+
# read checkpoint if required
160+
if participant.requires_reading_checkpoint():
161+
lhs, t, istep = checkpoint
162+
163+
participant.finalize()
164+
165+
166+
if __name__ == '__main__':
167+
cli.run(main)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
setuptools # required by nutils
2+
nutils==7
3+
numpy >1, <2
4+
pyprecice~=3.0
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env 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 && pip freeze > pip-installed-packages.log
10+
11+
rm -rf Dirichlet-*.vtk
12+
NUTILS_RICHOUTPUT=no python3 heat.py
13+
14+
close_log
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
FoamFile
2+
{
3+
version 2.0;
4+
format ascii;
5+
class volScalarField;
6+
object T;
7+
}
8+
9+
dimensions [0 0 0 1 0 0 0];
10+
11+
12+
internalField #codeStream
13+
{
14+
codeInclude
15+
#{
16+
#include "fvCFD.H"
17+
#};
18+
19+
codeOptions
20+
#{
21+
-I$(LIB_SRC)/finiteVolume/lnInclude \
22+
-I$(LIB_SRC)/meshTools/lnInclude
23+
#};
24+
25+
codeLibs
26+
#{
27+
-lmeshTools \
28+
-lfiniteVolume
29+
#};
30+
31+
code
32+
33+
#{
34+
const IOdictionary& d = static_cast<const IOdictionary&>(dict);
35+
const fvMesh& mesh = refCast<const fvMesh>(d.db());
36+
const vectorField& CC = mesh.C(); // cell center
37+
38+
// assign values
39+
scalarField T(mesh.nCells());
40+
forAll(CC, cellI)
41+
{
42+
scalar x = CC[cellI].x();
43+
scalar y = CC[cellI].y();
44+
45+
T[cellI] = 1 + pow(x, 2) + (3 * pow(y, 2)); // t is zero for initial conditions
46+
}
47+
T.writeEntry("", os);
48+
#};
49+
};
50+
51+
boundaryField
52+
{
53+
interface
54+
{
55+
type fixedGradient;
56+
gradient uniform -2;
57+
}
58+
59+
DirichletBoundary
60+
{
61+
type codedFixedValue;
62+
value uniform 1;
63+
name DirichletBoundary;
64+
code
65+
#{
66+
const vectorField& Cf = patch().Cf();
67+
const scalar t = this->db().time().value();
68+
69+
scalarField& field = *this;
70+
forAll(Cf, faceI)
71+
{
72+
const scalar x = Cf[faceI].x();
73+
const scalar y = Cf[faceI].y();
74+
75+
field[faceI] = 1 + pow(x, 2) + (3 * pow(y, 2)) + 1.2 * t;
76+
}
77+
#};
78+
}
79+
80+
defaultFaces
81+
{
82+
type empty;
83+
}
84+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env sh
2+
set -e -u
3+
4+
. ../../tools/cleaning-tools.sh
5+
6+
clean_openfoam .
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FoamFile
2+
{
3+
version 2.0;
4+
format ascii;
5+
class dictionary;
6+
object transportProperties;
7+
}
8+
9+
DT DT [ 0 2 -1 0 0 0 0 ] 1;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
set -e -u
3+
4+
. ../../tools/log.sh
5+
exec > >(tee --append "$LOGFILE") 2>&1
6+
7+
blockMesh
8+
9+
../../tools/run-openfoam.sh "$@"
10+
. ../../tools/openfoam-remove-empty-dirs.sh && openfoam_remove_empty_dirs
11+
12+
close_log
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
FoamFile
2+
{
3+
version 2.0;
4+
format ascii;
5+
class dictionary;
6+
object blockMeshDict;
7+
}
8+
9+
convertToMeters 1;
10+
11+
vertices
12+
(
13+
14+
(1 0 0)
15+
(2 0 0)
16+
(2 1 0)
17+
(1 1 0)
18+
19+
(1 0 .1)
20+
(2 0 .1)
21+
(2 1 .1)
22+
(1 1 .1)
23+
24+
);
25+
26+
blocks
27+
(
28+
hex (0 1 2 3 4 5 6 7) (100 100 1) simpleGrading (1 1 1)
29+
);
30+
31+
boundary
32+
(
33+
34+
interface
35+
{
36+
type patch;
37+
faces
38+
(
39+
(4 7 3 0)
40+
);
41+
}
42+
43+
DirichletBoundary
44+
{
45+
type patch;
46+
faces
47+
(
48+
(1 2 6 5)
49+
(4 0 1 5)
50+
(7 6 2 3)
51+
);
52+
}
53+
);

0 commit comments

Comments
 (0)