-
Notifications
You must be signed in to change notification settings - Fork 70
/
Copy pathpatch1d.py
439 lines (297 loc) · 12.1 KB
/
patch1d.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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
"""
The patch module allows for a grid to be created and for data to be
defined on that grid.
Typical usage:
-- create the grid
grid = Grid1d(nx)
-- create the data that lives on that grid
data = CellCenterData1d(grid)
bcObj = bcObject(xlb="reflect", xrb="reflect"_
data.registerVar("density", bcObj)
...
data.create()
-- initialize some data
dens = data.get_var("density")
dens[:,:] = ...
-- fill the ghost cells
data.fil_lBC("density")
"""
from __future__ import print_function
import sys
import numpy
valid = ["outflow", "periodic",
"reflect", "reflect-even", "reflect-odd",
"dirichlet", "neumann"]
class BCObject(object):
"""
Boundary condition container -- hold the BCs on each boundary
for a single variable
"""
def __init__(self,
xlb="outflow", xrb="outflow",
odd_reflect_dir=""):
# note: "reflect" is ambiguous and will be converted into
# either reflect-even (the default) or reflect-odd
if xlb not in valid or xrb not in valid:
sys.exit("ERROR: invalid BC")
# -x boundary
self.xlb = xlb
if self.xlb == "reflect":
self.xlb = numpy.where(odd_reflect_dir == "x",
"reflect-odd", "reflect-even")
# +x boundary
self.xrb = xrb
if self.xrb == "reflect":
self.xrb = numpy.where(odd_reflect_dir == "x",
"reflect-odd", "reflect-even")
# periodic checks
if ((xlb == "periodic" and xrb != "periodic") or
(xrb == "periodic" and xlb != "periodic")):
sys.exit("ERROR: both xlb and xrb must be periodic")
def __str__(self):
""" print out some basic information about the BC object """
string = "BCs: -x: %s +x: %s " % \
(self.xlb, self.xrb)
return string
class Grid1d(object):
"""
the 1-d grid class. The grid object will contain the coordinate
information (at various centerings).
A basic (1-d) representation of the layout is:
| | | X | | | | X | | |
+--*--+- // -+--*--X--*--+--*--+- // -+--*--+--*--X--*--+- // -+--*--+
0 ng-1 ng ng+1 ... ng+nx-1 ng+nx 2ng+nx-1
ilo ihi
|<- ng ghostcells->|<---- nx interior zones ----->|<- ng ghostcells->|
The '*' marks the data locations.
"""
def __init__(self, nx, ng=1, xmin=0.0, xmax=1.0):
"""
The class constructor function.
The only data that we require is the number of points that
make up the mesh.
We optionally take the extrema of the domain, number of ghost
cells (assume 1)
"""
# size of grid
self.nx = nx
self.ng = ng
self.qx = 2*ng+nx
# domain extrema
self.xmin = xmin
self.xmax = xmax
# compute the indices of the block interior (excluding guardcells)
self.ilo = ng
self.ihi = ng+nx-1
# define the coordinate information at the left, center, and right
# zone coordinates
self.dx = (xmax - xmin)/nx
self.xl = (numpy.arange(nx+2*ng) - ng)*self.dx + xmin
self.xr = (numpy.arange(nx+2*ng) + 1.0 - ng)*self.dx + xmin
self.x = 0.5*(self.xl + self.xr)
def scratch_array(self):
return numpy.zeros((self.qx), dtype=numpy.float64)
def __str__(self):
""" print out some basic information about the grid object """
return "1-d grid: nx = {}, ng = {}".format(self.nx, self.ng)
class CellCenterData1d(object):
"""
the cell-centered data that lives on a grid.
a CellCenterData1d object is built in a multi-step process before it can
be used. We pass in a grid object to describe where the data
lives:
my_data = patch.CellCenterData1d(myGrid)
register any variables that we expect to live on this patch. Here
bcObject describes the boundary conditions for that variable.
my_data.registerVar('density', bcObject)
my_data.registerVar('x-momentum', bcObject)
...
finally, finish the initialization of the patch
my_data.create()
This last step actually allocates the storage for the state
variables. Once this is done, the patch is considered to be
locked. New variables cannot be added.
"""
def __init__(self, grid, dtype=numpy.float64):
self.grid = grid
self.dtype = dtype
self.data = None
self.vars = []
self.nvar = 0
self.BCs = {}
# time
self.t = -1
self.initialized = 0
def register_var(self, name, bc_object):
"""
register a variable with CellCenterData1d object. Here we pass in a
BCObject that describes the boundary conditions for that
variable.
"""
if self.initialized == 1:
sys.exit("ERROR: grid already initialized")
self.vars.append(name)
self.nvar += 1
self.BCs[name] = bc_object
def create(self):
"""
called after all the variables are registered and allocates
the storage for the state data
"""
if self.initialized == 1:
sys.exit("ERROR: grid already initialized")
self.data = numpy.zeros((self.nvar, self.grid.qx), dtype=self.dtype)
self.initialized = 1
def __str__(self):
""" print out some basic information about the ccData2d object """
if self.initialized == 0:
mystr = "CellCenterData1d object not yet initialized"
return mystr
mystr = "cc data: nx = {}, ng = {}\n".format(self.grid.nx, self.grid.ng) + \
" nvars = {}\n".format(self.nvar) + \
"variables: \n"
ilo = self.grid.ilo
ihi = self.grid.ihi
for n in range(self.nvar):
mystr += "%16s: min: %15.10f max: %15.10f\n" % \
(self.vars[n],
numpy.min(self.data[n, ilo:ihi+1]),
numpy.max(self.data[n, ilo:ihi+1]))
mystr += "%16s BCs: -x: %-12s +x: %-12s \n" %\
(" ", self.BCs[self.vars[n]].xlb,
self.BCs[self.vars[n]].xrb)
return mystr
def get_var(self, name):
"""
return a data array the variable described by name. Any changes
made to this are automatically reflected in the CellCenterData1d
object.
"""
n = self.vars.index(name)
return self.data[n, :]
def zero(self, name):
n = self.vars.index(name)
self.data[n, :] = 0.0
def fill_BC_all(self):
"""
fill boundary conditions on all variables
"""
for name in self.vars:
self.fill_BC(name)
def fill_BC(self, name):
"""
fill the boundary conditions. This operates on a single state
variable at a time, to allow for maximum flexibility
we do periodic, reflect-even, reflect-odd, and outflow
each variable name has a corresponding bc_object stored in the
ccData2d object -- we refer to this to figure out the action
to take at each boundary.
"""
# there is only a single grid, so every boundary is on
# a physical boundary (except if we are periodic)
# Note: we piggy-back on outflow and reflect-odd for
# Neumann and Dirichlet homogeneous BCs respectively, but
# this only works for a single ghost cell
n = self.vars.index(name)
# -x boundary
if self.BCs[name].xlb == "outflow" or self.BCs[name].xlb == "neumann":
for i in range(0, self.grid.ilo):
self.data[n, i] = self.data[n, self.grid.ilo]
elif self.BCs[name].xlb == "reflect-even":
for i in range(0, self.grid.ilo):
self.data[n, i] = self.data[n, 2*self.grid.ng-i-1]
elif self.BCs[name].xlb in ["reflect-odd", "dirichlet"]:
for i in range(0, self.grid.ilo):
self.data[n, i] = -self.data[n, 2*self.grid.ng-i-1]
elif self.BCs[name].xlb == "periodic":
for i in range(0, self.grid.ilo):
self.data[n, i] = self.data[n, self.grid.ihi-self.grid.ng+i+1]
# +x boundary
if self.BCs[name].xrb == "outflow" or self.BCs[name].xrb == "neumann":
for i in range(self.grid.ihi+1, self.grid.nx+2*self.grid.ng):
self.data[n, i] = self.data[n, self.grid.ihi]
elif self.BCs[name].xrb == "reflect-even":
for i in range(0, self.grid.ng):
i_bnd = self.grid.ihi+1+i
i_src = self.grid.ihi-i
self.data[n, i_bnd] = self.data[n, i_src]
elif self.BCs[name].xrb in ["reflect-odd", "dirichlet"]:
for i in range(0, self.grid.ng):
i_bnd = self.grid.ihi+1+i
i_src = self.grid.ihi-i
self.data[n, i_bnd] = -self.data[n, i_src]
elif self.BCs[name].xrb == "periodic":
for i in range(self.grid.ihi+1, 2*self.grid.ng + self.grid.nx):
self.data[n, i] = self.data[n, i-self.grid.ihi-1+self.grid.ng]
def restrict(self, varname):
"""
restrict the variable varname to a coarser grid (factor of 2
coarser) and return an array with the resulting data (and same
number of ghostcells)
"""
fG = self.grid
fData = self.get_var(varname)
# allocate an array for the coarsely gridded data
ng_c = fG.ng
nx_c = fG.nx//2
cData = numpy.zeros((2*ng_c+nx_c), dtype=self.dtype)
ilo_c = ng_c
ihi_c = ng_c+nx_c-1
# fill the coarse array with the restricted data -- just
# average the 2 fine cells into the corresponding coarse cell
# that encompasses them.
# This is done by shifting our view into the fData array and
# using a stride of 2 in the indexing.
cData[ilo_c:ihi_c+1] = \
0.5*(fData[fG.ilo :fG.ihi+1:2] + fData[fG.ilo+1:fG.ihi+1:2])
return cData
def prolong(self, varname):
"""
prolong the data in the current (coarse) grid to a finer
(factor of 2 finer) grid. Return an array with the resulting
data (and same number of ghostcells).
We will reconstruct the data in the zone from the
zone-averaged variables using the centered-difference slopes
(x)
f(x,y) = m x/dx + <f>
When averaged over the parent cell, this reproduces <f>.
Each zone's reconstrution will be averaged over 2 children.
| | | | |
| <f> | --> | | |
| | | 1 | 2 |
+-----------+ +-----+-----+
We will fill each of the finer resolution zones by filling all
the 1's together, using a stride 2 into the fine array. Then
the 2's, this allows us to operate in a vector
fashion. All operations will use the same slopes for their
respective parents.
"""
cG = self.grid
cData = self.get_var(varname)
# allocate an array for the coarsely gridded data
ng_f = cG.ng
nx_f = cG.nx*2
fData = numpy.zeros((2*ng_f+nx_f), dtype=self.dtype)
ilo_f = ng_f
ihi_f = ng_f+nx_f-1
# slopes for the coarse data
m_x = cG.scratch_array()
m_x[cG.ilo:cG.ihi+1] = \
0.5*(cData[cG.ilo+1:cG.ihi+2] - cData[cG.ilo-1:cG.ihi])
# fill the '1' children
fData[ilo_f:ihi_f+1:2] = \
cData[cG.ilo:cG.ihi+1] - 0.25*m_x[cG.ilo:cG.ihi+1]
# fill the '2' children
fData[ilo_f+1:ihi_f+1:2] = \
cData[cG.ilo:cG.ihi+1] + 0.25*m_x[cG.ilo:cG.ihi+1]
return fData
if __name__ == "__main__":
# illustrate basic mesh operations
myg = Grid1d(16, xmax=1.0)
mydata = CellCenterData1d(myg)
bc = BCObject()
mydata.register_var("a", bc)
mydata.create()
a = mydata.get_var("a")
a[:] = numpy.exp(-(myg.x - 0.5)**2/0.1**2)
print(mydata)