-
Notifications
You must be signed in to change notification settings - Fork 0
/
coupled.jl
302 lines (241 loc) · 8.66 KB
/
coupled.jl
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
### A Pluto.jl notebook ###
# v0.18.1
using Markdown
using InteractiveUtils
# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
quote
local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
local el = $(esc(element))
global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
el
end
end
# ╔═╡ 011c70e0-83e8-4ec7-9dc7-19c9dd69142e
begin
import Pkg
Pkg.activate(".")
using EpitaxialDeposition
using DataFrames
using CairoMakie
using JFVM
using PlutoUI
using StatsBase
end
# ╔═╡ ceac564a-9ff6-11ec-0ba7-13f6890e2021
md"""
# Coupled Kinetics and Transport
"""
# ╔═╡ 632a0cc6-6c8f-4a93-b72c-d6802fa3fef4
md"""
!!! note \" Linguistic Side-Note \"
In German, compound words are often used to express novel ideas. Example: when one is simultaneously proud and embarrassed, the feeling is called "Schamstolz" (portmanteau of the words for "shame" and "pride").
This code fills me with Schamstolz.
"""
# ╔═╡ 4411f086-c12c-45c4-a5b0-bd6d9ac2bcd9
md"""
## Set up initial conditions and static variables
"""
# ╔═╡ 25f27d9b-4acb-4803-a2c1-845801de7ba7
md"""
!!! warning \"Caveat Quaesitor\"
The runtime is approximately 10-20 seconds per simulated second.
Yep. It's literally faster to do the experiment!
"""
# ╔═╡ 54e026fa-12c1-4906-b96e-88b11eabfb38
begin
y_SiCl₄ = 0.5
Psys = 760.
temperature = 1000.
u₀ = [
:SiCl₄ => (y_SiCl₄ * Psys) / EpitaxialDeposition.PARAMS[:R][:L_Torr_mol_K] / temperature,
:H₂ => Psys * (1 - y_SiCl₄) / EpitaxialDeposition.PARAMS[:R][:L_Torr_mol_K] / temperature,
:Si_dep => 0.0,
:Si_etch => 0.0,
:HCl => 0.0,
:SiCl₂ => 0.0,
:k => EpitaxialDeposition.PARAMS[:kKp][:k],
:T => temperature
]
timesteps = 5
Δt = 1
Lx = 30
Ly = 30
Nx = Ny = 150
x = [1:Nx...] * Lx / Nx
y = [1:Ny...] * Ly / Ny
wafer_diameter = 10
D = Dict(
:SiCl₄ => 10 * DAB(:SiCl₄, :H₂),
:HCl => 10 * DAB(:HCl, :H₂),
:H₂ => 10 * DAB(:H₂, :H₂),
:SiCl₂ => 10 * DAB(:SiCl₂, :H₂)
)
# initial concentration of each species
local udict = Dict(u₀)
c₀ = Dict([
:H₂ => udict[:H₂],
:SiCl₄ => udict[:SiCl₄],
:SiCl₂ => udict[:SiCl₂],
:HCl => udict[:HCl]
])
mesh = createMesh2D(Nx + 2, Ny + 2, Lx, Ly)
wafer_bounds = EpitaxialDeposition.wafer_boundary(Nx, Lx, wafer_diameter)
# book-keeping variables
colnames = Symbol.(String.([u[1] for u in u₀]) .* "(t)")
sol_to_u = Dict([name => Symbol(chop(String(name), tail=3)) for name in colnames])
u_to_sol = Dict([value => key for (key, value) in sol_to_u])
end;
# ╔═╡ c1d044da-26df-4eb4-96fa-a16349a2d396
md"""
## Run a coupled simulation with Catalyst/JFVM
"""
# ╔═╡ 2492cf1b-f352-46e1-8e2a-1e2877568b41
try
LocalResource("dfd.png")
catch
try
LocalResource("../dfd.png")
catch e
@warn e
end
end
# ╔═╡ f00a16d4-9fbd-4ef8-aa0f-93476443c372
function transport_step(m::MeshStructure, D::Float64, c₀::Matrix{Float64}, Nx::Int,
ϕ::Float64; N_steps=5, Lx=30., Ly=30.)
# Define boundary conditions
BC = createBC(m)
BC.left.a[:] .= 1.0
BC.right.a[:] .= 1.0
BC.left.b[:] .= 0.0
BC.right.b[:] .= 0.0
BC.left.c[:] .= 0.0
BC.right.c[:] .= 0.0
# top and bottom boundary conditions for each coefficient
BC.top.a[1,:] .= 1.0
BC.bottom.a[1,:] .= 1.0
BC.top.b[1,:] .= 0.0
BC.bottom.b[1,:] .= 0.0
BC.top.c[1,:] .= 0.0
BC.bottom.c[1,:] .= 0.0
BC.bottom.c[wafer_bounds, 1] .= -ϕ
# Give a value for the diffusion coefficient based on current system
D_cell = createCellVariable(m, D) # assign the diffusion coefficient as a variable to each cell of the mesh
D_face = geometricMean(D_cell) # choose an averaging scheme, for how the diffusion coefficient on a cell face is calculated
c = createCellVariable(m, c₀, BC) # assign the "initial" species concentration to cells [mol/L]
# Discretize the problem and build our solution
M_diff = diffusionTerm(D_face) # matrix of diffusion term coefficients
(M_bc, RHS_bc) = boundaryConditionTerm(BC) # matrix composed of coefficients and the right hand side for the BC
Δt = EpitaxialDeposition.PARAMS[:Δt][:JFVM]
for i = 1:N_steps
(M_t, RHS_t) = transientTerm(c, Δt, 1.0)
M = M_t - M_diff + M_bc # adding together all sparse matrices of coefficients
RHS = RHS_bc + RHS_t # add all RHS's to each other
c = solveLinearPDE(m, M, RHS)
end
return c
end
# ╔═╡ 059e8e10-d1d1-4499-b884-8ff8cec42bad
function coupled_simulation()
# arrays to store time points
kinetics = []
transport = []
# variables to store states
u = u₀ # surface concentration
c = Dict([key => ones(Nx + 2, Ny + 2) * value for (key, value) in c₀]) # cells
ϕ = Dict([key => 1e-32 for (key, value) in c₀]) # surface Δc (pseudo-zero)
# loop over time points
for t in 1:timesteps
# run the kinetic model
k_sol = solve(ODEProblem(deposition_rxn_network, u, ((t - 1) * Δt, t * Δt)), Tsit5(), saveat=EpitaxialDeposition.PARAMS[:Δt][:Catalyst], maxiters=1e8)
# run the transport models
t_sol = Dict([key => transport_step(mesh, value, c[key], Nx, ϕ[key], N_steps=20) for (key, value) in D])
# update the transport models' cell states for next iteration
c = Dict([species => cell.value[2:end-1, 2:end-1] for (species, cell) in t_sol])
u_sol = Dict([sol_to_u[col] => DataFrame(k_sol)[end, col] for col in colnames])
# change in concentrations due to chemical reactions
old_u = Dict(u)
ϕ = Dict([species => new_u - old_u[species] for (species, new_u) in u_sol])
for (species, ci) in c
c[species][wafer_bounds, 1] .+= ϕ[species] #- Δu[species]
end
# update the kinetic model's state for next iteration
u′ = [
:SiCl₄ => mean(c[:SiCl₄][wafer_bounds, 1]),
:H₂ => mean(c[:H₂][wafer_bounds, 1]),
:Si_dep => u_sol[:Si_dep],
:Si_etch => u_sol[:Si_etch],
:HCl => mean(c[:HCl][wafer_bounds, 1]),
:SiCl₂ => mean(c[:SiCl₂][wafer_bounds, 1]),
:k => u_sol[:k],
:T => u_sol[:T]
]
u = u′
# append the output data
push!(kinetics, k_sol)
push!(transport, t_sol)
end
# merge timestep outputs
kinetics = reduce(append!, DataFrame.(kinetics))
# calculate film deposition
δ = film_thickness.(kinetics[:, "Si_dep(t)"], kinetics[:, "Si_etch(t)"])
return kinetics, transport, δ
end
# ╔═╡ 0c83de9c-9f4e-4f3c-97e1-efe01fecc4a3
kinetics, transport, δ = coupled_simulation();
# ╔═╡ 48063896-50eb-4e95-96a8-c563e8baaf8c
md"""
## Results
"""
# ╔═╡ 5296cf87-ee7f-483b-ba52-996ea974aaec
begin
local fig = Figure()
local ax = Axis(
fig[1,1],
title = "Surface Gas-Film Species",
ylabel="Concentration [mmol/L]",
xlabel="Time [s]"
)
plotted_species = setdiff(
String.(Symbol.(species(deposition_rxn_network))),
["T(t)", "k(t)", "Si_dep(t)", "Si_etch(t)"]
)
for name in plotted_species
lines!(kinetics[:, :timestamp], 1000 .* kinetics[:, name], label=chop(name, tail=3)) #mol/L --> mmol/L
end
fig[:,2] = Legend(fig, ax)
Axis(
fig[2,1],
title="Si Film Growth on a $wafer_diameter cm Wafer, T = $temperature [K]",
xlabel="Time [s]",
ylabel="δ [μm]"
)
lines!(kinetics[:, :timestamp], δ)
fig
end
# ╔═╡ 7da4da48-be95-4df4-ba69-8927d02da60b
## THIS IS PICKING TIMESTEP, NOT ACTUAL TIME
md"""
Profile snapshot: $(@bind t_i PlutoUI.NumberField(0:length(transport)-1, default=0))
"""
# ╔═╡ 600ebffc-d978-4813-9d06-67208b11a61d
begin
local i = round(Int, floor(t_i)) + 1
plot_species_profiles(transport[i], x, y)
end
# ╔═╡ Cell order:
# ╟─ceac564a-9ff6-11ec-0ba7-13f6890e2021
# ╟─632a0cc6-6c8f-4a93-b72c-d6802fa3fef4
# ╠═011c70e0-83e8-4ec7-9dc7-19c9dd69142e
# ╟─4411f086-c12c-45c4-a5b0-bd6d9ac2bcd9
# ╟─25f27d9b-4acb-4803-a2c1-845801de7ba7
# ╠═54e026fa-12c1-4906-b96e-88b11eabfb38
# ╟─c1d044da-26df-4eb4-96fa-a16349a2d396
# ╟─2492cf1b-f352-46e1-8e2a-1e2877568b41
# ╠═f00a16d4-9fbd-4ef8-aa0f-93476443c372
# ╠═059e8e10-d1d1-4499-b884-8ff8cec42bad
# ╠═0c83de9c-9f4e-4f3c-97e1-efe01fecc4a3
# ╟─48063896-50eb-4e95-96a8-c563e8baaf8c
# ╟─5296cf87-ee7f-483b-ba52-996ea974aaec
# ╟─600ebffc-d978-4813-9d06-67208b11a61d
# ╠═7da4da48-be95-4df4-ba69-8927d02da60b