Skip to content

Commit eaae918

Browse files
Files_upload
1 parent 64f882f commit eaae918

File tree

2 files changed

+271
-0
lines changed

2 files changed

+271
-0
lines changed

data.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
###
2+
#
3+
# Input parameters
4+
#
5+
###
6+
7+
# Label for the simulation
8+
label = '3d_plate'
9+
10+
# Mesh dimensions (m)
11+
xmin = 0
12+
xmax = 5e-2
13+
ymin = 0
14+
ymax = xmax
15+
zmin = 0
16+
zmax = 1e-3
17+
nx = 40 # Number of nodes
18+
ny = 160
19+
nz = 40
20+
21+
# Timestepping
22+
Δt0 = 1e-1 # Initial time step length (s)
23+
Δt_min = 1e-2
24+
Δt_max = 10
25+
t_end = 120 # Simulation time (s)
26+
27+
# Porosity
28+
ϕ = 0.6
29+
30+
# Surface tension (N/m)
31+
γ = 30e-3
32+
33+
# Contact angle
34+
θdeg = 10
35+
36+
# Mean pore radius (m)
37+
r_μ = 20e-6
38+
39+
# Relative standard deviation of pore radius
40+
rstd = 1/5
41+
42+
# Pore radius cutoff
43+
r_cutoff = 2e-6
44+
45+
46+
# Permeability
47+
k_long = 1e-12
48+
k_trans = 1e-16
49+
50+
# Fluid viscosity (Pa s)
51+
μ = 1e-3
52+
53+
# External pressure
54+
u_ext = -1e-6
55+
56+
###
57+
###

infiltration.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
from matplotlib import pyplot
2+
import numpy as np
3+
from tqdm import trange, tqdm
4+
5+
from petsc4py import PETSc
6+
from mpi4py import MPI
7+
8+
import ufl
9+
from ufl import dot, grad, dx, exp, inner, sqrt, erf, cos, pi
10+
from dolfinx import fem, mesh, io, default_scalar_type, log
11+
from dolfinx.fem.petsc import NonlinearProblem
12+
from dolfinx.nls.petsc import NewtonSolver
13+
from basix.ufl import element
14+
15+
import sys
16+
from runpy import run_path
17+
18+
# Inject the variables from the parameter file to the global namespace
19+
try:
20+
param_file = sys.argv[1]
21+
params = run_path(param_file)
22+
except:
23+
error("Please provide the parameter file (.py) in the working directory!")
24+
25+
for k, v in params.items():
26+
if not k.startswith('_'):
27+
globals()[k] = v
28+
29+
30+
# Output
31+
solution_file = f'{label}_solution.npy' # Full solution
32+
output_file = f'{label}_output.bp' # VTX mesh for visualization
33+
34+
log.set_output_file(f'{label}.log')
35+
log.set_log_level(log.LogLevel.INFO)
36+
37+
# Helper functions
38+
39+
def get_variable_value(v):
40+
""" Get the nodal values for an expression v """
41+
q = fem.Function(V)
42+
q.interpolate(fem.Expression(v, V.element.interpolation_points()))
43+
return q.x.array
44+
45+
def ramp_up(val=1, t1=0.1):
46+
""" Linear ramp from zero beginning for t = 0, useful to assist convergence in the beginning """
47+
return val*ufl.min_value(1, t/t1)
48+
49+
# Generate 3D mesh
50+
domain = mesh.create_box(MPI.COMM_WORLD, [[xmin, ymin, zmin], [xmax, ymax, zmax]], [nx, ny, nz],
51+
mesh.CellType.hexahedron)
52+
53+
x = ufl.SpatialCoordinate(domain)
54+
55+
# Current time and time step size
56+
t = fem.Constant(domain, default_scalar_type(0))
57+
Δt = fem.Constant(domain, default_scalar_type(Δt0))
58+
59+
# Create function space
60+
P1 = element("Lagrange", domain.basix_cell(), 1)
61+
V = fem.functionspace(domain, P1)
62+
63+
# Unknown solution (t=n+1)
64+
u = fem.Function(V, name='u')
65+
66+
# Previous time step solution
67+
u_n = fem.Function(V, name='u_n')
68+
69+
# Saturation curve
70+
def S_curve(r):
71+
σ = rstd*r_μ
72+
return 1/2*(erf((r-r_μ)/(sqrt(2)*σ)) - erf((r_cutoff-r_μ)/(sqrt(2)*σ)))
73+
74+
# Capillary radius
75+
θ = θdeg*pi/180
76+
r_c = -2*γ*cos(θ)/u
77+
78+
# Saturation
79+
#S = ufl.conditional(u < 0, S_curve(r_c), 1.0)
80+
S = S_curve(r_c)
81+
82+
# Previous saturation
83+
S_n = ufl.replace(S, {u: u_n})
84+
85+
# Flux
86+
k = ufl.as_tensor([[k_long, 0, 0], [0, k_trans, 0], [0, 0, k_trans]])
87+
k_s = 1 + ramp_up(-1+S)
88+
J = -k_s*k/μ*grad(u)
89+
90+
###############
91+
# Weak form
92+
###############
93+
94+
δu = ufl.TestFunction(V)
95+
96+
F = δu * ϕ*(S - S_n) * dx - Δt*dot(grad(δu), J) * dx
97+
98+
# Direchlet boundary conditions
99+
100+
fdim = domain.topology.dim - 1
101+
domain.topology.create_connectivity(fdim, fdim+1)
102+
boundary_facets = mesh.exterior_facet_indices(domain.topology)
103+
boundary_dofs = fem.locate_dofs_topological(V, fdim, boundary_facets)
104+
bc = fem.dirichletbc(default_scalar_type(u_ext), boundary_dofs, V)
105+
bcs = [bc]
106+
107+
# Initial pressure
108+
u0 = -2*γ*cos(θ)/r_cutoff
109+
110+
if __name__ == '__main__':
111+
# Initial moisture content
112+
u.interpolate(lambda x: np.full(x[0].shape, u0))
113+
u.x.array[boundary_dofs] = u_ext
114+
115+
# Initialize the Newton solver
116+
problem = NonlinearProblem(F, u, bcs)
117+
solver = NewtonSolver(MPI.COMM_WORLD, problem)
118+
119+
# Solution is rather sensitive to tolerance,
120+
# numerical accuracy seems to be close to 1e-11
121+
solver.rtol = 0
122+
solver.atol = 1e-11
123+
solver.report = True
124+
solver.error_on_nonconvergence = False
125+
solver.max_it = 10
126+
solver.relaxation_parameter = 0.8
127+
128+
# Use mumps LU solver
129+
ksp = solver.krylov_solver
130+
ksp.setType("preonly")
131+
ksp.getPC().setType("lu")
132+
ksp.getPC().setFactorSolverType("mumps")
133+
134+
# Solutions at each time step
135+
sol = [u.x.array.copy()]
136+
137+
# Time values
138+
times = [t.value.item()]
139+
140+
if domain.comm.rank == 0:
141+
pbar = tqdm(total=t_end, unit_scale=True)
142+
143+
S_func = fem.Function(V, name='S')
144+
145+
with io.VTXWriter(domain.comm, output_file, [u, S_func]) as vtxfile:
146+
while True:
147+
u_n.x.array[:] = u.x.array
148+
149+
# Adaptive timestepping
150+
while True:
151+
n, converged = solver.solve(u)
152+
if converged:
153+
solver.relaxation_parameter = 0.8
154+
if n <= 3:
155+
Δt.value = min(Δt.value * 1.2, Δt_max)
156+
log.log(log.LogLevel.INFO, f'Timestep = {Δt.value}')
157+
elif n >= 7:
158+
Δt.value = Δt.value * 0.5
159+
log.log(log.LogLevel.INFO, f'Timestep = {Δt.value}')
160+
break
161+
else:
162+
solver.relaxation_parameter = 0.4
163+
Δt.value = Δt.value * 0.5
164+
if Δt.value < Δt_min:
165+
raise Error("Minimum timestep size reached!")
166+
u.x.array[:] = u_n.x.array
167+
log.log(log.LogLevel.INFO, f'Timestep = {Δt.value}')
168+
169+
t.value += Δt.value
170+
171+
# Record values
172+
sol.append(u.x.array.copy())
173+
times.append(t.value.item())
174+
175+
if domain.comm.rank == 0:
176+
pbar.update(Δt.value)
177+
178+
# Save output
179+
S_func.interpolate(fem.Expression(S, V.element.interpolation_points()))
180+
vtxfile.write(t.value.item())
181+
182+
# Termination criterion
183+
if t.value > t_end:
184+
break
185+
186+
S_threshold = 0.99
187+
S_vals = get_variable_value(S)
188+
finished = np.all(S_vals > S_threshold)
189+
statuses = domain.comm.gather(finished, root=0)
190+
can_stop = None
191+
if domain.comm.rank == 0:
192+
can_stop = all(statuses)
193+
if domain.comm.bcast(can_stop, root=0):
194+
break
195+
196+
# Numpy array output for single parallel runs
197+
if domain.comm.size == 1:
198+
sol = np.array(sol)
199+
np.save(solution_file, sol)
200+
201+
202+
# The residual and Jacobian for diagnostics
203+
#residual = fem.form(F)
204+
#RD = fem.petsc.create_vector(residual)
205+
#fem.petsc.assemble_vector(RD, residual)
206+
#jacobian = fem.form(ufl.derivative(F, u))
207+
208+
#fem.petsc.apply_lifting(RD, [jacobian], [bcs])
209+
210+
#A = fem.petsc.create_matrix(jacobian)
211+
#fem.petsc.assemble_matrix(A, jacobian)
212+
#A.assemble()
213+
#print(domain.comm.rank, A.getSize())
214+
#m = A.convert('dense').getDenseArray()

0 commit comments

Comments
 (0)