PyMHD 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2000 @@
1
+ # Adapted from AthenaK (https://github.com/IAS-Astrophysics/athenak)
2
+ # Copyright (c) 2020, Institute for Advanced Study / High-Performance Computing / jmstone / Athena-Parthenon
3
+ # Licensed under BSD-3-Clause License
4
+
5
+ """
6
+ Functions to:
7
+ (1) convert bin --> Python dictionary
8
+ (2) convert Python dictionary --> athdf(xdmf) files
9
+
10
+ This module contains a collection of helper functions for readng and
11
+ writing athena file data formats. More information is provided in the
12
+ function docstrings.
13
+
14
+ ----
15
+
16
+ In order to translate a binary file into athdf and corresponding xdmf
17
+ files, you could do the following:
18
+
19
+ import bin_convert
20
+ import os
21
+
22
+ binary_fname = "path/to/file.bin"
23
+ athdf_fname = binary_fname.replace(".bin", ".athdf")
24
+ xdmf_fname = athdf_fname + ".xdmf"
25
+ filedata = bin_convert.read_binary(binary_fname)
26
+ bin_convert.write_athdf(athdf_fname, filedata)
27
+ bin_convert.write_xdmf_for(xdmf_fname, os.path.basename(athdf_fname), filedata)
28
+
29
+ Notice that write_xdmf_for(...) function expects the relative path to
30
+ the athdf file from the xdmf, so please be aware of this requirement.
31
+
32
+ ----
33
+
34
+ The read_*(...) functions return a filedata dictionary-like object with
35
+
36
+ filedata['header'] = array of strings
37
+ ordered array of header, including all the header information
38
+ filedata['time'] = float
39
+ time from input file
40
+ filedata['cycle'] = int
41
+ cycle from input file
42
+ filedata['var_names'] = array of strings
43
+ ordered array of variable names, like ['dens', 'eint', ...]
44
+ filedata['n_mbs'] = int
45
+ total number of meshblocks in the file
46
+ filedata['nx1_mb'] = int
47
+ number of cells in x1 direction in MeshBlock
48
+ filedata['nx2_mb'] = int
49
+ number of cells in x2 direction in MeshBlock
50
+ filedata['nx3_mb'] = int
51
+ number of cells in x3 direction in MeshBlock
52
+ filedata['nx1_out_mb'] = int
53
+ number of output cells in x1 direction in MeshBlock (useful for slicing)
54
+ filedata['nx2_out_mb'] = int
55
+ number of output cells in x2 direction in MeshBlock (useful for slicing)
56
+ filedata['nx3_out_mb'] = int
57
+ number of output cells in x3 direction in MeshBlock (useful for slicing)
58
+ filedata['Nx1'] = int
59
+ total number of cell in x1 direction in root grid
60
+ filedata['Nx2'] = int
61
+ total number of cell in x2 direction in root grid
62
+ filedata['Nx3'] = int
63
+ total number of cell in x3 direction in root grid
64
+ filedata['x1min'] = float
65
+ coordinate minimum of root grid in x1 direction
66
+ filedata['x1max'] = float
67
+ coordinate maximum of root grid in x1 direction
68
+ filedata['x2min'] = float
69
+ coordinate minimum of root grid in x2 direction
70
+ filedata['x2max'] = float
71
+ coordinate maximum of root grid in x2 direction
72
+ filedata['x3min'] = float
73
+ coordinate minimum of root grid in x3 direction
74
+ filedata['x3max'] = float
75
+ coordinate maximum of root grid in x3 direction
76
+ filedata['nvars'] = int
77
+ number of output variables (including magnetic field if it exists)
78
+ filedata['mb_index'] = array with shape [n_mbs, 6]
79
+ is,ie,js,je,ks,ke range for output MeshBlock indexing (useful for slicing)
80
+ filedata['mb_logical'] = array with shape [n_mbs, 4]
81
+ i,j,k,level coordinates for each MeshBlock
82
+ filedata['mb_geometry'] = array with shape [n_mbs, 6]
83
+ x1i,x2i,x3i,dx1,dx2,dx3 including cell-centered location of left-most
84
+ cell and offsets between cells
85
+ filedata['mb_data'] = dict of arrays with shape [n_mbs, nx3, nx2, nx1]
86
+ {'var1':var1_array, 'var2':var2_array, ...} dictionary of fluid data arrays
87
+ for each variable in var_names
88
+ """
89
+
90
+ import numpy as np
91
+ import os
92
+ import h5py
93
+ import glob
94
+
95
+
96
+ def read_binary(filename):
97
+ """
98
+ Reads a bin file from filename to dictionary.
99
+
100
+ Originally written by Lev Arzamasskiy (leva@ias.edu) on 11/15/2021
101
+ Updated to support mesh refinement by George Wong (gnwong@ias.edu) on 01/27/2022
102
+ Made faster by Drummond Fielding on 09/09/2024
103
+
104
+ args:
105
+ filename - string
106
+ filename of bin file to read
107
+
108
+ returns:
109
+ filedata - dict
110
+ dictionary of fluid file data
111
+ """
112
+
113
+ filedata = {}
114
+
115
+ # load file and get size
116
+ fp = open(filename, "rb")
117
+ fp.seek(0, 2)
118
+ filesize = fp.tell()
119
+ fp.seek(0, 0)
120
+
121
+ # load header information and validate file format
122
+ code_header = fp.readline().split()
123
+ if len(code_header) < 1:
124
+ raise TypeError("unknown file format")
125
+ if code_header[0] != b"Athena":
126
+ raise TypeError(
127
+ f"bad file format \"{code_header[0].decode('utf-8')}\" "
128
+ + '(should be "Athena")'
129
+ )
130
+ version = code_header[-1].split(b"=")[-1]
131
+ if version != b"1.1":
132
+ raise TypeError(f"unsupported file format version {version.decode('utf-8')}")
133
+
134
+ pheader_count = int(fp.readline().split(b"=")[-1])
135
+ pheader = {}
136
+ for _ in range(pheader_count - 1):
137
+ key, val = [x.strip() for x in fp.readline().decode("utf-8").split("=")]
138
+ pheader[key] = val
139
+ time = float(pheader["time"])
140
+ cycle = int(pheader["cycle"])
141
+ locsizebytes = int(pheader["size of location"])
142
+ varsizebytes = int(pheader["size of variable"])
143
+
144
+ nvars = int(fp.readline().split(b"=")[-1])
145
+ var_list = [v.decode("utf-8") for v in fp.readline().split()[1:]]
146
+ header_size = int(fp.readline().split(b"=")[-1])
147
+ header = [
148
+ line.decode("utf-8").split("#")[0].strip()
149
+ for line in fp.read(header_size).split(b"\n")
150
+ ]
151
+ header = [line for line in header if len(line) > 0]
152
+
153
+ if locsizebytes not in [4, 8]:
154
+ raise ValueError(f"unsupported location size (in bytes) {locsizebytes}")
155
+ if varsizebytes not in [4, 8]:
156
+ raise ValueError(f"unsupported variable size (in bytes) {varsizebytes}")
157
+
158
+ locfmt = "d" if locsizebytes == 8 else "f"
159
+ varfmt = "d" if varsizebytes == 8 else "f"
160
+
161
+ # load grid information from header and validate
162
+ def get_from_header(header, blockname, keyname):
163
+ blockname = blockname.strip()
164
+ keyname = keyname.strip()
165
+ if not blockname.startswith("<"):
166
+ blockname = "<" + blockname
167
+ if blockname[-1] != ">":
168
+ blockname += ">"
169
+ block = "<none>"
170
+ for line in [entry for entry in header]:
171
+ if line.startswith("<"):
172
+ block = line
173
+ continue
174
+ key, value = line.split("=")
175
+ if block == blockname and key.strip() == keyname:
176
+ return value
177
+ raise KeyError(f"no parameter called {blockname}/{keyname}")
178
+
179
+ Nx1 = int(get_from_header(header, "<mesh>", "nx1"))
180
+ Nx2 = int(get_from_header(header, "<mesh>", "nx2"))
181
+ Nx3 = int(get_from_header(header, "<mesh>", "nx3"))
182
+ nx1 = int(get_from_header(header, "<meshblock>", "nx1"))
183
+ nx2 = int(get_from_header(header, "<meshblock>", "nx2"))
184
+ nx3 = int(get_from_header(header, "<meshblock>", "nx3"))
185
+
186
+ nghost = int(get_from_header(header, "<mesh>", "nghost"))
187
+
188
+ x1min = float(get_from_header(header, "<mesh>", "x1min"))
189
+ x1max = float(get_from_header(header, "<mesh>", "x1max"))
190
+ x2min = float(get_from_header(header, "<mesh>", "x2min"))
191
+ x2max = float(get_from_header(header, "<mesh>", "x2max"))
192
+ x3min = float(get_from_header(header, "<mesh>", "x3min"))
193
+ x3max = float(get_from_header(header, "<mesh>", "x3max"))
194
+
195
+ # load data from each meshblock
196
+ n_vars = len(var_list)
197
+ mb_count = 0
198
+
199
+ mb_index = []
200
+ mb_logical = []
201
+ mb_geometry = []
202
+
203
+ mb_data = {}
204
+ for var in var_list:
205
+ mb_data[var] = []
206
+ while fp.tell() < filesize:
207
+ mb_index.append(
208
+ np.frombuffer(fp.read(24), dtype=np.int32).astype(np.int64) - nghost
209
+ )
210
+ nx1_out = (mb_index[mb_count][1] - mb_index[mb_count][0]) + 1
211
+ nx2_out = (mb_index[mb_count][3] - mb_index[mb_count][2]) + 1
212
+ nx3_out = (mb_index[mb_count][5] - mb_index[mb_count][4]) + 1
213
+ mb_logical.append(np.frombuffer(fp.read(16), dtype=np.int32))
214
+ mb_geometry.append(
215
+ np.frombuffer(
216
+ fp.read(6 * locsizebytes),
217
+ dtype=np.float64 if locfmt == "d" else np.float32,
218
+ )
219
+ )
220
+
221
+ data = np.fromfile(
222
+ fp,
223
+ dtype=np.float64 if varfmt == "d" else np.float32,
224
+ count=nx1_out * nx2_out * nx3_out * n_vars,
225
+ )
226
+ data = data.reshape(nvars, nx3_out, nx2_out, nx1_out)
227
+ for vari, var in enumerate(var_list):
228
+ mb_data[var].append(data[vari])
229
+ mb_count += 1
230
+
231
+ fp.close()
232
+
233
+ filedata["header"] = header
234
+ filedata["time"] = time
235
+ filedata["cycle"] = cycle
236
+ filedata["var_names"] = var_list
237
+
238
+ filedata["Nx1"] = Nx1
239
+ filedata["Nx2"] = Nx2
240
+ filedata["Nx3"] = Nx3
241
+ filedata["nvars"] = nvars
242
+
243
+ filedata["x1min"] = x1min
244
+ filedata["x1max"] = x1max
245
+ filedata["x2min"] = x2min
246
+ filedata["x2max"] = x2max
247
+ filedata["x3min"] = x3min
248
+ filedata["x3max"] = x3max
249
+
250
+ filedata["n_mbs"] = mb_count
251
+ filedata["nx1_mb"] = nx1
252
+ filedata["nx2_mb"] = nx2
253
+ filedata["nx3_mb"] = nx3
254
+ filedata["nx1_out_mb"] = (mb_index[0][1] - mb_index[0][0]) + 1
255
+ filedata["nx2_out_mb"] = (mb_index[0][3] - mb_index[0][2]) + 1
256
+ filedata["nx3_out_mb"] = (mb_index[0][5] - mb_index[0][4]) + 1
257
+
258
+ filedata["mb_index"] = np.array(mb_index)
259
+ filedata["mb_logical"] = np.array(mb_logical)
260
+ filedata["mb_geometry"] = np.array(mb_geometry)
261
+ filedata["mb_data"] = mb_data
262
+
263
+ return filedata
264
+
265
+
266
+ def read_coarsened_binary(filename):
267
+ """
268
+ Reads a coarsened bin file from filename to dictionary.
269
+ Originally written by Lev Arzamasskiy (leva@ias.edu) on 11/15/2021
270
+ Updated to support mesh refinement by George Wong (gnwong@ias.edu) on 01/27/2022
271
+ Updated to support coarsened outputs and for speed by Drummond Fielding on 09/09/2024
272
+
273
+ args:
274
+ filename - string
275
+ filename of bin file to read
276
+
277
+ returns:
278
+ filedata - dict
279
+ dictionary of fluid file data
280
+ """
281
+
282
+ filedata = {}
283
+
284
+ # load file and get size
285
+ fp = open(filename, "rb")
286
+ fp.seek(0, 2)
287
+ filesize = fp.tell()
288
+ fp.seek(0, 0)
289
+
290
+ # load header information and validate file format
291
+ code_header = fp.readline().split()
292
+ if len(code_header) < 1:
293
+ raise TypeError("unknown file format")
294
+ if code_header[0] != b"Athena":
295
+ raise TypeError(
296
+ f"bad file format \"{code_header[0].decode('utf-8')}\" "
297
+ + '(should be "Athena")'
298
+ )
299
+ version = code_header[-1].split(b"=")[-1]
300
+ if version != b"1.1":
301
+ raise TypeError(f"unsupported file format version {version.decode('utf-8')}")
302
+
303
+ pheader_count = int(fp.readline().split(b"=")[-1])
304
+ pheader = {}
305
+ for _ in range(pheader_count - 1):
306
+ key, val = [x.strip() for x in fp.readline().decode("utf-8").split("=")]
307
+ pheader[key] = val
308
+ time = float(pheader["time"])
309
+ cycle = int(pheader["cycle"])
310
+ locsizebytes = int(pheader["size of location"])
311
+ varsizebytes = int(pheader["size of variable"])
312
+ coarsen_factor = int(pheader["coarsening factor"])
313
+
314
+ nvars = int(fp.readline().split(b"=")[-1])
315
+ var_list = [v.decode("utf-8") for v in fp.readline().split()[1:]]
316
+ header_size = int(fp.readline().split(b"=")[-1])
317
+ header = [
318
+ line.decode("utf-8").split("#")[0].strip()
319
+ for line in fp.read(header_size).split(b"\n")
320
+ ]
321
+ header = [line for line in header if len(line) > 0]
322
+
323
+ if locsizebytes not in [4, 8]:
324
+ raise ValueError(f"unsupported location size (in bytes) {locsizebytes}")
325
+ if varsizebytes not in [4, 8]:
326
+ raise ValueError(f"unsupported variable size (in bytes) {varsizebytes}")
327
+
328
+ locfmt = "d" if locsizebytes == 8 else "f"
329
+ varfmt = "d" if varsizebytes == 8 else "f"
330
+
331
+ # load grid information from header and validate
332
+ def get_from_header(header, blockname, keyname):
333
+ blockname = blockname.strip()
334
+ keyname = keyname.strip()
335
+ if not blockname.startswith("<"):
336
+ blockname = "<" + blockname
337
+ if blockname[-1] != ">":
338
+ blockname += ">"
339
+ block = "<none>"
340
+ for line in [entry for entry in header]:
341
+ if line.startswith("<"):
342
+ block = line
343
+ continue
344
+ key, value = line.split("=")
345
+ if block == blockname and key.strip() == keyname:
346
+ return value
347
+ raise KeyError(f"no parameter called {blockname}/{keyname}")
348
+
349
+ Nx1 = int(get_from_header(header, "<mesh>", "nx1"))
350
+ Nx2 = int(get_from_header(header, "<mesh>", "nx2"))
351
+ Nx3 = int(get_from_header(header, "<mesh>", "nx3"))
352
+ nx1 = int(get_from_header(header, "<meshblock>", "nx1"))
353
+ nx2 = int(get_from_header(header, "<meshblock>", "nx2"))
354
+ nx3 = int(get_from_header(header, "<meshblock>", "nx3"))
355
+
356
+ nghost = int(get_from_header(header, "<mesh>", "nghost"))
357
+
358
+ x1min = float(get_from_header(header, "<mesh>", "x1min"))
359
+ x1max = float(get_from_header(header, "<mesh>", "x1max"))
360
+ x2min = float(get_from_header(header, "<mesh>", "x2min"))
361
+ x2max = float(get_from_header(header, "<mesh>", "x2max"))
362
+ x3min = float(get_from_header(header, "<mesh>", "x3min"))
363
+ x3max = float(get_from_header(header, "<mesh>", "x3max"))
364
+
365
+ # load data from each meshblock
366
+ n_vars = len(var_list)
367
+ mb_count = 0
368
+
369
+ mb_index = []
370
+ mb_logical = []
371
+ mb_geometry = []
372
+
373
+ mb_data = {}
374
+ for var in var_list:
375
+ mb_data[var] = []
376
+ while fp.tell() < filesize:
377
+ mb_index_i = (
378
+ np.frombuffer(fp.read(24), dtype=np.int32).astype(np.int64) - nghost
379
+ )
380
+ mb_index.append(mb_index_i)
381
+ nx1_out = (mb_index_i[1] - mb_index_i[0]) + 1
382
+ nx2_out = (mb_index_i[3] - mb_index_i[2]) + 1
383
+ nx3_out = (mb_index_i[5] - mb_index_i[4]) + 1
384
+
385
+ mb_logical.append(np.frombuffer(fp.read(16), dtype=np.int32))
386
+ mb_geometry.append(
387
+ np.frombuffer(
388
+ fp.read(6 * locsizebytes),
389
+ dtype=np.float64 if locfmt == "d" else np.float32,
390
+ )
391
+ )
392
+
393
+ data = np.fromfile(
394
+ fp,
395
+ dtype=np.float64 if varfmt == "d" else np.float32,
396
+ count=nx1_out * nx2_out * nx3_out * n_vars,
397
+ )
398
+ data = data.reshape(nvars, nx3_out, nx2_out, nx1_out)
399
+ for vari, var in enumerate(var_list):
400
+ mb_data[var].append(data[vari])
401
+ mb_count += 1
402
+
403
+ fp.close()
404
+
405
+ filedata["header"] = header
406
+ filedata["time"] = time
407
+ filedata["cycle"] = cycle
408
+ filedata["var_names"] = var_list
409
+
410
+ filedata["Nx1"] = Nx1 // coarsen_factor
411
+ filedata["Nx2"] = Nx2 // coarsen_factor
412
+ filedata["Nx3"] = Nx3 // coarsen_factor
413
+ filedata["nvars"] = nvars
414
+ filedata["number_of_moments"] = int(pheader["number of moments"])
415
+
416
+ filedata["x1min"] = x1min
417
+ filedata["x1max"] = x1max
418
+ filedata["x2min"] = x2min
419
+ filedata["x2max"] = x2max
420
+ filedata["x3min"] = x3min
421
+ filedata["x3max"] = x3max
422
+
423
+ filedata["n_mbs"] = mb_count
424
+ filedata["nx1_mb"] = nx1 // coarsen_factor
425
+ filedata["nx2_mb"] = nx2 // coarsen_factor
426
+ filedata["nx3_mb"] = nx3 // coarsen_factor
427
+ filedata["nx1_out_mb"] = (mb_index[0][1] - mb_index[0][0]) + 1
428
+ filedata["nx2_out_mb"] = (mb_index[0][3] - mb_index[0][2]) + 1
429
+ filedata["nx3_out_mb"] = (mb_index[0][5] - mb_index[0][4]) + 1
430
+
431
+ filedata["mb_index"] = np.array(mb_index)
432
+ filedata["mb_logical"] = np.array(mb_logical)
433
+ filedata["mb_geometry"] = np.array(mb_geometry)
434
+ filedata["mb_data"] = mb_data
435
+
436
+ return filedata
437
+
438
+
439
+ def read_all_ranks_binary(rank0_filename):
440
+ """
441
+ Reads binary files from all ranks and combines them into a single dictionary.
442
+
443
+ args:
444
+ rank0_filename - string
445
+ filename of the rank 0 binary file
446
+
447
+ returns:
448
+ combined_filedata - dict
449
+ dictionary of combined fluid file data from all ranks
450
+ """
451
+ # Determine the directory and base filename pattern
452
+ # rank0_dir = os.path.dirname(rank0_filename)
453
+ # rank0_base = os.path.basename(rank0_filename).replace("rank_00000000", "rank_*")
454
+ # Find all rank files
455
+ rank_files = sorted(
456
+ glob.glob(
457
+ os.path.dirname(rank0_filename).replace("rank_00000000", "rank_*")
458
+ + "/"
459
+ + os.path.basename(rank0_filename)
460
+ )
461
+ )
462
+ file_sizes = np.array([os.path.getsize(file) for file in rank_files])
463
+ if len(np.unique(file_sizes)) > 1:
464
+ unique_file_sizes = np.unique(file_sizes)
465
+ larger_file_size = max(unique_file_sizes)
466
+ rank_files = [
467
+ file
468
+ for file, size in zip(rank_files, file_sizes)
469
+ if size == larger_file_size
470
+ ]
471
+
472
+ # Read the rank 0 file to get the metadata
473
+ rank0_filedata = read_binary(rank_files[0])
474
+
475
+ # Initialize combined filedata with rank 0 data
476
+ combined_filedata = rank0_filedata.copy()
477
+
478
+ # Initialize lists to hold combined data
479
+ combined_filedata["mb_index"] = []
480
+ combined_filedata["mb_logical"] = []
481
+ combined_filedata["mb_geometry"] = []
482
+ combined_filedata["mb_data"] = {var: [] for var in rank0_filedata["var_names"]}
483
+
484
+ # Read data from all ranks
485
+ for rank_filename in rank_files:
486
+ rank_filedata = read_binary(rank_filename)
487
+
488
+ combined_filedata["mb_index"].extend(rank_filedata["mb_index"])
489
+ combined_filedata["mb_logical"].extend(rank_filedata["mb_logical"])
490
+ combined_filedata["mb_geometry"].extend(rank_filedata["mb_geometry"])
491
+ for var in rank0_filedata["var_names"]:
492
+ combined_filedata["mb_data"][var].extend(rank_filedata["mb_data"][var])
493
+
494
+ # Convert lists to numpy arrays
495
+ combined_filedata["mb_index"] = np.array(combined_filedata["mb_index"])
496
+ combined_filedata["mb_logical"] = np.array(combined_filedata["mb_logical"])
497
+ combined_filedata["mb_geometry"] = np.array(combined_filedata["mb_geometry"])
498
+ for var in rank0_filedata["var_names"]:
499
+ combined_filedata["mb_data"][var] = np.array(combined_filedata["mb_data"][var])
500
+
501
+ # Ensure all relevant fields are stored
502
+ combined_filedata["header"] = rank0_filedata["header"]
503
+ combined_filedata["time"] = rank0_filedata["time"]
504
+ combined_filedata["cycle"] = rank0_filedata["cycle"]
505
+ combined_filedata["var_names"] = rank0_filedata["var_names"]
506
+ combined_filedata["Nx1"] = rank0_filedata["Nx1"]
507
+ combined_filedata["Nx2"] = rank0_filedata["Nx2"]
508
+ combined_filedata["Nx3"] = rank0_filedata["Nx3"]
509
+ combined_filedata["nvars"] = rank0_filedata["nvars"]
510
+ combined_filedata["x1min"] = rank0_filedata["x1min"]
511
+ combined_filedata["x1max"] = rank0_filedata["x1max"]
512
+ combined_filedata["x2min"] = rank0_filedata["x2min"]
513
+ combined_filedata["x2max"] = rank0_filedata["x2max"]
514
+ combined_filedata["x3min"] = rank0_filedata["x3min"]
515
+ combined_filedata["x3max"] = rank0_filedata["x3max"]
516
+ combined_filedata["n_mbs"] = len(combined_filedata["mb_index"])
517
+ combined_filedata["nx1_mb"] = rank0_filedata["nx1_mb"]
518
+ combined_filedata["nx2_mb"] = rank0_filedata["nx2_mb"]
519
+ combined_filedata["nx3_mb"] = rank0_filedata["nx3_mb"]
520
+ combined_filedata["nx1_out_mb"] = rank0_filedata["nx1_out_mb"]
521
+ combined_filedata["nx2_out_mb"] = rank0_filedata["nx2_out_mb"]
522
+ combined_filedata["nx3_out_mb"] = rank0_filedata["nx3_out_mb"]
523
+
524
+ return combined_filedata
525
+
526
+
527
+ def read_all_ranks_coarsened_binary(rank0_filename):
528
+ """
529
+ Reads binary files from all ranks and combines them into a single dictionary.
530
+
531
+ args:
532
+ rank0_filename - string
533
+ filename of the rank 0 binary file
534
+
535
+ returns:
536
+ combined_filedata - dict
537
+ dictionary of combined fluid file data from all ranks
538
+ """
539
+ # Determine the directory and base filename pattern
540
+ # rank0_dir = os.path.dirname(rank0_filename)
541
+ # rank0_base = os.path.basename(rank0_filename).replace("rank_00000000", "rank_*")
542
+
543
+ # Find all rank files
544
+ rank_files = sorted(
545
+ glob.glob(
546
+ os.path.dirname(rank0_filename).replace("rank_00000000", "rank_*")
547
+ + "/"
548
+ + os.path.basename(rank0_filename)
549
+ )
550
+ )
551
+ # print(rank_files)
552
+
553
+ # Read the rank 0 file to get the metadata
554
+ rank0_filedata = read_coarsened_binary(rank0_filename)
555
+
556
+ # Initialize combined filedata with rank 0 data
557
+ combined_filedata = rank0_filedata.copy()
558
+
559
+ # Initialize lists to hold combined data
560
+ combined_filedata["mb_index"] = []
561
+ combined_filedata["mb_logical"] = []
562
+ combined_filedata["mb_geometry"] = []
563
+ combined_filedata["mb_data"] = {var: [] for var in rank0_filedata["var_names"]}
564
+
565
+ # Read data from all ranks
566
+ for rank_filename in rank_files:
567
+ rank_filedata = read_coarsened_binary(rank_filename)
568
+
569
+ combined_filedata["mb_index"].extend(rank_filedata["mb_index"])
570
+ combined_filedata["mb_logical"].extend(rank_filedata["mb_logical"])
571
+ combined_filedata["mb_geometry"].extend(rank_filedata["mb_geometry"])
572
+ for var in rank0_filedata["var_names"]:
573
+ combined_filedata["mb_data"][var].extend(rank_filedata["mb_data"][var])
574
+
575
+ # Convert lists to numpy arrays
576
+ combined_filedata["mb_index"] = np.array(combined_filedata["mb_index"])
577
+ combined_filedata["mb_logical"] = np.array(combined_filedata["mb_logical"])
578
+ combined_filedata["mb_geometry"] = np.array(combined_filedata["mb_geometry"])
579
+ for var in rank0_filedata["var_names"]:
580
+ combined_filedata["mb_data"][var] = np.array(combined_filedata["mb_data"][var])
581
+
582
+ # Ensure all relevant fields are stored
583
+ combined_filedata["header"] = rank0_filedata["header"]
584
+ combined_filedata["time"] = rank0_filedata["time"]
585
+ combined_filedata["cycle"] = rank0_filedata["cycle"]
586
+ combined_filedata["var_names"] = rank0_filedata["var_names"]
587
+ combined_filedata["Nx1"] = rank0_filedata["Nx1"]
588
+ combined_filedata["Nx2"] = rank0_filedata["Nx2"]
589
+ combined_filedata["Nx3"] = rank0_filedata["Nx3"]
590
+ combined_filedata["nvars"] = rank0_filedata["nvars"]
591
+ combined_filedata["x1min"] = rank0_filedata["x1min"]
592
+ combined_filedata["x1max"] = rank0_filedata["x1max"]
593
+ combined_filedata["x2min"] = rank0_filedata["x2min"]
594
+ combined_filedata["x2max"] = rank0_filedata["x2max"]
595
+ combined_filedata["x3min"] = rank0_filedata["x3min"]
596
+ combined_filedata["x3max"] = rank0_filedata["x3max"]
597
+ combined_filedata["n_mbs"] = len(combined_filedata["mb_index"])
598
+ combined_filedata["nx1_mb"] = rank0_filedata["nx1_mb"]
599
+ combined_filedata["nx2_mb"] = rank0_filedata["nx2_mb"]
600
+ combined_filedata["nx3_mb"] = rank0_filedata["nx3_mb"]
601
+ combined_filedata["nx1_out_mb"] = rank0_filedata["nx1_out_mb"]
602
+ combined_filedata["nx2_out_mb"] = rank0_filedata["nx2_out_mb"]
603
+ combined_filedata["nx3_out_mb"] = rank0_filedata["nx3_out_mb"]
604
+
605
+ return combined_filedata
606
+
607
+
608
+ def read_binary_as_athdf(
609
+ filename,
610
+ raw=False,
611
+ data=None,
612
+ quantities=None,
613
+ dtype=None,
614
+ level=None,
615
+ return_levels=False,
616
+ subsample=False,
617
+ fast_restrict=False,
618
+ x1_min=None,
619
+ x1_max=None,
620
+ x2_min=None,
621
+ x2_max=None,
622
+ x3_min=None,
623
+ x3_max=None,
624
+ vol_func=None,
625
+ vol_params=None,
626
+ face_func_1=None,
627
+ face_func_2=None,
628
+ face_func_3=None,
629
+ center_func_1=None,
630
+ center_func_2=None,
631
+ center_func_3=None,
632
+ num_ghost=0,
633
+ ):
634
+ """
635
+ Reads a bin file and organizes data similar to athdf format without writing to file.
636
+ """
637
+ # Step 1: Read binary data
638
+ filedata = read_binary(filename)
639
+
640
+ # Step 2: Organize data similar to athdf
641
+ if raw:
642
+ return filedata
643
+
644
+ # Prepare dictionary for results
645
+ if data is None:
646
+ data = {}
647
+ new_data = True
648
+ else:
649
+ new_data = False
650
+
651
+ # Extract size information
652
+ max_level = max(filedata["mb_logical"][:, 3])
653
+ if level is None:
654
+ level = max_level
655
+ block_size = [
656
+ filedata["nx1_out_mb"],
657
+ filedata["nx2_out_mb"],
658
+ filedata["nx3_out_mb"],
659
+ ]
660
+ root_grid_size = [filedata["Nx1"], filedata["Nx2"], filedata["Nx3"]]
661
+ levels = filedata["mb_logical"][:, 3]
662
+ logical_locations = filedata["mb_logical"][:, :3]
663
+ if dtype is None:
664
+ dtype = np.float32
665
+
666
+ # Calculate nx_vals
667
+ nx_vals = []
668
+ for d in range(3):
669
+ if block_size[d] == 1 and root_grid_size[d] > 1: # sum or slice
670
+ other_locations = [
671
+ location
672
+ for location in zip(
673
+ levels,
674
+ logical_locations[:, (d + 1) % 3],
675
+ logical_locations[:, (d + 2) % 3],
676
+ )
677
+ ]
678
+ if len(set(other_locations)) == len(other_locations): # effective slice
679
+ nx_vals.append(1)
680
+ else: # nontrivial sum
681
+ num_blocks_this_dim = 0
682
+ for level_this_dim, loc_this_dim in zip(
683
+ levels, logical_locations[:, d]
684
+ ):
685
+ if level_this_dim <= level:
686
+ possible_max = (loc_this_dim + 1) * 2 ** (
687
+ level - level_this_dim
688
+ )
689
+ num_blocks_this_dim = max(num_blocks_this_dim, possible_max)
690
+ else:
691
+ possible_max = (loc_this_dim + 1) // 2 ** (
692
+ level_this_dim - level
693
+ )
694
+ num_blocks_this_dim = max(num_blocks_this_dim, possible_max)
695
+ nx_vals.append(num_blocks_this_dim)
696
+ elif block_size[d] == 1: # singleton dimension
697
+ nx_vals.append(1)
698
+ else: # normal case
699
+ nx_vals.append(root_grid_size[d] * 2**level + 2 * num_ghost)
700
+ nx1, nx2, nx3 = nx_vals
701
+ lx1, lx2, lx3 = [nx // bs for nx, bs in zip(nx_vals, block_size)]
702
+
703
+ # Set coordinate system and related functions
704
+ # coord = "cartesian" # Adjust based on your data
705
+ if vol_func is None:
706
+
707
+ def vol_func(xm, xp, ym, yp, zm, zp):
708
+ return (xp - xm) * (yp - ym) * (zp - zm)
709
+
710
+ # Define center functions if not provided
711
+ if center_func_1 is None:
712
+
713
+ def center_func_1(xm, xp):
714
+ return 0.5 * (xm + xp)
715
+
716
+ if center_func_2 is None:
717
+
718
+ def center_func_2(xm, xp):
719
+ return 0.5 * (xm + xp)
720
+
721
+ if center_func_3 is None:
722
+
723
+ def center_func_3(xm, xp):
724
+ return 0.5 * (xm + xp)
725
+
726
+ # Populate coordinate arrays
727
+ center_funcs = [center_func_1, center_func_2, center_func_3]
728
+ for d in range(1, 4):
729
+ xf = f"x{d}f"
730
+ xv = f"x{d}v"
731
+ nx = nx_vals[d - 1]
732
+ if nx == 1:
733
+ xmin = filedata[f"x{d}min"]
734
+ xmax = filedata[f"x{d}max"]
735
+ data[xf] = np.array([xmin, xmax], dtype=dtype)
736
+ else:
737
+ xmin = filedata[f"x{d}min"]
738
+ xmax = filedata[f"x{d}max"]
739
+ data[xf] = np.linspace(xmin, xmax, nx + 1, dtype=dtype)
740
+ data[xv] = np.empty(nx, dtype=dtype)
741
+ for i in range(nx):
742
+ data[xv][i] = center_funcs[d - 1](data[xf][i], data[xf][i + 1])
743
+
744
+ # Create list of quantities
745
+ if quantities is None:
746
+ quantities = filedata["var_names"]
747
+
748
+ # Account for selection
749
+ i_min, i_max = 0, nx1
750
+ j_min, j_max = 0, nx2
751
+ k_min, k_max = 0, nx3
752
+ if x1_min is not None:
753
+ i_min = max(i_min, np.searchsorted(data["x1f"], x1_min))
754
+ if x1_max is not None:
755
+ i_max = min(i_max, np.searchsorted(data["x1f"], x1_max))
756
+ if x2_min is not None:
757
+ j_min = max(j_min, np.searchsorted(data["x2f"], x2_min))
758
+ if x2_max is not None:
759
+ j_max = min(j_max, np.searchsorted(data["x2f"], x2_max))
760
+ if x3_min is not None:
761
+ k_min = max(k_min, np.searchsorted(data["x3f"], x3_min))
762
+ if x3_max is not None:
763
+ k_max = min(k_max, np.searchsorted(data["x3f"], x3_max))
764
+
765
+ # Prepare arrays for data and bookkeeping
766
+ if new_data:
767
+ for q in quantities:
768
+ data[q] = np.zeros(
769
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=dtype
770
+ )
771
+ if return_levels:
772
+ data["Levels"] = np.empty(
773
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=np.int32
774
+ )
775
+ else:
776
+ for q in quantities:
777
+ data[q].fill(0.0)
778
+ if not subsample and not fast_restrict and max_level > level:
779
+ restricted_data = np.zeros((lx3, lx2, lx1), dtype=bool)
780
+
781
+ # Step 3: Process each block
782
+ for block_num in range(filedata["n_mbs"]):
783
+ block_level = levels[block_num]
784
+ block_location = logical_locations[block_num]
785
+
786
+ # Implement logic for prolongation, restriction, subsampling
787
+ if block_level <= level:
788
+ s = 2 ** (level - block_level)
789
+ il_d = block_location[0] * block_size[0] * s if nx1 > 1 else 0
790
+ jl_d = block_location[1] * block_size[1] * s if nx2 > 1 else 0
791
+ kl_d = block_location[2] * block_size[2] * s if nx3 > 1 else 0
792
+ iu_d = il_d + block_size[0] * s if nx1 > 1 else 1
793
+ ju_d = jl_d + block_size[1] * s if nx2 > 1 else 1
794
+ ku_d = kl_d + block_size[2] * s if nx3 > 1 else 1
795
+
796
+ il_s = max(il_d, i_min) - il_d
797
+ jl_s = max(jl_d, j_min) - jl_d
798
+ kl_s = max(kl_d, k_min) - kl_d
799
+ iu_s = min(iu_d, i_max) - il_d
800
+ ju_s = min(ju_d, j_max) - jl_d
801
+ ku_s = min(ku_d, k_max) - kl_d
802
+
803
+ if il_s >= iu_s or jl_s >= ju_s or kl_s >= ku_s:
804
+ continue
805
+
806
+ il_d = max(il_d, i_min) - i_min
807
+ jl_d = max(jl_d, j_min) - j_min
808
+ kl_d = max(kl_d, k_min) - k_min
809
+ iu_d = min(iu_d, i_max) - i_min
810
+ ju_d = min(ju_d, j_max) - j_min
811
+ ku_d = min(ku_d, k_max) - k_min
812
+
813
+ for q in quantities:
814
+ block_data = filedata["mb_data"][q][block_num]
815
+ if s > 1:
816
+ block_data = np.repeat(
817
+ np.repeat(np.repeat(block_data, s, axis=2), s, axis=1),
818
+ s,
819
+ axis=0,
820
+ )
821
+ data[q][kl_d:ku_d, jl_d:ju_d, il_d:iu_d] = block_data[
822
+ kl_s:ku_s, jl_s:ju_s, il_s:iu_s
823
+ ]
824
+ else:
825
+ # Implement restriction logic here (similar to athdf function)
826
+ pass
827
+
828
+ if return_levels:
829
+ data["Levels"][kl_d:ku_d, jl_d:ju_d, il_d:iu_d] = block_level
830
+
831
+ # Step 4: Finalize data
832
+ if level < max_level and not subsample and not fast_restrict:
833
+ # Remove volume factors from restricted data
834
+ for loc3 in range(lx3):
835
+ for loc2 in range(lx2):
836
+ for loc1 in range(lx1):
837
+ if restricted_data[loc3, loc2, loc1]:
838
+ il = loc1 * block_size[0]
839
+ jl = loc2 * block_size[1]
840
+ kl = loc3 * block_size[2]
841
+ iu = il + block_size[0]
842
+ ju = jl + block_size[1]
843
+ ku = kl + block_size[2]
844
+ il = max(il, i_min) - i_min
845
+ jl = max(jl, j_min) - j_min
846
+ kl = max(kl, k_min) - k_min
847
+ iu = min(iu, i_max) - i_min
848
+ ju = min(ju, j_max) - j_min
849
+ ku = min(ku, k_max) - k_min
850
+ for k in range(kl, ku):
851
+ for j in range(jl, ju):
852
+ for i in range(il, iu):
853
+ x1m, x1p = data["x1f"][i], data["x1f"][i + 1]
854
+ x2m, x2p = data["x2f"][j], data["x2f"][j + 1]
855
+ x3m, x3p = data["x3f"][k], data["x3f"][k + 1]
856
+ vol = vol_func(x1m, x1p, x2m, x2p, x3m, x3p)
857
+ for q in quantities:
858
+ data[q][k, j, i] /= vol
859
+
860
+ # Add metadata
861
+ data["Time"] = filedata["time"]
862
+ data["NumCycles"] = filedata["cycle"]
863
+ data["MaxLevel"] = max_level
864
+
865
+ return data
866
+
867
+
868
+ def read_all_ranks_binary_as_athdf(
869
+ rank0_filename,
870
+ raw=False,
871
+ data=None,
872
+ quantities=None,
873
+ dtype=None,
874
+ level=None,
875
+ return_levels=False,
876
+ subsample=False,
877
+ fast_restrict=False,
878
+ x1_min=None,
879
+ x1_max=None,
880
+ x2_min=None,
881
+ x2_max=None,
882
+ x3_min=None,
883
+ x3_max=None,
884
+ vol_func=None,
885
+ vol_params=None,
886
+ face_func_1=None,
887
+ face_func_2=None,
888
+ face_func_3=None,
889
+ center_func_1=None,
890
+ center_func_2=None,
891
+ center_func_3=None,
892
+ num_ghost=0,
893
+ ):
894
+ """
895
+ Reads a bin file and organizes data similar to athdf format without writing to file.
896
+ """
897
+ # Step 1: Read binary data
898
+ filedata = read_all_ranks_binary(rank0_filename)
899
+
900
+ # Step 2: Organize data similar to athdf
901
+ if raw:
902
+ return filedata
903
+
904
+ # Prepare dictionary for results
905
+ if data is None:
906
+ data = {}
907
+ new_data = True
908
+ else:
909
+ new_data = False
910
+
911
+ # Extract size information
912
+ max_level = max(filedata["mb_logical"][:, 3])
913
+ if level is None:
914
+ level = max_level
915
+ block_size = [
916
+ filedata["nx1_out_mb"],
917
+ filedata["nx2_out_mb"],
918
+ filedata["nx3_out_mb"],
919
+ ]
920
+ root_grid_size = [filedata["Nx1"], filedata["Nx2"], filedata["Nx3"]]
921
+ levels = filedata["mb_logical"][:, 3]
922
+ logical_locations = filedata["mb_logical"][:, :3]
923
+ if dtype is None:
924
+ dtype = np.float32
925
+
926
+ # Calculate nx_vals
927
+ nx_vals = []
928
+ for d in range(3):
929
+ if block_size[d] == 1 and root_grid_size[d] > 1: # sum or slice
930
+ other_locations = [
931
+ location
932
+ for location in zip(
933
+ levels,
934
+ logical_locations[:, (d + 1) % 3],
935
+ logical_locations[:, (d + 2) % 3],
936
+ )
937
+ ]
938
+ if len(set(other_locations)) == len(other_locations): # effective slice
939
+ nx_vals.append(1)
940
+ else: # nontrivial sum
941
+ num_blocks_this_dim = 0
942
+ for level_this_dim, loc_this_dim in zip(
943
+ levels, logical_locations[:, d]
944
+ ):
945
+ if level_this_dim <= level:
946
+ possible_max = (loc_this_dim + 1) * 2 ** (
947
+ level - level_this_dim
948
+ )
949
+ num_blocks_this_dim = max(num_blocks_this_dim, possible_max)
950
+ else:
951
+ possible_max = (loc_this_dim + 1) // 2 ** (
952
+ level_this_dim - level
953
+ )
954
+ num_blocks_this_dim = max(num_blocks_this_dim, possible_max)
955
+ nx_vals.append(num_blocks_this_dim)
956
+ elif block_size[d] == 1: # singleton dimension
957
+ nx_vals.append(1)
958
+ else: # normal case
959
+ nx_vals.append(root_grid_size[d] * 2**level + 2 * num_ghost)
960
+ nx1, nx2, nx3 = nx_vals
961
+ lx1, lx2, lx3 = [nx // bs for nx, bs in zip(nx_vals, block_size)]
962
+ # Set coordinate system and related functions
963
+ # coord = "cartesian" # Adjust based on your data
964
+ if vol_func is None:
965
+
966
+ def vol_func(xm, xp, ym, yp, zm, zp):
967
+ return (xp - xm) * (yp - ym) * (zp - zm)
968
+
969
+ # Define center functions if not provided
970
+ if center_func_1 is None:
971
+
972
+ def center_func_1(xm, xp):
973
+ return 0.5 * (xm + xp)
974
+
975
+ if center_func_2 is None:
976
+
977
+ def center_func_2(xm, xp):
978
+ return 0.5 * (xm + xp)
979
+
980
+ if center_func_3 is None:
981
+
982
+ def center_func_3(xm, xp):
983
+ return 0.5 * (xm + xp)
984
+
985
+ # Populate coordinate arrays
986
+ center_funcs = [center_func_1, center_func_2, center_func_3]
987
+ for d in range(1, 4):
988
+ xf = f"x{d}f"
989
+ xv = f"x{d}v"
990
+ nx = nx_vals[d - 1]
991
+ if nx == 1:
992
+ xmin = filedata[f"x{d}min"]
993
+ xmax = filedata[f"x{d}max"]
994
+ data[xf] = np.array([xmin, xmax], dtype=dtype)
995
+ else:
996
+ xmin = filedata[f"x{d}min"]
997
+ xmax = filedata[f"x{d}max"]
998
+ data[xf] = np.linspace(xmin, xmax, nx + 1, dtype=dtype)
999
+ data[xv] = np.empty(nx, dtype=dtype)
1000
+ for i in range(nx):
1001
+ data[xv][i] = center_funcs[d - 1](data[xf][i], data[xf][i + 1])
1002
+
1003
+ # Create list of quantities
1004
+ if quantities is None:
1005
+ quantities = filedata["var_names"]
1006
+
1007
+ # Account for selection
1008
+ i_min, i_max = 0, nx1
1009
+ j_min, j_max = 0, nx2
1010
+ k_min, k_max = 0, nx3
1011
+ if x1_min is not None:
1012
+ i_min = max(i_min, np.searchsorted(data["x1f"], x1_min))
1013
+ if x1_max is not None:
1014
+ i_max = min(i_max, np.searchsorted(data["x1f"], x1_max))
1015
+ if x2_min is not None:
1016
+ j_min = max(j_min, np.searchsorted(data["x2f"], x2_min))
1017
+ if x2_max is not None:
1018
+ j_max = min(j_max, np.searchsorted(data["x2f"], x2_max))
1019
+ if x3_min is not None:
1020
+ k_min = max(k_min, np.searchsorted(data["x3f"], x3_min))
1021
+ if x3_max is not None:
1022
+ k_max = min(k_max, np.searchsorted(data["x3f"], x3_max))
1023
+
1024
+ # Prepare arrays for data and bookkeeping
1025
+ if new_data:
1026
+ for q in quantities:
1027
+ data[q] = np.zeros(
1028
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=dtype
1029
+ )
1030
+ if return_levels:
1031
+ data["Levels"] = np.empty(
1032
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=np.int32
1033
+ )
1034
+ else:
1035
+ for q in quantities:
1036
+ data[q].fill(0.0)
1037
+ if not subsample and not fast_restrict and max_level > level:
1038
+ restricted_data = np.zeros((lx3, lx2, lx1), dtype=bool)
1039
+
1040
+ # Step 3: Process each block
1041
+ for block_num in range(filedata["n_mbs"]):
1042
+ block_level = levels[block_num]
1043
+ block_location = logical_locations[block_num]
1044
+
1045
+ # Implement logic for prolongation, restriction, subsampling
1046
+ if block_level <= level:
1047
+ s = 2 ** (level - block_level)
1048
+ il_d = block_location[0] * block_size[0] * s if nx1 > 1 else 0
1049
+ jl_d = block_location[1] * block_size[1] * s if nx2 > 1 else 0
1050
+ kl_d = block_location[2] * block_size[2] * s if nx3 > 1 else 0
1051
+ iu_d = il_d + block_size[0] * s if nx1 > 1 else 1
1052
+ ju_d = jl_d + block_size[1] * s if nx2 > 1 else 1
1053
+ ku_d = kl_d + block_size[2] * s if nx3 > 1 else 1
1054
+
1055
+ il_s = max(il_d, i_min) - il_d
1056
+ jl_s = max(jl_d, j_min) - jl_d
1057
+ kl_s = max(kl_d, k_min) - kl_d
1058
+ iu_s = min(iu_d, i_max) - il_d
1059
+ ju_s = min(ju_d, j_max) - jl_d
1060
+ ku_s = min(ku_d, k_max) - kl_d
1061
+
1062
+ if il_s >= iu_s or jl_s >= ju_s or kl_s >= ku_s:
1063
+ continue
1064
+
1065
+ il_d = max(il_d, i_min) - i_min
1066
+ jl_d = max(jl_d, j_min) - j_min
1067
+ kl_d = max(kl_d, k_min) - k_min
1068
+ iu_d = min(iu_d, i_max) - i_min
1069
+ ju_d = min(ju_d, j_max) - j_min
1070
+ ku_d = min(ku_d, k_max) - k_min
1071
+
1072
+ for q in quantities:
1073
+ block_data = filedata["mb_data"][q][block_num]
1074
+ if s > 1:
1075
+ block_data = np.repeat(
1076
+ np.repeat(np.repeat(block_data, s, axis=2), s, axis=1),
1077
+ s,
1078
+ axis=0,
1079
+ )
1080
+ data[q][kl_d:ku_d, jl_d:ju_d, il_d:iu_d] = block_data[
1081
+ kl_s:ku_s, jl_s:ju_s, il_s:iu_s
1082
+ ]
1083
+ else:
1084
+ # Implement restriction logic here (similar to athdf function)
1085
+ pass
1086
+
1087
+ if return_levels:
1088
+ data["Levels"][kl_d:ku_d, jl_d:ju_d, il_d:iu_d] = block_level
1089
+
1090
+ # Step 4: Finalize data
1091
+ if level < max_level and not subsample and not fast_restrict:
1092
+ # Remove volume factors from restricted data
1093
+ for loc3 in range(lx3):
1094
+ for loc2 in range(lx2):
1095
+ for loc1 in range(lx1):
1096
+ if restricted_data[loc3, loc2, loc1]:
1097
+ il = loc1 * block_size[0]
1098
+ jl = loc2 * block_size[1]
1099
+ kl = loc3 * block_size[2]
1100
+ iu = il + block_size[0]
1101
+ ju = jl + block_size[1]
1102
+ ku = kl + block_size[2]
1103
+ il = max(il, i_min) - i_min
1104
+ jl = max(jl, j_min) - j_min
1105
+ kl = max(kl, k_min) - k_min
1106
+ iu = min(iu, i_max) - i_min
1107
+ ju = min(ju, j_max) - j_min
1108
+ ku = min(ku, k_max) - k_min
1109
+ for k in range(kl, ku):
1110
+ for j in range(jl, ju):
1111
+ for i in range(il, iu):
1112
+ x1m, x1p = data["x1f"][i], data["x1f"][i + 1]
1113
+ x2m, x2p = data["x2f"][j], data["x2f"][j + 1]
1114
+ x3m, x3p = data["x3f"][k], data["x3f"][k + 1]
1115
+ vol = vol_func(x1m, x1p, x2m, x2p, x3m, x3p)
1116
+ for q in quantities:
1117
+ data[q][k, j, i] /= vol
1118
+
1119
+ # Add metadata
1120
+ data["Time"] = filedata["time"]
1121
+ data["NumCycles"] = filedata["cycle"]
1122
+ data["MaxLevel"] = max_level
1123
+
1124
+ return data
1125
+
1126
+
1127
+ def read_all_ranks_coarsened_binary_as_athdf(
1128
+ rank0_filename,
1129
+ raw=False,
1130
+ data=None,
1131
+ quantities=None,
1132
+ dtype=None,
1133
+ level=None,
1134
+ return_levels=False,
1135
+ subsample=False,
1136
+ fast_restrict=False,
1137
+ x1_min=None,
1138
+ x1_max=None,
1139
+ x2_min=None,
1140
+ x2_max=None,
1141
+ x3_min=None,
1142
+ x3_max=None,
1143
+ vol_func=None,
1144
+ vol_params=None,
1145
+ face_func_1=None,
1146
+ face_func_2=None,
1147
+ face_func_3=None,
1148
+ center_func_1=None,
1149
+ center_func_2=None,
1150
+ center_func_3=None,
1151
+ num_ghost=0,
1152
+ ):
1153
+ """
1154
+ Reads a bin file and organizes data similar to athdf format without writing to file.
1155
+ """
1156
+ # Step 1: Read binary data
1157
+ filedata = read_all_ranks_coarsened_binary(rank0_filename)
1158
+
1159
+ # Step 2: Organize data similar to athdf
1160
+ if raw:
1161
+ return filedata
1162
+
1163
+ # Prepare dictionary for results
1164
+ if data is None:
1165
+ data = {}
1166
+ new_data = True
1167
+ else:
1168
+ new_data = False
1169
+
1170
+ # Extract size information
1171
+ max_level = max(filedata["mb_logical"][:, 3])
1172
+ if level is None:
1173
+ level = max_level
1174
+ block_size = [filedata["nx1_mb"], filedata["nx2_mb"], filedata["nx3_mb"]]
1175
+ root_grid_size = [filedata["Nx1"], filedata["Nx2"], filedata["Nx3"]]
1176
+ levels = filedata["mb_logical"][:, 3]
1177
+ logical_locations = filedata["mb_logical"][:, :3]
1178
+ if dtype is None:
1179
+ dtype = np.float32
1180
+
1181
+ # Calculate nx_vals
1182
+ nx_vals = []
1183
+ for d in range(3):
1184
+ if block_size[d] == 1 and root_grid_size[d] > 1:
1185
+ # Implement logic for sum or slice as in athdf
1186
+ nx_vals.append(root_grid_size[d] * 2**level)
1187
+ elif block_size[d] == 1:
1188
+ nx_vals.append(1)
1189
+ else:
1190
+ nx_vals.append(root_grid_size[d] * 2**level + 2 * num_ghost)
1191
+ nx1, nx2, nx3 = nx_vals
1192
+ lx1, lx2, lx3 = [nx // bs for nx, bs in zip(nx_vals, block_size)]
1193
+
1194
+ # Set coordinate system and related functions
1195
+ # coord = "cartesian" # Adjust based on your data
1196
+ if vol_func is None:
1197
+
1198
+ def vol_func(xm, xp, ym, yp, zm, zp):
1199
+ return (xp - xm) * (yp - ym) * (zp - zm)
1200
+
1201
+ # Define center functions if not provided
1202
+ if center_func_1 is None:
1203
+
1204
+ def center_func_1(xm, xp):
1205
+ return 0.5 * (xm + xp)
1206
+
1207
+ if center_func_2 is None:
1208
+
1209
+ def center_func_2(xm, xp):
1210
+ return 0.5 * (xm + xp)
1211
+
1212
+ if center_func_3 is None:
1213
+
1214
+ def center_func_3(xm, xp):
1215
+ return 0.5 * (xm + xp)
1216
+
1217
+ # Populate coordinate arrays
1218
+ center_funcs = [center_func_1, center_func_2, center_func_3]
1219
+ for d in range(1, 4):
1220
+ xf = f"x{d}f"
1221
+ xv = f"x{d}v"
1222
+ nx = nx_vals[d - 1]
1223
+ if nx == 1:
1224
+ xmin = filedata[f"x{d}min"]
1225
+ xmax = filedata[f"x{d}max"]
1226
+ data[xf] = np.array([xmin, xmax], dtype=dtype)
1227
+ else:
1228
+ xmin = filedata[f"x{d}min"]
1229
+ xmax = filedata[f"x{d}max"]
1230
+ data[xf] = np.linspace(xmin, xmax, nx + 1, dtype=dtype)
1231
+ data[xv] = np.empty(nx, dtype=dtype)
1232
+ for i in range(nx):
1233
+ data[xv][i] = center_funcs[d - 1](data[xf][i], data[xf][i + 1])
1234
+
1235
+ # Create list of quantities
1236
+ if quantities is None:
1237
+ quantities = filedata["var_names"]
1238
+
1239
+ # Account for selection
1240
+ i_min, i_max = 0, nx1
1241
+ j_min, j_max = 0, nx2
1242
+ k_min, k_max = 0, nx3
1243
+ if x1_min is not None:
1244
+ i_min = max(i_min, np.searchsorted(data["x1f"], x1_min))
1245
+ if x1_max is not None:
1246
+ i_max = min(i_max, np.searchsorted(data["x1f"], x1_max))
1247
+ if x2_min is not None:
1248
+ j_min = max(j_min, np.searchsorted(data["x2f"], x2_min))
1249
+ if x2_max is not None:
1250
+ j_max = min(j_max, np.searchsorted(data["x2f"], x2_max))
1251
+ if x3_min is not None:
1252
+ k_min = max(k_min, np.searchsorted(data["x3f"], x3_min))
1253
+ if x3_max is not None:
1254
+ k_max = min(k_max, np.searchsorted(data["x3f"], x3_max))
1255
+
1256
+ # Prepare arrays for data and bookkeeping
1257
+ if new_data:
1258
+ for q in quantities:
1259
+ data[q] = np.zeros(
1260
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=dtype
1261
+ )
1262
+ if return_levels:
1263
+ data["Levels"] = np.empty(
1264
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=np.int32
1265
+ )
1266
+ else:
1267
+ for q in quantities:
1268
+ data[q].fill(0.0)
1269
+ if not subsample and not fast_restrict and max_level > level:
1270
+ restricted_data = np.zeros((lx3, lx2, lx1), dtype=bool)
1271
+
1272
+ # Step 3: Process each block
1273
+ for block_num in range(filedata["n_mbs"]):
1274
+ block_level = levels[block_num]
1275
+ block_location = logical_locations[block_num]
1276
+
1277
+ # Implement logic for prolongation, restriction, subsampling
1278
+ if block_level <= level:
1279
+ s = 2 ** (level - block_level)
1280
+ il_d = block_location[0] * block_size[0] * s if nx1 > 1 else 0
1281
+ jl_d = block_location[1] * block_size[1] * s if nx2 > 1 else 0
1282
+ kl_d = block_location[2] * block_size[2] * s if nx3 > 1 else 0
1283
+ iu_d = il_d + block_size[0] * s if nx1 > 1 else 1
1284
+ ju_d = jl_d + block_size[1] * s if nx2 > 1 else 1
1285
+ ku_d = kl_d + block_size[2] * s if nx3 > 1 else 1
1286
+
1287
+ il_s = max(il_d, i_min) - il_d
1288
+ jl_s = max(jl_d, j_min) - jl_d
1289
+ kl_s = max(kl_d, k_min) - kl_d
1290
+ iu_s = min(iu_d, i_max) - il_d
1291
+ ju_s = min(ju_d, j_max) - jl_d
1292
+ ku_s = min(ku_d, k_max) - kl_d
1293
+
1294
+ if il_s >= iu_s or jl_s >= ju_s or kl_s >= ku_s:
1295
+ continue
1296
+
1297
+ il_d = max(il_d, i_min) - i_min
1298
+ jl_d = max(jl_d, j_min) - j_min
1299
+ kl_d = max(kl_d, k_min) - k_min
1300
+ iu_d = min(iu_d, i_max) - i_min
1301
+ ju_d = min(ju_d, j_max) - j_min
1302
+ ku_d = min(ku_d, k_max) - k_min
1303
+
1304
+ for q in quantities:
1305
+ block_data = filedata["mb_data"][q][block_num]
1306
+ if s > 1:
1307
+ block_data = np.repeat(
1308
+ np.repeat(np.repeat(block_data, s, axis=2), s, axis=1),
1309
+ s,
1310
+ axis=0,
1311
+ )
1312
+ data[q][kl_d:ku_d, jl_d:ju_d, il_d:iu_d] = block_data[
1313
+ kl_s:ku_s, jl_s:ju_s, il_s:iu_s
1314
+ ]
1315
+ else:
1316
+ # Implement restriction logic here (similar to athdf function)
1317
+ pass
1318
+
1319
+ if return_levels:
1320
+ data["Levels"][kl_d:ku_d, jl_d:ju_d, il_d:iu_d] = block_level
1321
+
1322
+ # Step 4: Finalize data
1323
+ if level < max_level and not subsample and not fast_restrict:
1324
+ # Remove volume factors from restricted data
1325
+ for loc3 in range(lx3):
1326
+ for loc2 in range(lx2):
1327
+ for loc1 in range(lx1):
1328
+ if restricted_data[loc3, loc2, loc1]:
1329
+ il = loc1 * block_size[0]
1330
+ jl = loc2 * block_size[1]
1331
+ kl = loc3 * block_size[2]
1332
+ iu = il + block_size[0]
1333
+ ju = jl + block_size[1]
1334
+ ku = kl + block_size[2]
1335
+ il = max(il, i_min) - i_min
1336
+ jl = max(jl, j_min) - j_min
1337
+ kl = max(kl, k_min) - k_min
1338
+ iu = min(iu, i_max) - i_min
1339
+ ju = min(ju, j_max) - j_min
1340
+ ku = min(ku, k_max) - k_min
1341
+ for k in range(kl, ku):
1342
+ for j in range(jl, ju):
1343
+ for i in range(il, iu):
1344
+ x1m, x1p = data["x1f"][i], data["x1f"][i + 1]
1345
+ x2m, x2p = data["x2f"][j], data["x2f"][j + 1]
1346
+ x3m, x3p = data["x3f"][k], data["x3f"][k + 1]
1347
+ vol = vol_func(x1m, x1p, x2m, x2p, x3m, x3p)
1348
+ for q in quantities:
1349
+ data[q][k, j, i] /= vol
1350
+
1351
+ # Add metadata
1352
+ data["Time"] = filedata["time"]
1353
+ data["NumCycles"] = filedata["cycle"]
1354
+ data["MaxLevel"] = max_level
1355
+
1356
+ return data
1357
+
1358
+
1359
+ def read_single_rank_binary_as_athdf(
1360
+ filename,
1361
+ raw=False,
1362
+ data=None,
1363
+ quantities=None,
1364
+ dtype=None,
1365
+ return_levels=False,
1366
+ x1_min=None,
1367
+ x1_max=None,
1368
+ x2_min=None,
1369
+ x2_max=None,
1370
+ x3_min=None,
1371
+ x3_max=None,
1372
+ vol_func=None,
1373
+ center_func_1=None,
1374
+ center_func_2=None,
1375
+ center_func_3=None,
1376
+ ):
1377
+ """
1378
+ Reads a single rank binary file and organizes data similar to
1379
+ athdf format without writing to file.
1380
+ """
1381
+ # Step 1: Read binary data for a single rank
1382
+ filedata = read_binary(filename)
1383
+
1384
+ if raw:
1385
+ return filedata
1386
+
1387
+ # Prepare dictionary for results
1388
+ if data is None:
1389
+ data = {}
1390
+ new_data = True
1391
+ else:
1392
+ new_data = False
1393
+
1394
+ # Extract size information
1395
+ block_size = [filedata["nx1_mb"], filedata["nx2_mb"], filedata["nx3_mb"]]
1396
+ if dtype is None:
1397
+ dtype = np.float32
1398
+
1399
+ # Set coordinate system and related functions
1400
+ if vol_func is None:
1401
+
1402
+ def vol_func(xm, xp, ym, yp, zm, zp):
1403
+ return (xp - xm) * (yp - ym) * (zp - zm)
1404
+
1405
+ if center_func_1 is None:
1406
+
1407
+ def center_func_1(xm, xp):
1408
+ return 0.5 * (xm + xp)
1409
+
1410
+ if center_func_2 is None:
1411
+
1412
+ def center_func_2(xm, xp):
1413
+ return 0.5 * (xm + xp)
1414
+
1415
+ if center_func_3 is None:
1416
+
1417
+ def center_func_3(xm, xp):
1418
+ return 0.5 * (xm + xp)
1419
+
1420
+ # Populate coordinate arrays
1421
+ center_funcs = [center_func_1, center_func_2, center_func_3]
1422
+ for d in range(1, 4):
1423
+ xf = f"x{d}f"
1424
+ xv = f"x{d}v"
1425
+ nx = block_size[d - 1]
1426
+
1427
+ # Use the meshblock geometry for local min and max
1428
+ xmin = filedata["mb_geometry"][0, (d - 1) * 2]
1429
+ xmax = filedata["mb_geometry"][0, (d - 1) * 2 + 1]
1430
+
1431
+ data[xf] = np.linspace(xmin, xmax, nx + 1, dtype=dtype)
1432
+ data[xv] = np.empty(nx, dtype=dtype)
1433
+ for i in range(nx):
1434
+ data[xv][i] = center_funcs[d - 1](data[xf][i], data[xf][i + 1])
1435
+
1436
+ # Create list of quantities
1437
+ if quantities is None:
1438
+ quantities = filedata["var_names"]
1439
+
1440
+ # Account for selection
1441
+ i_min, i_max = 0, block_size[0]
1442
+ j_min, j_max = 0, block_size[1]
1443
+ k_min, k_max = 0, block_size[2]
1444
+ if x1_min is not None:
1445
+ i_min = max(i_min, np.searchsorted(data["x1f"], x1_min))
1446
+ if x1_max is not None:
1447
+ i_max = min(i_max, np.searchsorted(data["x1f"], x1_max))
1448
+ if x2_min is not None:
1449
+ j_min = max(j_min, np.searchsorted(data["x2f"], x2_min))
1450
+ if x2_max is not None:
1451
+ j_max = min(j_max, np.searchsorted(data["x2f"], x2_max))
1452
+ if x3_min is not None:
1453
+ k_min = max(k_min, np.searchsorted(data["x3f"], x3_min))
1454
+ if x3_max is not None:
1455
+ k_max = min(k_max, np.searchsorted(data["x3f"], x3_max))
1456
+
1457
+ # Prepare arrays for data
1458
+ if new_data:
1459
+ for q in quantities:
1460
+ data[q] = np.zeros(
1461
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=dtype
1462
+ )
1463
+ if return_levels:
1464
+ data["Levels"] = np.empty(
1465
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=np.int32
1466
+ )
1467
+ else:
1468
+ for q in quantities:
1469
+ data[q].fill(0.0)
1470
+
1471
+ # Process the single block
1472
+ for q in quantities:
1473
+ block_data = filedata["mb_data"][q][0] # Single rank, so only one block
1474
+ data[q] = block_data[k_min:k_max, j_min:j_max, i_min:i_max]
1475
+
1476
+ if return_levels:
1477
+ data["Levels"].fill(filedata["mb_logical"][0, 3]) # Level of the single block
1478
+
1479
+ # Add metadata
1480
+ data["Time"] = filedata["time"]
1481
+ data["NumCycles"] = filedata["cycle"]
1482
+ data["MaxLevel"] = filedata["mb_logical"][0, 3]
1483
+
1484
+ return data
1485
+
1486
+
1487
+ def read_coarsened_binary_as_athdf(
1488
+ filename,
1489
+ raw=False,
1490
+ data=None,
1491
+ quantities=None,
1492
+ dtype=None,
1493
+ level=None,
1494
+ return_levels=False,
1495
+ subsample=False,
1496
+ fast_restrict=False,
1497
+ x1_min=None,
1498
+ x1_max=None,
1499
+ x2_min=None,
1500
+ x2_max=None,
1501
+ x3_min=None,
1502
+ x3_max=None,
1503
+ vol_func=None,
1504
+ vol_params=None,
1505
+ face_func_1=None,
1506
+ face_func_2=None,
1507
+ face_func_3=None,
1508
+ center_func_1=None,
1509
+ center_func_2=None,
1510
+ center_func_3=None,
1511
+ num_ghost=0,
1512
+ ):
1513
+ """
1514
+ Reads a bin file and organizes data similar to athdf format without writing to file.
1515
+ """
1516
+ # Step 1: Read binary data
1517
+ filedata = read_coarsened_binary(filename)
1518
+
1519
+ # Step 2: Organize data similar to athdf
1520
+ if raw:
1521
+ return filedata
1522
+
1523
+ # Prepare dictionary for results
1524
+ if data is None:
1525
+ data = {}
1526
+ new_data = True
1527
+ else:
1528
+ new_data = False
1529
+
1530
+ # Extract size information
1531
+ max_level = max(filedata["mb_logical"][:, 3])
1532
+ if level is None:
1533
+ level = max_level
1534
+ block_size = [filedata["nx1_mb"], filedata["nx2_mb"], filedata["nx3_mb"]]
1535
+ root_grid_size = [filedata["Nx1"], filedata["Nx2"], filedata["Nx3"]]
1536
+ levels = filedata["mb_logical"][:, 3]
1537
+ logical_locations = filedata["mb_logical"][:, :3]
1538
+ if dtype is None:
1539
+ dtype = np.float32
1540
+
1541
+ # Calculate nx_vals
1542
+ nx_vals = []
1543
+ for d in range(3):
1544
+ if block_size[d] == 1 and root_grid_size[d] > 1:
1545
+ # Implement logic for sum or slice as in athdf
1546
+ nx_vals.append(root_grid_size[d] * 2**level)
1547
+ elif block_size[d] == 1:
1548
+ nx_vals.append(1)
1549
+ else:
1550
+ nx_vals.append(root_grid_size[d] * 2**level + 2 * num_ghost)
1551
+ nx1, nx2, nx3 = nx_vals
1552
+ lx1, lx2, lx3 = [nx // bs for nx, bs in zip(nx_vals, block_size)]
1553
+
1554
+ # Set coordinate system and related functions
1555
+ # coord = "cartesian" # Adjust based on your data
1556
+ if vol_func is None:
1557
+
1558
+ def vol_func(xm, xp, ym, yp, zm, zp):
1559
+ return (xp - xm) * (yp - ym) * (zp - zm)
1560
+
1561
+ # Define center functions if not provided
1562
+ if center_func_1 is None:
1563
+
1564
+ def center_func_1(xm, xp):
1565
+ return 0.5 * (xm + xp)
1566
+
1567
+ if center_func_2 is None:
1568
+
1569
+ def center_func_2(xm, xp):
1570
+ return 0.5 * (xm + xp)
1571
+
1572
+ if center_func_3 is None:
1573
+
1574
+ def center_func_3(xm, xp):
1575
+ return 0.5 * (xm + xp)
1576
+
1577
+ # Populate coordinate arrays
1578
+ center_funcs = [center_func_1, center_func_2, center_func_3]
1579
+ for d in range(1, 4):
1580
+ xf = f"x{d}f"
1581
+ xv = f"x{d}v"
1582
+ nx = nx_vals[d - 1]
1583
+ if nx == 1:
1584
+ xmin = filedata[f"x{d}min"]
1585
+ xmax = filedata[f"x{d}max"]
1586
+ data[xf] = np.array([xmin, xmax], dtype=dtype)
1587
+ else:
1588
+ xmin = filedata[f"x{d}min"]
1589
+ xmax = filedata[f"x{d}max"]
1590
+ data[xf] = np.linspace(xmin, xmax, nx + 1, dtype=dtype)
1591
+ data[xv] = np.empty(nx, dtype=dtype)
1592
+ for i in range(nx):
1593
+ data[xv][i] = center_funcs[d - 1](data[xf][i], data[xf][i + 1])
1594
+
1595
+ # Create list of quantities
1596
+ if quantities is None:
1597
+ quantities = filedata["var_names"]
1598
+
1599
+ # Account for selection
1600
+ i_min, i_max = 0, nx1
1601
+ j_min, j_max = 0, nx2
1602
+ k_min, k_max = 0, nx3
1603
+ if x1_min is not None:
1604
+ i_min = max(i_min, np.searchsorted(data["x1f"], x1_min))
1605
+ if x1_max is not None:
1606
+ i_max = min(i_max, np.searchsorted(data["x1f"], x1_max))
1607
+ if x2_min is not None:
1608
+ j_min = max(j_min, np.searchsorted(data["x2f"], x2_min))
1609
+ if x2_max is not None:
1610
+ j_max = min(j_max, np.searchsorted(data["x2f"], x2_max))
1611
+ if x3_min is not None:
1612
+ k_min = max(k_min, np.searchsorted(data["x3f"], x3_min))
1613
+ if x3_max is not None:
1614
+ k_max = min(k_max, np.searchsorted(data["x3f"], x3_max))
1615
+
1616
+ # Prepare arrays for data and bookkeeping
1617
+ if new_data:
1618
+ for q in quantities:
1619
+ data[q] = np.zeros(
1620
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=dtype
1621
+ )
1622
+ if return_levels:
1623
+ data["Levels"] = np.empty(
1624
+ (k_max - k_min, j_max - j_min, i_max - i_min), dtype=np.int32
1625
+ )
1626
+ else:
1627
+ for q in quantities:
1628
+ data[q].fill(0.0)
1629
+ if not subsample and not fast_restrict and max_level > level:
1630
+ restricted_data = np.zeros((lx3, lx2, lx1), dtype=bool)
1631
+
1632
+ # Step 3: Process each block
1633
+ for block_num in range(filedata["n_mbs"]):
1634
+ block_level = levels[block_num]
1635
+ block_location = logical_locations[block_num]
1636
+
1637
+ # Implement logic for prolongation, restriction, subsampling
1638
+ if block_level <= level:
1639
+ s = 2 ** (level - block_level)
1640
+ il_d = block_location[0] * block_size[0] * s if nx1 > 1 else 0
1641
+ jl_d = block_location[1] * block_size[1] * s if nx2 > 1 else 0
1642
+ kl_d = block_location[2] * block_size[2] * s if nx3 > 1 else 0
1643
+ iu_d = il_d + block_size[0] * s if nx1 > 1 else 1
1644
+ ju_d = jl_d + block_size[1] * s if nx2 > 1 else 1
1645
+ ku_d = kl_d + block_size[2] * s if nx3 > 1 else 1
1646
+
1647
+ il_s = max(il_d, i_min) - il_d
1648
+ jl_s = max(jl_d, j_min) - jl_d
1649
+ kl_s = max(kl_d, k_min) - kl_d
1650
+ iu_s = min(iu_d, i_max) - il_d
1651
+ ju_s = min(ju_d, j_max) - jl_d
1652
+ ku_s = min(ku_d, k_max) - kl_d
1653
+
1654
+ if il_s >= iu_s or jl_s >= ju_s or kl_s >= ku_s:
1655
+ continue
1656
+
1657
+ il_d = max(il_d, i_min) - i_min
1658
+ jl_d = max(jl_d, j_min) - j_min
1659
+ kl_d = max(kl_d, k_min) - k_min
1660
+ iu_d = min(iu_d, i_max) - i_min
1661
+ ju_d = min(ju_d, j_max) - j_min
1662
+ ku_d = min(ku_d, k_max) - k_min
1663
+
1664
+ for q in quantities:
1665
+ block_data = filedata["mb_data"][q][block_num]
1666
+ if s > 1:
1667
+ block_data = np.repeat(
1668
+ np.repeat(np.repeat(block_data, s, axis=2), s, axis=1),
1669
+ s,
1670
+ axis=0,
1671
+ )
1672
+ data[q][kl_d:ku_d, jl_d:ju_d, il_d:iu_d] = block_data[
1673
+ kl_s:ku_s, jl_s:ju_s, il_s:iu_s
1674
+ ]
1675
+ else:
1676
+ # Implement restriction logic here (similar to athdf function)
1677
+ pass
1678
+
1679
+ if return_levels:
1680
+ data["Levels"][kl_d:ku_d, jl_d:ju_d, il_d:iu_d] = block_level
1681
+
1682
+ # Step 4: Finalize data
1683
+ if level < max_level and not subsample and not fast_restrict:
1684
+ # Remove volume factors from restricted data
1685
+ for loc3 in range(lx3):
1686
+ for loc2 in range(lx2):
1687
+ for loc1 in range(lx1):
1688
+ if restricted_data[loc3, loc2, loc1]:
1689
+ il = loc1 * block_size[0]
1690
+ jl = loc2 * block_size[1]
1691
+ kl = loc3 * block_size[2]
1692
+ iu = il + block_size[0]
1693
+ ju = jl + block_size[1]
1694
+ ku = kl + block_size[2]
1695
+ il = max(il, i_min) - i_min
1696
+ jl = max(jl, j_min) - j_min
1697
+ kl = max(kl, k_min) - k_min
1698
+ iu = min(iu, i_max) - i_min
1699
+ ju = min(ju, j_max) - j_min
1700
+ ku = min(ku, k_max) - k_min
1701
+ for k in range(kl, ku):
1702
+ for j in range(jl, ju):
1703
+ for i in range(il, iu):
1704
+ x1m, x1p = data["x1f"][i], data["x1f"][i + 1]
1705
+ x2m, x2p = data["x2f"][j], data["x2f"][j + 1]
1706
+ x3m, x3p = data["x3f"][k], data["x3f"][k + 1]
1707
+ vol = vol_func(x1m, x1p, x2m, x2p, x3m, x3p)
1708
+ for q in quantities:
1709
+ data[q][k, j, i] /= vol
1710
+
1711
+ # Add metadata
1712
+ data["Time"] = filedata["time"]
1713
+ data["NumCycles"] = filedata["cycle"]
1714
+ data["MaxLevel"] = max_level
1715
+
1716
+ return data
1717
+
1718
+
1719
+ def write_athdf(filename, fdata, varsize_bytes=4, locsize_bytes=8):
1720
+ """
1721
+ Writes an athdf (hdf5) file from a loaded python filedata object.
1722
+
1723
+ args:
1724
+ filename - string
1725
+ filename for output athdf (hdf5) file
1726
+ fdata - dict
1727
+ dictionary of fluid file data, e.g., as loaded from read_binary(...)
1728
+ varsize_bytes - int (default=4, options=4,8)
1729
+ number of bytes to use for output variable data
1730
+ locsize_bytes - int (default=8, options=4,8)
1731
+ number of bytes to use for output location data
1732
+ """
1733
+
1734
+ if varsize_bytes not in [4, 8]:
1735
+ raise ValueError(f"varsizebytes must be 4 or 8, not {varsize_bytes}")
1736
+ if locsize_bytes not in [4, 8]:
1737
+ raise ValueError(f"locsizebytes must be 4 or 8, not {locsize_bytes}")
1738
+ locfmt = "<f4" if locsize_bytes == 4 else "<f8"
1739
+ varfmt = "<f4" if varsize_bytes == 4 else "<f8"
1740
+
1741
+ # extract Mesh/MeshBlock parameters
1742
+ nmb = fdata["n_mbs"]
1743
+ Nx1 = fdata["Nx1"] # noqa: F841
1744
+ Nx2 = fdata["Nx2"]
1745
+ Nx3 = fdata["Nx3"]
1746
+ nx1 = fdata["nx1_mb"]
1747
+ nx2 = fdata["nx2_mb"]
1748
+ nx3 = fdata["nx3_mb"]
1749
+ nx1_out = fdata["nx1_out_mb"]
1750
+ nx2_out = fdata["nx2_out_mb"]
1751
+ nx3_out = fdata["nx3_out_mb"]
1752
+
1753
+ number_of_moments = fdata.get("number_of_moments", 1)
1754
+
1755
+ # check dimensionality/slicing
1756
+ two_d = Nx2 != 1 and Nx3 == 1
1757
+ three_d = Nx3 != 1
1758
+ x1slice = nx1_out == 1
1759
+ x2slice = nx2_out == 1 and (two_d or three_d)
1760
+ x3slice = nx3_out == 1 and three_d
1761
+
1762
+ # keep variable order but separate out magnetic field
1763
+ vars_without_b = [v for v in fdata["var_names"] if "bcc" not in v]
1764
+ vars_only_b = [v for v in fdata["var_names"] if v not in vars_without_b]
1765
+
1766
+ if len(vars_only_b) > 0:
1767
+ B = np.zeros((3 * number_of_moments, nmb, nx3_out, nx2_out, nx1_out))
1768
+ Levels = np.zeros(nmb)
1769
+ LogicalLocations = np.zeros((nmb, 3))
1770
+ uov = np.zeros((len(vars_without_b), nmb, nx3_out, nx2_out, nx1_out))
1771
+ x1f = np.zeros((nmb, nx1_out + 1))
1772
+ x1v = np.zeros((nmb, nx1_out))
1773
+ x2f = np.zeros((nmb, nx2_out + 1))
1774
+ x2v = np.zeros((nmb, nx2_out))
1775
+ x3f = np.zeros((nmb, nx3_out + 1))
1776
+ x3v = np.zeros((nmb, nx3_out))
1777
+
1778
+ for ivar, var in enumerate(vars_without_b):
1779
+ uov[ivar] = fdata["mb_data"][var]
1780
+ for ibvar, bvar in enumerate(vars_only_b):
1781
+ B[ibvar] = fdata["mb_data"][bvar]
1782
+
1783
+ for mb in range(nmb):
1784
+ logical = fdata["mb_logical"][mb]
1785
+ LogicalLocations[mb] = logical[:3]
1786
+ Levels[mb] = logical[-1]
1787
+ geometry = fdata["mb_geometry"][mb]
1788
+ mb_x1f = np.linspace(geometry[0], geometry[1], nx1 + 1)
1789
+ mb_x1v = 0.5 * (mb_x1f[1:] + mb_x1f[:-1])
1790
+ mb_x2f = np.linspace(geometry[2], geometry[3], nx2 + 1)
1791
+ mb_x2v = 0.5 * (mb_x2f[1:] + mb_x2f[:-1])
1792
+ mb_x3f = np.linspace(geometry[4], geometry[5], nx3 + 1)
1793
+ mb_x3v = 0.5 * (mb_x3f[1:] + mb_x3f[:-1])
1794
+ if x1slice:
1795
+ x1f[mb] = np.array(
1796
+ mb_x1f[(fdata["mb_index"][mb][0]): (fdata["mb_index"][mb][0] + 2)]
1797
+ )
1798
+ x1v[mb] = np.array([np.average(mb_x1f)])
1799
+ else:
1800
+ x1f[mb] = mb_x1f
1801
+ x1v[mb] = mb_x1v
1802
+ if x2slice:
1803
+ x2f[mb] = np.array(
1804
+ mb_x2f[(fdata["mb_index"][mb][2]): (fdata["mb_index"][mb][2] + 2)]
1805
+ )
1806
+ x2v[mb] = np.array([np.average(x2f[mb])])
1807
+ else:
1808
+ x2f[mb] = mb_x2f
1809
+ x2v[mb] = mb_x2v
1810
+ if x3slice:
1811
+ x3f[mb] = np.array(
1812
+ mb_x3f[(fdata["mb_index"][mb][4]): (fdata["mb_index"][mb][4] + 2)]
1813
+ )
1814
+ x3v[mb] = np.array([np.average(x3f[mb])])
1815
+ else:
1816
+ x3f[mb] = mb_x3f
1817
+ x3v[mb] = mb_x3v
1818
+
1819
+ # set dataset names and number of variables
1820
+ dataset_names = [np.array("uov", dtype="|S21")]
1821
+ dataset_nvars = [len(vars_without_b)]
1822
+ if len(vars_only_b) > 0:
1823
+ dataset_names.append(np.array("B", dtype="|S21"))
1824
+ dataset_nvars.append(len(vars_only_b))
1825
+
1826
+ # Set Attributes
1827
+ hfp = h5py.File(filename, "w")
1828
+ hfp.attrs["Header"] = fdata["header"]
1829
+ hfp.attrs["Time"] = fdata["time"]
1830
+ hfp.attrs["NumCycles"] = fdata["cycle"]
1831
+ hfp.attrs["Coordinates"] = np.array("cartesian", dtype="|S11")
1832
+ hfp.attrs["NumMeshBlocks"] = fdata["n_mbs"]
1833
+ hfp.attrs["MaxLevel"] = int(max(Levels))
1834
+ hfp.attrs["MeshBlockSize"] = [
1835
+ fdata["nx1_out_mb"],
1836
+ fdata["nx2_out_mb"],
1837
+ fdata["nx3_out_mb"],
1838
+ ]
1839
+ hfp.attrs["RootGridSize"] = [fdata["Nx1"], fdata["Nx2"], fdata["Nx3"]]
1840
+ hfp.attrs["RootGridX1"] = [fdata["x1min"], fdata["x1max"], 1.0]
1841
+ hfp.attrs["RootGridX2"] = [fdata["x2min"], fdata["x2max"], 1.0]
1842
+ hfp.attrs["RootGridX3"] = [fdata["x3min"], fdata["x3max"], 1.0]
1843
+ hfp.attrs["DatasetNames"] = dataset_names
1844
+ hfp.attrs["NumVariables"] = dataset_nvars
1845
+ hfp.attrs["VariableNames"] = [
1846
+ np.array(i, dtype="|S21") for i in (vars_without_b + vars_only_b)
1847
+ ]
1848
+
1849
+ # Create Datasets
1850
+ if len(vars_only_b) > 0:
1851
+ hfp.create_dataset("B", data=B, dtype=varfmt)
1852
+ hfp.create_dataset("Levels", data=Levels, dtype=">i4")
1853
+ hfp.create_dataset("LogicalLocations", data=LogicalLocations, dtype=">i8")
1854
+ hfp.create_dataset("uov", data=uov, dtype=varfmt)
1855
+ hfp.create_dataset("x1f", data=x1f, dtype=locfmt)
1856
+ hfp.create_dataset("x1v", data=x1v, dtype=locfmt)
1857
+ hfp.create_dataset("x2f", data=x2f, dtype=locfmt)
1858
+ hfp.create_dataset("x2v", data=x2v, dtype=locfmt)
1859
+ hfp.create_dataset("x3f", data=x3f, dtype=locfmt)
1860
+ hfp.create_dataset("x3v", data=x3v, dtype=locfmt)
1861
+ hfp.close()
1862
+
1863
+
1864
+ def write_xdmf_for(xdmfname, dumpname, fdata, mode="auto"):
1865
+ """
1866
+ Writes an xdmf file for a fluid snapshot file.
1867
+
1868
+ args:
1869
+ xdmfname - string
1870
+ name of xdmf file
1871
+ dumpname - string
1872
+ location of fluid data file relative to xdmfname directory
1873
+ fdata - dict
1874
+ dictionary of fluid file data, e.g., as loaded from read_binary(...)
1875
+ mode - string (unimplemented)
1876
+ force xdmf for format (auto sets by extension)
1877
+ """
1878
+
1879
+ fp = open(xdmfname, "w")
1880
+
1881
+ def write_meshblock(fp, mb, nx1, nx2, nx3, nmb, dumpname, vars_no_b, vars_w_b):
1882
+ fp.write(f""" <Grid Name="MeshBlock{mb}" GridType="Uniform">\n""")
1883
+ fp.write(""" <Topology TopologyType="3DRectMesh" """)
1884
+ fp.write(f""" NumberOfElements="{nx3+1} {nx2+1} {nx1+1}"/>\n""")
1885
+ fp.write(""" <Geometry GeometryType="VXVYVZ">\n""")
1886
+ fp.write(
1887
+ f""" <DataItem ItemType="HyperSlab" Dimensions="{nx1+1}">
1888
+ <DataItem Dimensions="3 2" NumberType="Int"> {mb} 0 1 1 1 {nx1+1} </DataItem>
1889
+ <DataItem Dimensions="{nmb} {nx1+1}" Format="HDF"> {dumpname}:/x1f </DataItem>
1890
+ </DataItem>
1891
+ <DataItem ItemType="HyperSlab" Dimensions="{nx2+1}">
1892
+ <DataItem Dimensions="3 2" NumberType="Int"> {mb} 0 1 1 1 {nx2+1} </DataItem>
1893
+ <DataItem Dimensions="{nmb} {nx2+1}" Format="HDF"> {dumpname}:/x2f </DataItem>
1894
+ </DataItem>
1895
+ <DataItem ItemType="HyperSlab" Dimensions="{nx3+1}">
1896
+ <DataItem Dimensions="3 2" NumberType="Int"> {mb} 0 1 1 1 {nx3+1} </DataItem>
1897
+ <DataItem Dimensions="{nmb} {nx3+1}" Format="HDF"> {dumpname}:/x3f </DataItem>
1898
+ </DataItem>
1899
+ </Geometry>\n"""
1900
+ )
1901
+
1902
+ nvar_no_b = len(vars_no_b)
1903
+ for vi, var_name in enumerate(vars_no_b):
1904
+ fp.write(
1905
+ f""" <Attribute Name="{var_name}" Center="Cell">
1906
+ <DataItem ItemType="HyperSlab" Dimensions="{nx3} {nx2} {nx1}">
1907
+ <DataItem Dimensions="3 5" NumberType="Int">
1908
+ {vi} {mb} 0 0 0 1 1 1 1 1 1 1 {nx3} {nx2} {nx1}
1909
+ </DataItem>
1910
+ <DataItem Dimensions="{nvar_no_b} {nmb} {nx3} {nx2} {nx1}" Format="HDF">
1911
+ {dumpname}:/uov
1912
+ </DataItem>
1913
+ </DataItem>
1914
+ </Attribute>\n"""
1915
+ )
1916
+
1917
+ nvar_w_b = len(vars_w_b)
1918
+ if nvar_w_b > 0:
1919
+ for vi, var_name in enumerate(vars_w_b):
1920
+ fp.write(
1921
+ f""" <Attribute Name="{var_name}" Center="Cell">
1922
+ <DataItem ItemType="HyperSlab" Dimensions="{nx3} {nx2} {nx1}">
1923
+ <DataItem Dimensions="3 5" NumberType="Int">
1924
+ {vi} {mb} 0 0 0 1 1 1 1 1 1 1 {nx3} {nx2} {nx1}
1925
+ </DataItem>
1926
+ <DataItem Dimensions="{nvar_w_b} {nmb} {nx3} {nx2} {nx1}" Format="HDF">
1927
+ {dumpname}:/B
1928
+ </DataItem>
1929
+ </DataItem>
1930
+ </Attribute>\n"""
1931
+ )
1932
+
1933
+ fp.write(""" </Grid>\n""")
1934
+
1935
+ fp.write(
1936
+ """<?xml version="1.0" ?>
1937
+ <!DOCTYPE Xdmf SYSTEM "Xdmf.dtd" []>
1938
+ <Xdmf Version="2.0">
1939
+ <Information Name="TimeVaryingMetaData" Value="True"/>\n"""
1940
+ )
1941
+ fp.write("""<Domain>\n""")
1942
+ fp.write("""<Grid Name="Mesh" GridType="Collection">\n""")
1943
+ fp.write(f""" <Time Value="{fdata['time']}"/>\n""")
1944
+
1945
+ vars_without_b = [v for v in fdata["var_names"] if "bcc" not in v]
1946
+ vars_only_b = [v for v in fdata["var_names"] if v not in vars_without_b]
1947
+
1948
+ nx1 = fdata["nx1_out_mb"]
1949
+ nx2 = fdata["nx2_out_mb"]
1950
+ nx3 = fdata["nx3_out_mb"]
1951
+ nmb = fdata["n_mbs"]
1952
+
1953
+ for mb in range(nmb):
1954
+ write_meshblock(
1955
+ fp, mb, nx1, nx2, nx3, nmb, dumpname, vars_without_b, vars_only_b
1956
+ )
1957
+
1958
+ fp.write("""</Grid>\n""")
1959
+ fp.write("""</Domain>\n""")
1960
+ fp.write("""</Xdmf>\n""")
1961
+
1962
+ fp.close()
1963
+
1964
+
1965
+ def convert_file(binary_fname):
1966
+ """
1967
+ Converts a single file.
1968
+
1969
+ args:
1970
+ binary_filename - string
1971
+ filename of bin file to convert
1972
+
1973
+ This will create new files "binary_data.bin" -> "binary_data.athdf" and
1974
+ "binary_data.athdf.xdmf"
1975
+ """
1976
+ athdf_fname = binary_fname.replace(".bin", "") + ".athdf"
1977
+ xdmf_fname = athdf_fname + ".xdmf"
1978
+ filedata = read_binary(binary_fname)
1979
+ write_athdf(athdf_fname, filedata)
1980
+ write_xdmf_for(xdmf_fname, os.path.basename(athdf_fname), filedata)
1981
+
1982
+
1983
+ if __name__ == "__main__":
1984
+ import sys
1985
+
1986
+ try:
1987
+ from tqdm import tqdm
1988
+ except ModuleNotFoundError:
1989
+
1990
+ def tqdm(L):
1991
+ for x in L:
1992
+ print(x)
1993
+ yield x
1994
+
1995
+ if len(sys.argv) < 2:
1996
+ print(f"Usage: {sys.argv[0]} output_file_1.bin [output_file_2.bin [...]]")
1997
+ exit(1)
1998
+
1999
+ for binary_fname in tqdm(sys.argv[1:]):
2000
+ convert_file(binary_fname)