-
-
Notifications
You must be signed in to change notification settings - Fork 162
Expand file tree
/
Copy pathsolid.py
More file actions
198 lines (146 loc) · 5.74 KB
/
solid.py
File metadata and controls
198 lines (146 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import numpy as np
from mpi4py import MPI
import basix.ufl
from petsc4py import PETSc
import ufl
from dolfinx import fem, io, mesh as msh, default_scalar_type
from dolfinx.fem.petsc import assemble_matrix, assemble_vector, apply_lifting, create_vector, set_bc, LinearProblem
from dolfinx.mesh import create_rectangle
import basix
from fenicsxprecice import Adapter, CouplingMesh
# geometry
nx = 100
ny = 25
nz = 1
y_top = 0
y_bottom = y_top - .25
x_left = 0
x_right = x_left + 1
fenics_dt = 0.01 # time step size
def top_boundary(x):
tol = 1E-14
return np.isclose(x[1], y_top, tol)
def bottom_boundary(x):
tol = 1E-14
return np.isclose(x[1], y_bottom, tol)
class initial_value():
def __init__(self, constant):
self.constant = constant
def __call__(self, x):
return np.full(x[0].shape, self.constant)
class GradientSolver:
"""
compute flux following http://hplgit.github.io/INF5620/doc/pub/fenics_tutorial1.1/tu2.html#tut-poisson-gradu
The solver has been changed since the original version from the link above introduces larger errors
:param V_g: Vector function space
:param u: solution where gradient is to be determined
"""
def __init__(self, domain, V_g):
self.domain = domain,
self.V_g = V_g
w = ufl.TrialFunction(V_g)
self.v = ufl.TestFunction(V_g)
a = fem.form(ufl.inner(w, self.v) * ufl.dx)
self.A = assemble_matrix(a)
self.A.assemble()
self.solver = PETSc.KSP().create(domain.comm)
self.solver.setOperators(self.A)
self.solver.setType(PETSc.KSP.Type.PREONLY)
self.solver.getPC().setType(PETSc.PC.Type.LU)
self.returnValue = fem.Function(V_g)
def compute(self, u, k):
L = fem.form(ufl.inner(-k * ufl.grad(u), self.v) * ufl.dx)
b = create_vector(fem.extract_function_spaces(L))
assemble_vector(b, L)
b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
self.solver.solve(b, self.returnValue.x.petsc_vec)
return self.returnValue
p0 = (x_left, y_bottom)
p1 = (x_right, y_top)
mesh = create_rectangle(MPI.COMM_WORLD, [np.asarray(p0), np.asarray(p1)], [nx, ny], msh.CellType.triangle)
V = fem.functionspace(mesh, ('P', 2))
# for the vector function space
element = basix.ufl.element("Lagrange", mesh.topology.cell_name(), 1, shape=(mesh.geometry.dim,))
V_g = fem.functionspace(mesh, element)
W, map_to_W = V_g.sub(1).collapse()
gradient_solver = GradientSolver(mesh, V_g)
alpha = 1 # m^2/s, https://en.wikipedia.org/wiki/Thermal_diffusivity
k = 100 # kg * m / s^3 / K, https://en.wikipedia.org/wiki/Thermal_conductivity
# We will only exchange flux in y direction on coupling interface. No initialization necessary.
flux_y = fem.Function(W)
# Define initial value
u_n = fem.Function(V)
u_n.interpolate(initial_value(310))
tdim = mesh.topology.dim
fdim = tdim - 1
mesh.topology.create_connectivity(fdim, tdim)
dofs_coupling = fem.locate_dofs_geometrical(V, top_boundary)
dofs_bottom = fem.locate_dofs_geometrical(V, bottom_boundary)
# Adapter definition and initialization
precice = Adapter(adapter_config_filename="precice-adapter-config.json", mpi_comm=MPI.COMM_WORLD)
# top_boundary is coupling boundary
coupling_mesh = CouplingMesh("Solid-Mesh", top_boundary, {"Temperature": V}, {"Heat-Flux": flux_y})
precice.initialize([coupling_mesh])
# boundary function for the coupling interface
coupling_function = fem.Function(V)
# Assigning appropriate dt
precice_dt = precice.get_max_time_step_size()
dt = np.min([fenics_dt, precice_dt])
# Define variational problem
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
F = u * v / dt * ufl.dx + alpha * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx - u_n * v / dt * ufl.dx
# apply constant Dirichlet boundary condition at bottom edge
# apply Dirichlet boundary condition on coupling interface
bcs = [fem.dirichletbc(coupling_function, dofs_coupling), fem.dirichletbc(default_scalar_type(310), dofs_bottom, V)]
a = fem.form(ufl.lhs(F))
L = fem.form(ufl.rhs(F))
A = assemble_matrix(a, bcs=bcs)
A.assemble()
b = create_vector(fem.extract_function_spaces(L))
uh = fem.Function(V)
solver = PETSc.KSP().create(mesh.comm)
solver.setOperators(A)
solver.setType(PETSc.KSP.Type.PREONLY)
solver.getPC().setType(PETSc.PC.Type.LU)
# Time-stepping
t = 0
vtxwriter = io.VTXWriter(MPI.COMM_WORLD, f"output_solid.bp", [u_n])
vtxwriter.write(t)
n = 0
flux = fem.Function(V_g)
while precice.is_coupling_ongoing():
if precice.requires_writing_checkpoint(): # write checkpoint
precice.store_checkpoint(u_n, t, 0)
precice_dt = precice.get_max_time_step_size()
dt = np.min([fenics_dt, precice_dt])
precice.read_data(coupling_mesh.get_name(), "Temperature", dt, coupling_function)
# Update the right hand side reusing the initial vector
with b.localForm() as loc_b:
loc_b.set(0)
assemble_vector(b, L)
apply_lifting(b, [a], [bcs])
set_bc(b, bcs)
# Solve linear problem
solver.solve(b, uh.x.petsc_vec)
# Dirichlet problem obtains flux from solution and sends flux on boundary to Neumann problem
flux = gradient_solver.compute(u_n, k)
flux_y.interpolate(flux.sub(1))
precice.write_data(coupling_mesh.get_name(), "Heat-Flux", flux_y)
precice.advance(dt)
if precice.requires_reading_checkpoint():
u_cp, t_cp, _ = precice.retrieve_checkpoint()
u_n.x.array[:] = u_cp.x.array
t = t_cp
else: # update solution
# Update solution at previous time step (u_n)
u_n.x.array[:] = uh.x.array
t += float(dt)
n += 1
if n % 20 == 0:
vtxwriter.write(t)
if precice.is_time_window_complete():
# update boundary condition not necessary because it is constant
pass
precice.finalize()
vtxwriter.close()