Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions Tools/multiFabConverter/offline/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# multiFabConverter Offline Tools

This directory provides offline tools to read AMReX plot files (`.plt`) and convert them to NumPy arrays or 2D slices, using **pyAMReX** (MPI-enabled).

---

## `main.py` — parallel visualization

### Purpose

Reads an AMReX plot file in a parallel MPI run. AMReX's `DistributionMapping` automatically distributes boxes across ranks. The script:

1. Prints how many boxes each MPI rank owns.
2. Gathers the full domain into a NumPy array via `input_mf[:]` (collective operation across all ranks).
3. On rank 0, extracts a 2D slice of `x_velocity` at a given physical z-coordinate and saves it as `slice_z.png`.

### Dependencies

- Python 3.8+
- `numpy`, `matplotlib`, `mpi4py`
- `pyAMReX` (MPI-enabled build): `conda install -c conda-forge "pyamrex=*=*mpi_openmpi*"`

### Usage

```bash
mpirun -np <N> python main.py <pltfile> [lev] [z_real]
```

| Argument | Description | Default |
|----------|-------------|---------|
| `pltfile` | Path to the AMReX plot file directory (e.g. `plt00002`) | required |
| `lev` | AMR level to read (0 = coarsest) | `0` |
| `z_real` | Physical z-coordinate for the 2D slice | `5.0` |

**Examples:**

```bash
mpirun -np 4 python main.py plt00002
mpirun -np 4 python main.py ../../../Tutorials/FlowPastSphere/plt00002 0 5.0
```

### Output

- **Terminal**: AMReX/MPI initialization info; per-rank box count.
- **File**: `slice_z.png` written by rank 0 in the current directory.

> **Note:** AMReX is initialized with `amr.initialize([])`. Do not pass `sys.argv` to it — AMReX's ParmParse will misinterpret the arguments and abort.

---

## `script_plt_to_npz.py` — batch conversion to NPY

### Purpose

Single-process script (no `mpirun` needed) that reads one or more AMReX plot files, selects specified field components, and stacks them into a NumPy array of shape `(n_samples, ncomp, nx, ny)` saved as a `.npy` file. Intended for machine learning workflows or offline analysis.

### Usage

```bash
python script_plt_to_npz.py <pltfile> [pltfile2 ...] [-o output.npy] [-l lev] [-c comp1 comp2 ...]
```

| Argument | Description | Default |
|----------|-------------|---------|
| `pltfile` | One or more plot file paths, or folders containing `plt*` subdirectories (uses the latest one) | required |
| `-o` | Output `.npy` file path | `dataset.npy` |
| `-l` | AMR level to export | `0` |
| `-c` | Space-separated list of component names to export | `x_velocity y_velocity avg_pressure` |

**Examples:**

```bash
# Single plot file with default components
python script_plt_to_npz.py plt00002

# Multiple plot files, export x_velocity only
python script_plt_to_npz.py plt00001 plt00002 plt00003 -c x_velocity -o uvel.npy

# Folder containing multiple plt* subdirectories (uses the latest)
python script_plt_to_npz.py ../../../Tutorials/LidDrivenCavity/Re_100/
```

### Output

- **Terminal**: path of each plot file read; final array shape and component names.
- **File**: `.npy` array of shape `(n_samples, ncomp, nx, ny)`.
70 changes: 70 additions & 0 deletions Tools/multiFabConverter/offline/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import sys
import numpy as np
from mpi4py import MPI
import amrex.space3d as amr
import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt

amr.initialize([])

comm = MPI.COMM_WORLD
mpi_rank = comm.Get_rank()
mpi_size = comm.Get_size()

## Check if you have MPI
# you might get nompi version of pyamrex from conda install
if mpi_rank == 0:
print(
f"MPI size={mpi_size}, AMReX NProcs={amr.ParallelDescriptor.NProcs()}, "
f"Config.have_mpi={amr.Config.have_mpi}"
)

argv = sys.argv
if len(argv) < 2:
if mpi_rank == 0:
print("Usage: python main.py pltfile [lev] [z_real]")
amr.finalize()
sys.exit(0)

pltfilename = argv[1]
lev = int(argv[2]) if len(argv) > 2 else 0

## Read plot file; AMReX distributes boxes across MPI ranks automatically
input_plt = amr.PlotFileData(pltfilename)
input_mf = input_plt.get(lev)

nBox_total = input_mf.box_array().size
local_fabs = input_mf.to_numpy()
nBox_local = len(local_fabs)
comm.Barrier()
for r in range(mpi_size):
if mpi_rank == r:
print(f"Rank {r}: {nBox_local}/{nBox_total} boxes")
comm.Barrier()

## Gather the full domain array for visualization (all ranks participate)
full_np = input_mf[:]

## Draw a z-slice of x_velocity
names = list(input_plt.varNames())
idxcomp = names.index("x_velocity")
z_real = float(argv[3]) if len(argv) > 3 else 5.0
dz = input_plt.cellSize(lev)[2]
z0 = input_plt.probLo()[2]
k = int(round((z_real - (z0 + 0.5 * dz)) / dz))

if mpi_rank == 0:
slice_2d = full_np[:, :, k, idxcomp].T
plt.figure(figsize=(6, 5))
im = plt.imshow(slice_2d, origin="lower")
plt.title(f"{names[idxcomp]} at z≈{z_real} (k={k})")
plt.colorbar(im, shrink=0.8)
out_path = "slice_z.png"
plt.savefig(out_path, dpi=150, bbox_inches="tight")
plt.close()

comm.Barrier()

amr.finalize()
74 changes: 74 additions & 0 deletions Tools/multiFabConverter/offline/script_plt_to_npz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import sys
import os
import argparse
import numpy as np
import amrex.space3d as amr

amr.initialize([])

def _select_components(all_names, requested):
if not requested:
return list(range(len(all_names)))
missing = [n for n in requested if n not in all_names]
if missing:
raise ValueError(f"Components not found in plotfile: {missing}")
return [all_names.index(n) for n in requested]

def _resolve_pltfile(path):
if not os.path.isdir(path):
return path
candidates = [
(int(n[3:]), n) for n in os.listdir(path)
if n.startswith("plt") and n[3:].isdigit()
]
if not candidates:
raise ValueError(f"No plt* directories found in {path}")
return os.path.join(path, sorted(candidates)[-1][1])

parser = argparse.ArgumentParser(description="Convert AMReX plt files to a stacked NPY tensor.")
parser.add_argument("pltfiles", nargs="+", help="plt directories or case folders; folders use the latest plt* inside")
parser.add_argument("-o", "--output", default="dataset.npy", help="output .npy path (default: dataset.npy)")
parser.add_argument("-l", "--level", type=int, default=0, help="AMR level to export (default: 0)")
parser.add_argument("-c", "--components", nargs="*",
default=["x_velocity", "y_velocity", "avg_pressure"],
help="component names to export (default: x_velocity y_velocity avg_pressure)")
args = parser.parse_args()

samples = []
selected_names = None

for path in args.pltfiles:
pltfile = _resolve_pltfile(path)
print(f"Reading {pltfile}")
pfd = amr.PlotFileData(pltfile)
if args.level > pfd.finestLevel():
raise ValueError(f"Requested level {args.level} exceeds finest level {pfd.finestLevel()}")
mf = pfd.get(args.level)
var_names = list(pfd.varNames())
comp_idx = _select_components(var_names, args.components)
names = [var_names[i] for i in comp_idx]

# mf[:] shape: (nx, ny, nz, ncomp) for 3-D, or (nx, ny, ncomp) for 2-D
full_np = mf[:]
if full_np.ndim == 4:
full_np = full_np[:, :, :, comp_idx]
if full_np.shape[2] == 1:
full_np = full_np[:, :, 0, :] # squeeze pseudo-2D z dim
sample = np.transpose(full_np, (2, 0, 1)) # (ncomp, nx, ny)
elif full_np.ndim == 3:
full_np = full_np[:, :, comp_idx]
sample = np.transpose(full_np, (2, 0, 1)) # (ncomp, nx, ny)
else:
raise ValueError(f"Unexpected array ndim {full_np.ndim}")

if selected_names is None:
selected_names = names
elif names != selected_names:
raise ValueError(f"Component mismatch in {pltfile}: {names} vs {selected_names}")
samples.append(sample)

data = np.stack(samples, axis=0) # (n_samples, ncomp, nx, ny)
np.save(args.output, data)
print(f"Saved {args.output} shape={data.shape} components={selected_names}")

amr.finalize()
Loading