-
Notifications
You must be signed in to change notification settings - Fork 0
/
Nav_stokes.py
366 lines (271 loc) · 12.7 KB
/
Nav_stokes.py
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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
import numpy as np
import matplotlib.pyplot as plt
import yt
from IPython.display import clear_output
import itertools
import time
import yt
from yt.units import meter
class Box:
#Initialise an object that keeps track of simulation
#specific variables
def __init__(self, variables):
self.Nx = variables[0]
self.Ny = variables[1]
self.dt = variables[2]
self.t_end = variables[3]
self.re = variables[4]
self.lx = variables[5]
self.ly = variables[6]
self.N_savesteps = variables[7]
self.U_top = variables[8]
self.U_bot = variables[9]
self.V_left = variables[10]
self.V_right = variables[11]
def initialise_grid(box):
#Initialises the computational grids using the
#box object
Nx = box.Nx
Ny = box.Ny
sf = np.zeros((Nx,Ny), dtype='float64')
vort = np.zeros((Nx,Ny), dtype='float64')
w = np.zeros((Nx,Ny), dtype='float64')
return w, vort, sf
def stream_func(box, sf, vort, w):
"""
For the successive over-relaxation procedure we can't use
Numpy indexing which would make this a factor ~10 faster.
This is because the vectorised Numpy operations do not update
the array in between calculations, which is required here
since it uses old and new values of the streamfunction.
This function is by far the most time-consuming. Specifying a
lenient max-error helps with computation time but comes at the
cost of less accurate results.
Loops are very inefficient in Python - nested loops even more so.
A better alternative would be C or C++, which can be a factor 100
faster.
"""
beta = 1.5
max_error = 0.1
iters = 50
dx = box.lx/(box.Nx-1)
for its in range(iters):
w[:,:]= sf[:,:]
for i,j in itertools.product(range(1,box.Nx-1), range(1,box.Ny-1)):
sf[i,j] = beta/4.*(sf[i+1,j] + sf[i-1,j] + sf[i,j+1] + sf[i,j-1] + dx*dx*vort[i,j]) + \
(1. - beta)*sf[i,j]
if np.sum(np.abs(w - sf)) < max_error:
break
return sf
def set_vort_bounds(box, sf, vort):
"""
Set the boundary conditions of the vorticity.
The corners are always set to zero, as that is how the
grid is initialised.
"""
dx = box.lx/(box.Nx-1)
Nx = box.Nx
Ny = box.Ny
vort[1:Nx-1, Ny-1] = -2.*sf[1:Nx-1, Ny-2]/dx/dx - box.U_top*2./dx #Top wall
vort[1:Nx-1, 0 ] = -2.*sf[1:Nx-1, 1 ]/dx/dx + box.U_bot*2./dx #Bottom wall
vort[0 , 1:Ny-1] = -2.*sf[1 , 1:Ny-1]/dx/dx - box.V_left*2./dx #Left wall
vort[Nx-1, 1:Ny-1] = -2.*sf[Nx-2, 1:Ny-1]/dx/dx + box.V_right*2./dx #Right wall
return vort
def vort_rhs(box, w, vort, sf):
U = 1.
dx = box.lx/(box.Nx-1)
visc = U/box.re
#A more comprehensible but very slow loop
"""for i,j in itertools.product(range(1,box.Nx-1), range(1,box.Ny-1)):
w[i,j] = -((sf[i,j+1] - sf[i,j-1])*(vort[i+1,j] - vort[i-1,j]) - \
(sf[i+1,j] - sf[i-1,j])*(vort[i,j+1] - vort[i,j-1]))/4./dx/dx + \
visc*(vort[i+1,j] + vort[i-1,j] + vort[i,j+1] + vort[i,j-1] - \
4.*vort[i,j])/dx/dx"""
#Much faster (factor 10 ish) with numpy indexing in C
w[1:-1,1:-1] = -((sf[1:-1,2:] - sf[1:-1,0:-2])*(vort[2:,1:-1] - vort[0:-2,1:-1]) - \
(sf[2:,1:-1] - sf[0:-2,1:-1])*(vort[1:-1,2:] - vort[1:-1,0:-2]))/4./dx/dx + \
visc*(vort[2:,1:-1] + vort[0:-2,1:-1] + vort[1:-1,2:] + vort[1:-1,0:-2] - \
4.*vort[1:-1,1:-1])/dx/dx
return w
def pressure(box, sf):
#Create a pressure grid
p = np.zeros((box.Nx, box.Ny), dtype = 'float64')
w = np.zeros((box.Nx, box.Ny), dtype = 'float64')
s = np.zeros((box.Nx, box.Ny), dtype = 'float64')
dx = box.lx/(box.Nx-1)
dy = box.ly/(box.Nx-1)
#Discretised Poisson
"""for i,j in itertools.product(range(1,box.Nx-1), range(1,box.Ny-1)):
p[i,j] = (p[i-1,j] + p[i+1, j] + p[i,j+1] + p[i, j-1])/4. - \
((sf[i+1,j] - 2*sf[i,j] + sf[i-1,j])*(sf[i,j+1] - 2*sf[i,j] + sf[i,j-1])/dx/dx/dy/dy - \
((sf[i+1,j] - sf[i-1,j])*(sf[i,j+1] - sf[i,j-1])/dx/dy/4.)**2)/2."""
beta = 1.2
max_error = 0.1
iters = 50
dx = box.lx/(box.Nx-1)
for its in range(iters):
w[:,:]= p[:,:]
for i,j in itertools.product(range(1,box.Nx-1), range(1,box.Ny-1)):
s[i,j] = 2*(sf[i+1,j] - 2*sf[i,j] + sf[i-1,j])*(sf[i,j+1] - 2*sf[i,j] + sf[i,j-1])/dx/dx - \
(sf[i+1,j+1] - sf[i+1,j-1] - sf[i-1,j+1] + sf[i-1,j-1])**2/dx/dx
p[i,j] = beta*(p[i-1,j] + p[i+1, j] + p[i,j+1] + p[i, j-1] - s[i,j])/4. + (1.-beta)*p[i,j]
if np.sum(np.abs(w - p)) < max_error:
break
"""p[1:-1, 1:-1] = p[0:-2, 1:-1]/4. + p[2:, 1:-1]/4. + p[1:-1, 2:]/4. + p[1:-1, 0:-2]/4. + \
((sf[2:,1:-1] - 2*sf[1:-1,1:-1] + sf[0:-2,1:-1])*(sf[1:-1,2:] - 2*sf[1:-1,1:-1] + sf[1:-1,0:-2])/dx/dx/dy/dy - \
((sf[2:,1:-1] - sf[0:-2,1:-1])*(sf[1:-1,2:] - sf[1:-1,0:-2])/dx/dy/4.)**2)/2.
"""
return p
def velocity(box, streamfunction):
dx = box.lx/(box.Nx-1)
dy = box.ly/(box.Nx-1)
u = np.zeros((box.Nx, box.Ny), dtype = 'float64')
v = np.zeros((box.Nx, box.Ny), dtype = 'float64')
#Simple NumPy indexing to speed up the calculation
u[1:-1,1:-1] = (streamfunction[1:-1,2:] - streamfunction[1:-1,1:-1])/dy
v[1:-1,1:-1] = (streamfunction[2:,1:-1] - streamfunction[1:-1,1:-1])/dx
#Filling the ghost zones of the velocity, not strictly necessary
#but looks better. In general, hydro codes do not return ghost
#zone values but for this assignment it makes sense to return them
#so that it can be checked that the boundaries are set correctly.
#I include a minus for left and right due to the definition of the
#stream function.
u[1:box.Nx-1,box.Ny-1] = box.U_top
u[1:box.Nx-1,0] = box.U_bot
v[box.Nx-1,1:box.Ny-1] = -box.V_right
v[0,1:box.Ny-1] = -box.V_left
#return [-np.gradient(streamfunction, axis=0), np.gradient(streamfunction, axis=1)]
return [u, -v]
def main(box):
N_savevars = 5 #Always constant
#Create a grid to save data
results = np.zeros([N_savevars, box.N_savesteps, box.Nx, box.Ny], dtype= 'float64')
#Keeps track of when to save the data
when_to_save = np.linspace(1, box.t_end/box.dt, box.N_savesteps, dtype='int') - 1
#Initialise the computational grids
w, vort , sf = initialise_grid(box)
#Time loop
t = 0.
count_global = 0
count_save = 0
while t <= box.t_end:
sf = stream_func(box, sf, vort, w)
vort = set_vort_bounds(box, sf, vort)
w = vort_rhs(box, w, vort, sf)
#Update the vorticity
vort[:,:] += box.dt*w[:,:]
t += box.dt
if count_global in when_to_save:
#Save results in the solution matrix.
#Note that this can become RAM expensive for very large boxes.
#But, for the purposes of this assignment being 2D with small grids
#this is completely fine.
#Should I wish to expand the code to support very large grids,
#it is better to save the data to disk at every savestep.
#Just to show that the implementation is simple and straightforward
#it would look like this:
# sf = np.reshape(sf, box.Nx*box.Ny)
# vort = np.reshape(vort, box.Nx*box.Ny)
# np.savetxt("data{:04d}.dat".format(count_save), np.c_[sf, vort])
#Which is the typical way that hydrodynamics codes save data to disk
#e.g. PLUTO, Athena or AREPO, though of course in other data formats (.dbl, .hdf5
#being the most common).
results[0, count_save, :] = sf
results[1, count_save, :] = vort
#Print approximate progress
clear_output(wait=True)
print("Calculation at {} %".format(count_save*100/box.N_savesteps))
count_save += 1
count_global += 1
clear_output(wait=True)
print("Calculation finished.")
print("Starting post-processing of the velocity and pressure...")
for i in range(box.N_savesteps):
sf = results[0, i, :]
ux, uy = velocity(box, sf)
p = pressure(box, sf)
results[2, i, :] = ux
results[3, i, :] = uy
results[4, i, :] = p
print("Done")
return results
def save_data(box, results, name):
#Save the data in single arrays
print("Saving data to disk...")
Nsavesteps = 100
sf = np.reshape(results[0], box.N_savesteps*box.Nx*box.Ny)
vort = np.reshape(results[1], box.N_savesteps*box.Nx*box.Ny)
ux = np.reshape(results[2], box.N_savesteps*box.Nx*box.Ny)
uy = np.reshape(results[3], box.N_savesteps*box.Nx*box.Ny)
p = np.reshape(results[4], box.N_savesteps*box.Nx*box.Ny)
np.savetxt("{}.dat".format(name), np.c_[sf, vort, ux, uy, p])
print("Done.")
def make_yt_plot(box, data, var, var_string, title, log, savename):
#This routine creates plots with python-yt
var = var[:,:, np.newaxis]
bbox = np.array([[0., 100.], [0., 100.], [0, 100.]])
ds = yt.load_uniform_grid(data, var.shape, bbox=bbox)
width = box.lx*meter
slc = yt.SlicePlot(ds, "z", var_string, width=width)
slc.set_log(var_string, log)
slc.annotate_title(title)
slc.save(savename)
def velocity_tracer(box, vels):
v = np.zeros(box.N_savesteps, dtype = 'float64')
#Loop over the data, saving the value of the velocity
#for the grid cell in the middle
for ind, grid2d in enumerate(vels):
v[ind] = grid2d[int(np.floor(box.Nx/2)), int(np.floor(box.Ny/2))]
return v
def problem_E():
nx = ny = 200
dt = 0.0001
tend = 20.
re = 1000
Lx = Ly = 1
N_savesteps = 100
U_top = 1.
U_bot = V_left = V_right = 0.
variables = [nx,ny,dt,tend,re,Lx,Ly,N_savesteps,U_top,U_bot,V_left,V_right]
box = Box(variables)
start = time.time()
results = main(box)
end = time.time()
print("Time taken: {:3.1f} minutes".format((end - start)/60))
#Save data
save_data(box, results, "problem_e")
#Unpack results
sf = results[0]
vort = results[1]
ux = results[2]
uy = results[3]
p = results[4]
#Free RAM
del results
print("Plotting some results with Python-yt...")
data = dict(Streamfunction = (sf[-1][:,:,np.newaxis], "m**2/s"), Vorticity = (vort[-1][:,:,np.newaxis], "s**-1"), \
u = (ux[-1][:,:,np.newaxis], "m/s"), v = (uy[-1][:,:,np.newaxis], "m/s"), \
Pressure = (p[-1][:,:,np.newaxis], "Pa"), Velocity = (np.sqrt(ux[-1]**2 + uy[-1]**2)[:,:,np.newaxis], \
"m/s"))
title_sf = "Plot of the stream function at t = {} s".format(tend)
title_vort = "Plot of the Vorticity at t = {} s".format(tend)
title_p = "Plot of the pressure at t = {} s".format(tend)
title_vel = "Plot of the velocity magnitude at t = {} s".format(tend)
title_velu = "Plot of the horizontal velocity at t = {} s".format(tend)
title_velv = "Plot of the vertical velocity at t = {} s".format(tend)
make_yt_plot(box, data, sf[-1], "Streamfunction", title_sf, False, "sf_e.pdf")
make_yt_plot(box, data, vort[-1], "Vorticity", title_vort, True, "vort_e.pdf")
make_yt_plot(box, data, p[-1], "Pressure", title_p, False, "pres_e.pdf")
make_yt_plot(box, data, np.sqrt(ux[-1]**2 + uy[-1]**2), "Velocity", title_vel, False, "vmag_e.pdf")
make_yt_plot(box, data, ux[-1], "u", title_velu, False, "velu_e.pdf")
make_yt_plot(box, data, uy[-1], "v", title_velv, False, "velv_e.pdf")
#Placing a velocity tracer in the middle of the grid
v_tracery = velocity_tracer(box, uy)
v_tracerx = velocity_tracer(box, ux)
plt.plot(np.linspace(0,tend, 100), v_tracery)
plt.savefig('traceryE.pdf')
plt.clf()
plt.plot(np.linspace(0,tend, 100), v_tracerx)
plt.savefig('tracerxE.pdf')
plt.clf()
problem_E()