|
| 1 | +<!-- markdownlint-disable --> |
| 2 | +# OpenRadioss Dataset Generation |
| 3 | + |
| 4 | +End-to-end recipe for generating parameterized OpenRadioss simulation datasets |
| 5 | +and converting the output to the LS-DYNA-style `d3plot` layout expected by |
| 6 | +[PhysicsNeMo-Curator](https://github.com/NVIDIA/physicsnemo-curator) — the |
| 7 | +upstream step for training structural dynamics surrogates in |
| 8 | +`examples/structural_mechanics/crash/` (for bumper beam) and |
| 9 | +`examples/structural_mechanics/drop_test` (for drop test). |
| 10 | + |
| 11 | +Two flows are supported out of the box: |
| 12 | + |
| 13 | +| Flow | Base case | Varied parameters | Default DoE size | |
| 14 | +|----------------|------------------------------|------------------------------------------------|------------------| |
| 15 | +| **bumper_beam**| Front-impact bumper beam | geometry scale, shell thickness, impact velocity, rigid-wall diameter/origin | 135 runs | |
| 16 | +| **drop_test** | Cell-phone drop onto a plane | per-material Young's modulus (E), rigid-wall plane orientation (rx, ry, rz) | 192 runs | |
| 17 | + |
| 18 | +Both flows share the same runner, d3plot-renaming step, and summary helpers; |
| 19 | +only the parameter-mutation logic differs. |
| 20 | + |
| 21 | + |
| 22 | +## Datasets |
| 23 | + |
| 24 | +Each flow is a Design-of-Experiments sweep: the generator takes the Cartesian |
| 25 | +product of the configured axes, writes one case folder per combination, and |
| 26 | +applies the mutations directly to the `_0000.rad` starter deck. The defaults |
| 27 | +below live in `DEFAULT_EXPERIMENT_SETUP` near the bottom of each flow's |
| 28 | +`generate_dataset.py` — edit or replace that dict to resize or reshape the sweep. |
| 29 | + |
| 30 | +### Bumper beam |
| 31 | + |
| 32 | +Front-impact bumper-beam setup driven by an impactor on a rigid wall. The |
| 33 | +sweep targets the crash-response envelope by perturbing *geometry and loading*: |
| 34 | +bumper shape, wall-thickness mass distribution, impact speed, and the |
| 35 | +impactor's relative location on the beam. |
| 36 | + |
| 37 | +Default: `5 × 3 × 3 × 1 × 3 = 135` runs. |
| 38 | + |
| 39 | +| # | Parameter | Deck target | Default values | Count | |
| 40 | +|---|-----------|-------------|----------------|------:| |
| 41 | +| 1 | **Geometry scale `(sx, sy, sz)`** — per-axis multiplier applied to every node | `/NODE` block | `(1,1,1)`, `(1,0.5,1)`, `(1,1,0.5)`, `(1,2,1)`, `(1,1,2)` | 5 | |
| 42 | +| 2 | **Initial velocity `(vx, vy, vz)`** of the impactor (mm/ms) | `/INIVEL` line 2 | `(-5,0,0)`, `(-3,0,0)`, `(-7,0,0)` | 3 | |
| 43 | +| 3 | **Shell thickness multiplier** | `/PROP/SHELL` line 4, col 3 | `1.0`, `0.7`, `1.3` | 3 | |
| 44 | +| 4 | **Rigid-wall diameter** (mm) | `/RWALL` line 3, col 3 | `254.0` | 1 | |
| 45 | +| 5 | **Rigid-wall origin `(x, y, z)`** — `M` is replaced; `M1` shifts by the same delta so the normal is preserved | `/RWALL` lines 4 + 5 | `(-170,0,0)`, `(-170,120,0)`, `(-170,240,0)` | 3 | |
| 46 | + |
| 47 | +Global-feature columns emitted for training: |
| 48 | +`geo_scale_{x,y,z}`, `velocity_{x,y,z}`, `thickness_scale`, `rwall_diameter`, |
| 49 | +`rwall_origin_{x,y,z}`. |
| 50 | + |
| 51 | +### Drop test |
| 52 | + |
| 53 | +Cell-phone drop onto a plane. The sweep targets *material stiffness × impact |
| 54 | +pose* — perturbing Young's modulus for every deformable component while |
| 55 | +tilting the contact plane — to cover the stiffness/orientation interactions |
| 56 | +that drive peak stress and deflection. |
| 57 | + |
| 58 | +Default: `2⁵ × 6 = 192` runs. |
| 59 | + |
| 60 | +| # | Parameter | Deck target | Default values | Combinations | |
| 61 | +|---|-----------|-------------|----------------|-------------:| |
| 62 | +| 1 | **Young's modulus scale per material** — scales the `E` value on the line after the `# E Nu` comment in each `/MAT/ELAST` or `/MAT/PLAS_TAB` block | mat 1 (polymer), mat 4 (battery), mat 5 (glass), mat 8 (PCB), mat 9 (composites) | each mat ∈ `{0.8, 1.2}` (±20%) | 2⁵ = 32 | |
| 63 | +| 2 | **Rigid-wall plane orientation `(rx, ry, rz)`** — rotates the `M→M1` normal about `M` by the given XYZ Euler angles (degrees); `M1` is rewritten | `/RWALL` line 5 | `(0,0,0)`, `(±10,0,0)`, `(0,±10,0)`, `(0,0,10)` | 6 | |
| 64 | + |
| 65 | +Global-feature columns emitted for training: |
| 66 | +`e_scale_mat{1,4,5,8,9}`, `rwall_orientation_{rx,ry,rz}`. |
| 67 | + |
| 68 | + |
| 69 | +## Directory layout |
| 70 | + |
| 71 | +``` |
| 72 | +openradioss_dataset_gen/ |
| 73 | +├── README.md |
| 74 | +├── common/ |
| 75 | +│ ├── radioss_runner.py # Starter / Engine / VTK / D3PLOT batch runner |
| 76 | +│ ├── rename_d3plot.py # Rename <BASE>.d3plot* -> d3plot* |
| 77 | +│ └── summary_utils.py # summary.json + summary.csv writer |
| 78 | +├── bumper_beam/ |
| 79 | +│ ├── generate_dataset.py # DoE generator (see DEFAULT_EXPERIMENT_SETUP) |
| 80 | +│ ├── run_simulations.py # Thin wrapper over common.radioss_runner |
| 81 | +│ ├── restructure_global_features.py # summary.json -> global_features.json |
| 82 | +│ └── templates/ # place Bumper_Beam_AP_meshed_0000.rad / _0001.rad here |
| 83 | +└── drop_test/ |
| 84 | + ├── generate_dataset.py |
| 85 | + ├── run_simulations.py |
| 86 | + ├── restructure_global_features.py |
| 87 | + └── templates/ # place Cell_Phone_Drop_0000.rad / _0001.rad here |
| 88 | +``` |
| 89 | + |
| 90 | + |
| 91 | +## Prerequisites |
| 92 | + |
| 93 | +- **OpenRadioss** built with the GFortran-Linux executables (`starter_linux64_gf`, |
| 94 | + `engine_linux64_gf`, `anim_to_vtk_linux64_gf`). Point the runner at your build |
| 95 | + via the `OPENRADIOSS_ROOT` env var. |
| 96 | +- **`vortex_radioss`** for the `anim -> d3plot` conversion step: |
| 97 | + ```bash |
| 98 | + pip install vortex-radioss |
| 99 | + ``` |
| 100 | + |
| 101 | +### Base case inputs |
| 102 | + |
| 103 | +The `.rad` starter and engine files are **not** shipped in the recipe because |
| 104 | +the bumper deck is ~1.8 MB and the drop-test deck is ~42 MB. Fetch them from |
| 105 | +the upstream OpenRadioss sources and place them under the corresponding |
| 106 | +`templates/` directory: |
| 107 | + |
| 108 | +| Flow | Files to place under `templates/` | Source | |
| 109 | +|-------------|------------------------------------------------------------|--------| |
| 110 | +| bumper_beam | `Bumper_Beam_AP_meshed_0000.rad`, `Bumper_Beam_AP_meshed_0001.rad` | https://openradioss.atlassian.net/wiki/spaces/OPENRADIOSS/pages/11075585/Bumper+Beam | |
| 111 | +| drop_test | `Cell_Phone_Drop_0000.rad`, `Cell_Phone_Drop_0001.rad` | OpenRadioss sample models (Cell Phone Drop example) | |
| 112 | + |
| 113 | + |
| 114 | +## Workflow (same for both flows) |
| 115 | + |
| 116 | +All commands below assume you are in the flow directory (`bumper_beam/` or |
| 117 | +`drop_test/`). |
| 118 | + |
| 119 | +### 1. Generate the parameterised dataset |
| 120 | + |
| 121 | +```bash |
| 122 | +python generate_dataset.py |
| 123 | +``` |
| 124 | + |
| 125 | +Creates: |
| 126 | +- `dataset/run1/`, `dataset/run2/`, … each containing a mutated `_0000.rad` |
| 127 | + starter, a copy of the `_0001.rad` engine deck, and a per-run `<run>.json` |
| 128 | + metadata file. |
| 129 | +- `dataset/summary.json` — hierarchical log of every run. |
| 130 | +- `dataset/summary.csv` — flat, ML-loader-friendly view. |
| 131 | + |
| 132 | +To customise the DoE, edit `DEFAULT_EXPERIMENT_SETUP` near the bottom of |
| 133 | +`generate_dataset.py`, or import `generate_dataset(...)` from another driver |
| 134 | +script. |
| 135 | + |
| 136 | +### 2. Run the simulations |
| 137 | + |
| 138 | +```bash |
| 139 | +export OPENRADIOSS_ROOT=/path/to/OpenRadioss # required |
| 140 | +export MAX_PARALLEL_JOBS=4 # optional |
| 141 | +export OMP_NUM_THREADS=8 # optional |
| 142 | +python run_simulations.py |
| 143 | +``` |
| 144 | + |
| 145 | +For each `run*/` folder, the runner executes: |
| 146 | +1. `starter_linux64_gf -i <BASE>_0000.rad -nt $OMP_NUM_THREADS` (log: `starter.log`) |
| 147 | +2. `engine_linux64_gf -i <BASE>_0001.rad` (log: `engine.log`) |
| 148 | +3. `anim_to_vtk_linux64_gf <BASE>A### > <BASE>A###.vtk` per animation frame |
| 149 | +4. `vortex_radioss.animtod3plot.Anim_to_D3plot.readAndConvert(<BASE>)` — emits |
| 150 | + `<BASE>.d3plot`, `<BASE>.d3plot01`, … (log: `d3plot_conv.log`) |
| 151 | + |
| 152 | +Total wall time scales with `MAX_PARALLEL_JOBS * OMP_NUM_THREADS`; do not |
| 153 | +oversubscribe CPU cores. |
| 154 | + |
| 155 | +Set `DEBUG_MODE=1` to run only the first case. |
| 156 | + |
| 157 | +### 3. Rename d3plot files to the LS-DYNA convention |
| 158 | + |
| 159 | +```bash |
| 160 | +python ../common/rename_d3plot.py \ |
| 161 | + --dataset-dir ./dataset \ |
| 162 | + --base-name Bumper_Beam_AP_meshed # or Cell_Phone_Drop |
| 163 | +``` |
| 164 | + |
| 165 | +PhysicsNeMo-Curator expects the plain `d3plot`, `d3plot01`, … layout; the |
| 166 | +runner produces `<BASE>.d3plot*`. This step renames them in place. |
| 167 | + |
| 168 | +### 4. Restructure global features for the datapipe |
| 169 | + |
| 170 | +The recipes' datapipe expects a `global_features.json` keyed by run ID |
| 171 | +(see `examples/structural_mechanics/crash/README.md`, *Global features* |
| 172 | +section). Convert `dataset/summary.json` into that format: |
| 173 | + |
| 174 | +```bash |
| 175 | +python restructure_global_features.py # writes ./global_features.json |
| 176 | +``` |
| 177 | + |
| 178 | +Bumper-beam keys: `geo_scale_{x,y,z}`, `velocity_{x,y,z}`, `thickness_scale`, |
| 179 | +`rwall_diameter`, `rwall_origin_{x,y,z}`. |
| 180 | + |
| 181 | +Drop-test keys: `e_scale_mat{1,4,5,8,9}`, |
| 182 | +`rwall_orientation_{rx,ry,rz}`. |
| 183 | + |
| 184 | + |
| 185 | +## One-command end-to-end |
| 186 | + |
| 187 | +After placing the `.rad` templates in `templates/`: |
| 188 | + |
| 189 | +```bash |
| 190 | +cd bumper_beam # or: cd drop_test |
| 191 | +python generate_dataset.py && \ |
| 192 | + python run_simulations.py && \ |
| 193 | + python ../common/rename_d3plot.py --dataset-dir ./dataset \ |
| 194 | + --base-name Bumper_Beam_AP_meshed && \ |
| 195 | + python restructure_global_features.py |
| 196 | +``` |
| 197 | + |
| 198 | + |
| 199 | +## Handing off to PhysicsNeMo-Curator |
| 200 | + |
| 201 | +After step 3, the dataset layout matches what PhysicsNeMo-Curator expects: |
| 202 | + |
| 203 | +``` |
| 204 | +dataset/ |
| 205 | +├── run1/ |
| 206 | +│ ├── d3plot |
| 207 | +│ ├── d3plot01 |
| 208 | +│ └── ... |
| 209 | +├── run2/ |
| 210 | +│ └── ... |
| 211 | +└── ... |
| 212 | +``` |
| 213 | + |
| 214 | +Point the curator's `etl.source.input_dir` at this `dataset/` folder and |
| 215 | +follow the VTP/Zarr export instructions in |
| 216 | +[`examples/structural_mechanics/crash/README.md`](../crash/README.md#data-preprocessing). |
| 217 | +Once curated, the `global_features.json` produced in step 4 slots directly |
| 218 | +into the training experiment configs via `training.global_features_filepath`. |
| 219 | + |
| 220 | + |
| 221 | +## Troubleshooting |
| 222 | + |
| 223 | +- **`starter.log` reports "BAD CARD" or zero E on a `/MAT` block.** |
| 224 | + OpenRadioss parses materials with fixed-width 20-char columns. If you add |
| 225 | + new materials with very large or very small E scales, inspect the rewritten |
| 226 | + `_0000.rad` to confirm the scaled value still fits the field. |
| 227 | +- **`anim_to_vtk` or `vortex_radioss` cannot find shared libraries.** |
| 228 | + The runner sets `LD_LIBRARY_PATH` from `OPENRADIOSS_ROOT/extlib/{hm_reader,h3d}/lib/linux64`. |
| 229 | + If your OpenRadioss layout differs, edit `common/radioss_runner.build_radioss_env`. |
| 230 | +- **`run_simulations.py` reports "No animation files (A001) found".** |
| 231 | + The Engine step did not finish a first animation checkpoint — check |
| 232 | + `engine.log`. Common cause: timestep collapse after an over-aggressive E |
| 233 | + reduction on the drop-test flow. |
| 234 | +- **`restructure_global_features.py` fails with `Duplicate run_id`.** |
| 235 | + Indicates `summary.json` was appended to twice. Regenerate from a clean |
| 236 | + `dataset/` folder. |
| 237 | + |
| 238 | + |
| 239 | +## Assumptions & OpenRadioss version notes |
| 240 | + |
| 241 | +- Assumed Radioss deck format: block-style `.rad` files with whitespace- or |
| 242 | + comma-separated fixed-width fields, matching the Altair docs shipped with |
| 243 | + OpenRadioss 2024.x. No `/INCLUDE` or `/SUBMODEL` support. |
| 244 | +- Material E detection in the drop-test flow relies on a preceding comment |
| 245 | + line that mentions `E` and `nu`/`Nu`. Remove this comment in the starter |
| 246 | + and the scaling silently becomes a no-op — intended, since the deck would |
| 247 | + then have an unknown field layout. |
| 248 | +- The bumper flow scales `/PROP/SHELL` thickness at line 4, column 3. Decks |
| 249 | + that place thickness elsewhere (e.g. `/PROP/TYPE1`) need the mutation logic |
| 250 | + adapted in `bumper_beam/generate_dataset.py`. |
0 commit comments