[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-nvidia-physicsnemo-cfd-create-dataset-adapter":3,"mdc-lg6bfo-key":44,"related-repo-nvidia-physicsnemo-cfd-create-dataset-adapter":2071,"related-org-nvidia-physicsnemo-cfd-create-dataset-adapter":2109},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":26,"repoUrl":27,"updatedAt":28,"license":29,"forks":30,"topics":31,"repo":39,"sourceUrl":42,"mdContent":43},"physicsnemo-cfd-create-dataset-adapter","create dataset adapters for CFD benchmarking","Create a new dataset adapter for the PhysicsNeMo CFD benchmarking workflow. Use when the user wants to add a new CFD dataset, write a DatasetAdapter, integrate a new mesh format, or benchmark models on custom data.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},"nvidia","NVIDIA","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fnvidia.png",[12,16,19,20,23],{"name":13,"slug":14,"type":15},"Datasets","datasets","tag",{"name":17,"slug":18,"type":15},"Benchmarking","benchmarking",{"name":9,"slug":8,"type":15},{"name":21,"slug":22,"type":15},"Simulation","simulation",{"name":24,"slug":25,"type":15},"Physics","physics",131,"https:\u002F\u002Fgithub.com\u002FNVIDIA\u002Fphysicsnemo-cfd","2026-07-14T05:33:35.569433","Apache-2.0",24,[32,33,34,35,36,8,37,38],"aerodynamics","cae","cfd","fluid-dynamics","nim","nvidia-warp","physicsnemo",{"repoUrl":27,"stars":26,"forks":30,"topics":40,"description":41},[32,33,34,35,36,8,37,38],"L​ibrary for using the models trained in PhysicsNeMo in Engineering and CFD workflows ","https:\u002F\u002Fgithub.com\u002FNVIDIA\u002Fphysicsnemo-cfd\u002Ftree\u002FHEAD\u002Fskills\u002Fphysicsnemo-cfd-create-dataset-adapter","---\nname: physicsnemo-cfd-create-dataset-adapter\ndescription: >-\n  Create a new dataset adapter for the PhysicsNeMo CFD benchmarking workflow.\n  Use when the user wants to add a new CFD dataset, write a DatasetAdapter,\n  integrate a new mesh format, or benchmark models on custom data.\nlicense: Apache-2.0\n---\n\n# Create a Dataset Adapter\n\nGuide the user through adding a new CFD dataset to the benchmarking\nworkflow by writing a `DatasetAdapter` subclass.\n\n## Reference files to read first\n\nBefore starting, read these files for context:\n\n- `physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fadapter_registry.py` — base class and registry\n- `physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fschema.py` — `CanonicalCase` and `build_predictions_dict`\n- `physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fadapters\u002Fdrivaerml.py` —\n  reference adapter implementation\n- `workflows\u002Fbenchmarking\u002Fnotebooks\u002Fadding_a_new_dataset.ipynb` —\n  end-to-end tutorial (writes a DrivAerStar adapter: format conversion,\n  field renaming, WSS sign flip, STL creation)\n\n## Step 1: Explore the new dataset\n\nAsk the user for the dataset path, then inspect one file. Report not just\narray *names* but their component count, dtype, and value range, plus\n`mesh.bounds` and any geometry arrays — the decision table below needs\nall of these:\n\n```python\nimport numpy as np\nimport pyvista as pv\n\nmesh = pv.read(\"\u003Cpath_to_one_file>\")\nprint(f\"Type: {type(mesh).__name__}, Points: {mesh.n_points}, Cells: {mesh.n_cells}\")\nprint(f\"Bounds (xmin,xmax,ymin,ymax,zmin,zmax): {mesh.bounds}\")\nfor loc, data in [(\"cell\", mesh.cell_data), (\"point\", mesh.point_data)]:\n    for name in data.keys():\n        arr = np.asarray(data[name])\n        comps = arr.shape[1] if arr.ndim > 1 else 1\n        print(f\"  [{loc}] {name}: comps={comps}, dtype={arr.dtype}, \"\n              f\"range=({arr.min():.3g}, {arr.max():.3g})\")\n# Explicit geometry arrays some datasets ship (DrivAerML has none):\nprint(\"Has Normals:\", \"Normals\" in mesh.cell_data or \"Normals\" in mesh.point_data)\nprint(\"Has Area:\", \"Area\" in mesh.cell_data or \"Area\" in mesh.point_data)\n```\n\nIdentify these differences from the canonical schema:\n\n| Question | What to look for |\n|----------|-----------------|\n| File format | `.vtp`, `.vtu`, `.vtk`, or a non-VTK format (CGNS, OpenFOAM, HDF5, CSV, ...)? Model wrappers ultimately read `.vtp` (surface) or `.vtu` (volume) XML — see \"Reading non-PyVista source formats\". |\n| Directory layout | Flat directory? Nested `run_\u003Cid>\u002F` dirs? How are case IDs derived from filenames? |\n| Pressure field name | The canonical key is `pressure`. What is the VTK array name? |\n| WSS field name | The canonical key is `shear_stress` (N, 3). Is it a single vector or separate scalar components? |\n| Sign conventions | Compare field ranges with DrivAerML. Are normals, WSS, or pressure flipped? |\n| Extra arrays | Are there explicit `Normals` or `Area` arrays? DrivAerML has none — remove them if present. |\n| STL files | Are separate STL geometry files available? If not, the surface mesh itself is the geometry. |\n| Coordinate frame & scale | Compare `mesh.bounds` and units against the training dataset. Matters only for geometry-referenced checkpoints (e.g. DrivAerML-trained). See \"Match geometry orientation and scale\". |\n| Inference domain | Surface (`.vtp`) or volume (`.vtu`)? |\n\n## Step 2: Write the adapter class\n\nSubclass `DatasetAdapter` with these methods:\n\n```python\nfrom pathlib import Path\nfrom physicsnemo.cfd.evaluation.datasets.adapter_registry import DatasetAdapter, register_adapter\nfrom physicsnemo.cfd.evaluation.datasets.schema import CanonicalCase\n\nclass MyDatasetAdapter(DatasetAdapter):\n    def __init__(self, root: str, **kwargs):\n        self._root = Path(root)\n\n    @classmethod\n    def inference_domain_from_kwargs(cls, kwargs=None):\n        return \"surface\"  # or \"volume\"\n\n    def list_cases(self):\n        # Return list of case ID strings\n        ...\n\n    def load_case(self, case_id: str) -> CanonicalCase:\n        # 1. Read the mesh file\n        # 2. Build ground_truth dict with canonical keys:\n        #    - \"pressure\": np.float32 array\n        #    - \"shear_stress\": np.float32 array of shape (N, 3)\n        #    For volume: \"pressure\", \"velocity\" (N,3), \"turbulent_viscosity\"\n        # 3. Return CanonicalCase(case_id, mesh_path, mesh_type, ground_truth, inference_domain)\n        ...\n```\n\n### Map source arrays to canonical keys\n\n`ground_truth` must use the framework's canonical keys, but source files\nrarely use those names. The canonical vocabulary (see `schema.py` \u002F\n`build_predictions_dict`) is:\n\n| Canonical key | Shape | Domain |\n|---|---|---|\n| `pressure` | (N,) | surface, volume |\n| `shear_stress` | (N, 3) | surface |\n| `velocity` | (N, 3) | volume |\n| `turbulent_viscosity` | (N,) | volume |\n\nBuild an explicit rename map from the source names you found in Step 1:\n\n```python\nRENAME = {\"pMean\": \"pressure\", \"wallShearStress\": \"shear_stress\"}\nground_truth = {\n    canon: np.asarray(mesh.cell_data[src], dtype=np.float32)\n    for src, canon in RENAME.items()\n}\n```\n\nWhen names are ambiguous, disambiguate by: component count (a 3-comp\nfield is `velocity` or `shear_stress`), dtype\u002Fvalue range, and —\ndecisively — **what the model's training data called each field** (see\n\"Why conventions must match the training data\"). Do not confuse this\nsource→canonical map with the separate canonical→VTK-name map in\n`output.mesh_field_names` (Step 4), which controls the *written* arrays.\n\n### Common transformations in `load_case`\n\n**Reading non-PyVista source formats**: `pv.read` handles VTK-family\nfiles, but CFD ground truth often ships as CGNS, OpenFOAM cases, Ensight,\nTecplot, HDF5\u002F`.npz`, or CSV point clouds. Only *reading* changes — the\ntarget is still a canonical `.vtp`\u002F`.vtu` mesh plus a `ground_truth`\ndict:\n\n```python\n# meshio covers many formats (CGNS, Ensight, ...); wrap to PyVista:\nimport meshio, pyvista as pv\nmesh = pv.wrap(meshio.read(src_path))\n\n# OpenFOAM case directory:\nmesh = pv.OpenFOAMReader(case_foam_file).read()\n\n# Raw arrays (HDF5 \u002F npz \u002F CSV): build the mesh, then attach fields:\ncloud = pv.PolyData(points_xyz)          # (N, 3) float array\ncloud[\"pressure\"] = p_values             # attach source arrays\n```\n\n**Format conversion** (legacy `.vtk` → `.vtp`):\n\n```python\nmesh = pv.read(vtk_path).extract_surface()\nmesh.save(vtp_path)\n```\n\n**Combining separate WSS scalars into a vector:**\n\n```python\nwss = np.stack([mesh.cell_data[\"WSSx\"], mesh.cell_data[\"WSSy\"], mesh.cell_data[\"WSSz\"]], axis=1)\n```\n\n**Removing explicit Normals\u002FArea** (DrivAerML convention):\n\n```python\nfor key in [\"Normals\", \"Area\"]:\n    if key in mesh.cell_data:\n        del mesh.cell_data[key]\n```\n\n**Creating STL from surface mesh** (when no STL is shipped):\n\n```python\nmesh.extract_surface().triangulate().save(stl_path)\n```\n\nThe STL must be named `drivaer_{int(case_id)}.stl` in the same directory\nas the VTP for the model wrappers to find it.\n\n### Match geometry orientation and scale\n\nGeometry-referenced models (e.g. DoMINO) normalize the mesh\u002FSTL\ncoordinates against a **fixed bounding box baked into the checkpoint\nfrom its training dataset**: DoMINO reads\n`cfg.data.bounding_box_surface.min\u002Fmax` (and `bounding_box.min\u002Fmax` for\nvolume) and maps every coordinate into that box. If the new dataset's\ngeometry sits in a different frame, origin, or unit scale, it lands in\nthe wrong normalized space — predictions are wrong even when field names\nand signs are correct.\n\n**This only matters when the checkpoint was trained on a specific\ngeometry-referenced dataset (e.g. DrivAerML).** For\nscale\u002Ftranslation-invariant models, or when the model was trained on\nthis same dataset, skip it.\n\nMatch three things to the training dataset (DrivAerML reference bounds\nbelow, in **meters**, from the DoMINO config):\n\n| Box | min (x, y, z) | max (x, y, z) |\n|---|---|---|\n| Surface | -1.5, -1.4, -0.32 | 5.0, 1.4, 1.4 |\n| Volume | -3.5, -2.25, -0.32 | 8.5, 2.25, 3.00 |\n\n- **Orientation \u002F axes**: same convention — x streamwise (length), y\n  width, z up. Permute or rotate if the new data uses a different\n  up-axis or flipped sign.\n- **Origin \u002F position**: the bounding box should *start* near the same\n  (x, y, z) minimum, so the geometry falls inside the model's domain\n  box.\n- **Scale \u002F units**: extents must be the same order of magnitude.\n  Millimetre data must be scaled to meters (×0.001).\n\nCheck `mesh.bounds` and transform in `load_case` **before** saving the prepared VTP\u002FSTL:\n\n```python\nb = mesh.bounds  # (xmin, xmax, ymin, ymax, zmin, zmax)\n# ~1000x larger extents => mm; scale to meters. A swapped axis range => reorient.\nmesh.points *= 0.001\nmesh.points += np.array([x_off, y_off, z_off], dtype=np.float32)  # translate to match origin\n```\n\n### Caching pattern\n\nDo expensive conversions lazily and cache:\n\n```python\ndef _prepare_case(self, case_id):\n    prepared_path = self._root \u002F \"_prepared\" \u002F f\"{case_id}.vtp\"\n    if not prepared_path.exists():\n        # ... convert and save\n    return str(prepared_path)\n```\n\n## Step 3: Register and test\n\n```python\nregister_adapter(\"my_dataset\", MyDatasetAdapter)\n\nadapter = MyDatasetAdapter(root=\"\u002Fpath\u002Fto\u002Fdata\")\ncases = adapter.list_cases()\ncase = adapter.load_case(cases[0])\nassert case.ground_truth is not None\nassert \"pressure\" in case.ground_truth\n```\n\n## Step 4: Run inference and benchmark\n\nBuild a config and run:\n\n```python\nfrom physicsnemo.cfd.evaluation.config import Config\nfrom physicsnemo.cfd.evaluation.benchmarks.engine import run_benchmark\n\nconfig = Config.from_dict({\n    \"run\": {\"device\": \"cuda:0\", \"output_dir\": \"results\"},\n    \"model\": {\"name\": \"\u003Cmodel_name>\", \"inference_domain\": \"\u003Csurface|volume>\", ...},\n    \"dataset\": {\"name\": \"my_dataset\", \"root\": \"\u002Fpath\u002Fto\u002Fdata\", \"case_ids\": cases[:2]},\n    \"output\": {\n        \"ground_truth_mesh_field_names\": {\"pressure\": \"\u003Cvtk_gt_name>\", \"shear_stress\": \"\u003Cvtk_gt_name>\"},\n        \"mesh_field_names\": {\"pressure\": \"\u003Cvtk_pred_name>\", \"shear_stress\": \"\u003Cvtk_pred_name>\"},\n    },\n    \"metrics\": [\"l2_pressure\", \"l2_shear_stress\", \"drag\", \"lift\"],\n    \"reports\": {\"enabled\": False},\n})\nresults = run_benchmark(config)\n```\n\n## Step 5: Make permanent (optional)\n\nSave the adapter to\n`physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fadapters\u002F\u003Cname>.py` and register in\n`adapters\u002F__init__.py`:\n\n```python\nfrom physicsnemo.cfd.evaluation.datasets.adapters.\u003Cname> import MyDatasetAdapter\nregister_adapter(\"my_dataset\", MyDatasetAdapter)\n```\n\n## Why conventions must match the training data\n\nThe field name mappings, sign conventions, and format conversions in the\nadapter exist because the model checkpoint was trained on a specific\ndataset (e.g., DrivAerML) with specific conventions. The adapter bridges\nthe gap between the new dataset's conventions and the training data's\nconventions — not some abstract standard. If a model is retrained\ndirectly on the new dataset, the adapter would not need these\ntransformations. When writing an adapter, always ask: \"What conventions\ndid the model's training data use?\" and map to those.\n\n## Gotchas\n\n- **DistributedManager**: Model wrappers call\n  `DistributedManager.initialize()`. In notebooks without `torchrun`,\n  set env vars first: `WORLD_SIZE=1`, `RANK=0`, `LOCAL_RANK=0`,\n  `MASTER_ADDR=localhost`, `MASTER_PORT=12355`.\n- **STL naming**: DoMINO looks for `drivaer_{tag}.stl`, GeoTransolver\n  looks for `drivaer_{tag}_single_solid.stl` then `*.stl`. Both now fall\n  back to any `*.stl` in the directory.\n- **VTP vs VTK**: Model wrappers use VTK XML readers internally. Legacy\n  `.vtk` files must be converted to `.vtp`\u002F`.vtu`.\n- **Checkpoint loading**: Some wrappers need\n  `trusted_torch_load_context()` for PyTorch 2.6+ checkpoint\n  compatibility.\n- **Domain-scoped metrics**: `l2_pressure` resolves to different\n  implementations for surface vs volume based on `inference_domain`. Use\n  the same metric name for both.\n- **Geometry frame**: geometry-referenced checkpoints (DoMINO) assume\n  the training dataset's coordinate frame and scale. A mm-vs-m or\n  flipped-axis mismatch produces wrong predictions with no error raised.\n  See \"Match geometry orientation and scale\".\n",{"data":45,"body":46},{"name":4,"description":6,"license":29},{"type":47,"children":48},"root",[49,58,73,80,85,148,154,175,324,329,569,575,587,789,796,822,932,937,984,1025,1037,1091,1176,1200,1223,1231,1245,1255,1286,1296,1310,1323,1329,1357,1367,1379,1442,1482,1508,1547,1553,1558,1605,1611,1673,1679,1684,1810,1816,1837,1859,1865,1870,1876,2065],{"type":50,"tag":51,"props":52,"children":54},"element","h1",{"id":53},"create-a-dataset-adapter",[55],{"type":56,"value":57},"text","Create a Dataset Adapter",{"type":50,"tag":59,"props":60,"children":61},"p",{},[62,64,71],{"type":56,"value":63},"Guide the user through adding a new CFD dataset to the benchmarking\nworkflow by writing a ",{"type":50,"tag":65,"props":66,"children":68},"code",{"className":67},[],[69],{"type":56,"value":70},"DatasetAdapter",{"type":56,"value":72}," subclass.",{"type":50,"tag":74,"props":75,"children":77},"h2",{"id":76},"reference-files-to-read-first",[78],{"type":56,"value":79},"Reference files to read first",{"type":50,"tag":59,"props":81,"children":82},{},[83],{"type":56,"value":84},"Before starting, read these files for context:",{"type":50,"tag":86,"props":87,"children":88},"ul",{},[89,101,126,137],{"type":50,"tag":90,"props":91,"children":92},"li",{},[93,99],{"type":50,"tag":65,"props":94,"children":96},{"className":95},[],[97],{"type":56,"value":98},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fadapter_registry.py",{"type":56,"value":100}," — base class and registry",{"type":50,"tag":90,"props":102,"children":103},{},[104,110,112,118,120],{"type":50,"tag":65,"props":105,"children":107},{"className":106},[],[108],{"type":56,"value":109},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fschema.py",{"type":56,"value":111}," — ",{"type":50,"tag":65,"props":113,"children":115},{"className":114},[],[116],{"type":56,"value":117},"CanonicalCase",{"type":56,"value":119}," and ",{"type":50,"tag":65,"props":121,"children":123},{"className":122},[],[124],{"type":56,"value":125},"build_predictions_dict",{"type":50,"tag":90,"props":127,"children":128},{},[129,135],{"type":50,"tag":65,"props":130,"children":132},{"className":131},[],[133],{"type":56,"value":134},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fadapters\u002Fdrivaerml.py",{"type":56,"value":136}," —\nreference adapter implementation",{"type":50,"tag":90,"props":138,"children":139},{},[140,146],{"type":50,"tag":65,"props":141,"children":143},{"className":142},[],[144],{"type":56,"value":145},"workflows\u002Fbenchmarking\u002Fnotebooks\u002Fadding_a_new_dataset.ipynb",{"type":56,"value":147}," —\nend-to-end tutorial (writes a DrivAerStar adapter: format conversion,\nfield renaming, WSS sign flip, STL creation)",{"type":50,"tag":74,"props":149,"children":151},{"id":150},"step-1-explore-the-new-dataset",[152],{"type":56,"value":153},"Step 1: Explore the new dataset",{"type":50,"tag":59,"props":155,"children":156},{},[157,159,165,167,173],{"type":56,"value":158},"Ask the user for the dataset path, then inspect one file. Report not just\narray ",{"type":50,"tag":160,"props":161,"children":162},"em",{},[163],{"type":56,"value":164},"names",{"type":56,"value":166}," but their component count, dtype, and value range, plus\n",{"type":50,"tag":65,"props":168,"children":170},{"className":169},[],[171],{"type":56,"value":172},"mesh.bounds",{"type":56,"value":174}," and any geometry arrays — the decision table below needs\nall of these:",{"type":50,"tag":176,"props":177,"children":182},"pre",{"className":178,"code":179,"language":180,"meta":181,"style":181},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import numpy as np\nimport pyvista as pv\n\nmesh = pv.read(\"\u003Cpath_to_one_file>\")\nprint(f\"Type: {type(mesh).__name__}, Points: {mesh.n_points}, Cells: {mesh.n_cells}\")\nprint(f\"Bounds (xmin,xmax,ymin,ymax,zmin,zmax): {mesh.bounds}\")\nfor loc, data in [(\"cell\", mesh.cell_data), (\"point\", mesh.point_data)]:\n    for name in data.keys():\n        arr = np.asarray(data[name])\n        comps = arr.shape[1] if arr.ndim > 1 else 1\n        print(f\"  [{loc}] {name}: comps={comps}, dtype={arr.dtype}, \"\n              f\"range=({arr.min():.3g}, {arr.max():.3g})\")\n# Explicit geometry arrays some datasets ship (DrivAerML has none):\nprint(\"Has Normals:\", \"Normals\" in mesh.cell_data or \"Normals\" in mesh.point_data)\nprint(\"Has Area:\", \"Area\" in mesh.cell_data or \"Area\" in mesh.point_data)\n","python","",[183],{"type":50,"tag":65,"props":184,"children":185},{"__ignoreMap":181},[186,197,206,216,225,234,243,252,261,270,279,288,297,306,315],{"type":50,"tag":187,"props":188,"children":191},"span",{"class":189,"line":190},"line",1,[192],{"type":50,"tag":187,"props":193,"children":194},{},[195],{"type":56,"value":196},"import numpy as np\n",{"type":50,"tag":187,"props":198,"children":200},{"class":189,"line":199},2,[201],{"type":50,"tag":187,"props":202,"children":203},{},[204],{"type":56,"value":205},"import pyvista as pv\n",{"type":50,"tag":187,"props":207,"children":209},{"class":189,"line":208},3,[210],{"type":50,"tag":187,"props":211,"children":213},{"emptyLinePlaceholder":212},true,[214],{"type":56,"value":215},"\n",{"type":50,"tag":187,"props":217,"children":219},{"class":189,"line":218},4,[220],{"type":50,"tag":187,"props":221,"children":222},{},[223],{"type":56,"value":224},"mesh = pv.read(\"\u003Cpath_to_one_file>\")\n",{"type":50,"tag":187,"props":226,"children":228},{"class":189,"line":227},5,[229],{"type":50,"tag":187,"props":230,"children":231},{},[232],{"type":56,"value":233},"print(f\"Type: {type(mesh).__name__}, Points: {mesh.n_points}, Cells: {mesh.n_cells}\")\n",{"type":50,"tag":187,"props":235,"children":237},{"class":189,"line":236},6,[238],{"type":50,"tag":187,"props":239,"children":240},{},[241],{"type":56,"value":242},"print(f\"Bounds (xmin,xmax,ymin,ymax,zmin,zmax): {mesh.bounds}\")\n",{"type":50,"tag":187,"props":244,"children":246},{"class":189,"line":245},7,[247],{"type":50,"tag":187,"props":248,"children":249},{},[250],{"type":56,"value":251},"for loc, data in [(\"cell\", mesh.cell_data), (\"point\", mesh.point_data)]:\n",{"type":50,"tag":187,"props":253,"children":255},{"class":189,"line":254},8,[256],{"type":50,"tag":187,"props":257,"children":258},{},[259],{"type":56,"value":260},"    for name in data.keys():\n",{"type":50,"tag":187,"props":262,"children":264},{"class":189,"line":263},9,[265],{"type":50,"tag":187,"props":266,"children":267},{},[268],{"type":56,"value":269},"        arr = np.asarray(data[name])\n",{"type":50,"tag":187,"props":271,"children":273},{"class":189,"line":272},10,[274],{"type":50,"tag":187,"props":275,"children":276},{},[277],{"type":56,"value":278},"        comps = arr.shape[1] if arr.ndim > 1 else 1\n",{"type":50,"tag":187,"props":280,"children":282},{"class":189,"line":281},11,[283],{"type":50,"tag":187,"props":284,"children":285},{},[286],{"type":56,"value":287},"        print(f\"  [{loc}] {name}: comps={comps}, dtype={arr.dtype}, \"\n",{"type":50,"tag":187,"props":289,"children":291},{"class":189,"line":290},12,[292],{"type":50,"tag":187,"props":293,"children":294},{},[295],{"type":56,"value":296},"              f\"range=({arr.min():.3g}, {arr.max():.3g})\")\n",{"type":50,"tag":187,"props":298,"children":300},{"class":189,"line":299},13,[301],{"type":50,"tag":187,"props":302,"children":303},{},[304],{"type":56,"value":305},"# Explicit geometry arrays some datasets ship (DrivAerML has none):\n",{"type":50,"tag":187,"props":307,"children":309},{"class":189,"line":308},14,[310],{"type":50,"tag":187,"props":311,"children":312},{},[313],{"type":56,"value":314},"print(\"Has Normals:\", \"Normals\" in mesh.cell_data or \"Normals\" in mesh.point_data)\n",{"type":50,"tag":187,"props":316,"children":318},{"class":189,"line":317},15,[319],{"type":50,"tag":187,"props":320,"children":321},{},[322],{"type":56,"value":323},"print(\"Has Area:\", \"Area\" in mesh.cell_data or \"Area\" in mesh.point_data)\n",{"type":50,"tag":59,"props":325,"children":326},{},[327],{"type":56,"value":328},"Identify these differences from the canonical schema:",{"type":50,"tag":330,"props":331,"children":332},"table",{},[333,352],{"type":50,"tag":334,"props":335,"children":336},"thead",{},[337],{"type":50,"tag":338,"props":339,"children":340},"tr",{},[341,347],{"type":50,"tag":342,"props":343,"children":344},"th",{},[345],{"type":56,"value":346},"Question",{"type":50,"tag":342,"props":348,"children":349},{},[350],{"type":56,"value":351},"What to look for",{"type":50,"tag":353,"props":354,"children":355},"tbody",{},[356,405,426,447,467,480,509,522,542],{"type":50,"tag":338,"props":357,"children":358},{},[359,365],{"type":50,"tag":360,"props":361,"children":362},"td",{},[363],{"type":56,"value":364},"File format",{"type":50,"tag":360,"props":366,"children":367},{},[368,374,376,382,383,389,391,396,398,403],{"type":50,"tag":65,"props":369,"children":371},{"className":370},[],[372],{"type":56,"value":373},".vtp",{"type":56,"value":375},", ",{"type":50,"tag":65,"props":377,"children":379},{"className":378},[],[380],{"type":56,"value":381},".vtu",{"type":56,"value":375},{"type":50,"tag":65,"props":384,"children":386},{"className":385},[],[387],{"type":56,"value":388},".vtk",{"type":56,"value":390},", or a non-VTK format (CGNS, OpenFOAM, HDF5, CSV, ...)? Model wrappers ultimately read ",{"type":50,"tag":65,"props":392,"children":394},{"className":393},[],[395],{"type":56,"value":373},{"type":56,"value":397}," (surface) or ",{"type":50,"tag":65,"props":399,"children":401},{"className":400},[],[402],{"type":56,"value":381},{"type":56,"value":404}," (volume) XML — see \"Reading non-PyVista source formats\".",{"type":50,"tag":338,"props":406,"children":407},{},[408,413],{"type":50,"tag":360,"props":409,"children":410},{},[411],{"type":56,"value":412},"Directory layout",{"type":50,"tag":360,"props":414,"children":415},{},[416,418,424],{"type":56,"value":417},"Flat directory? Nested ",{"type":50,"tag":65,"props":419,"children":421},{"className":420},[],[422],{"type":56,"value":423},"run_\u003Cid>\u002F",{"type":56,"value":425}," dirs? How are case IDs derived from filenames?",{"type":50,"tag":338,"props":427,"children":428},{},[429,434],{"type":50,"tag":360,"props":430,"children":431},{},[432],{"type":56,"value":433},"Pressure field name",{"type":50,"tag":360,"props":435,"children":436},{},[437,439,445],{"type":56,"value":438},"The canonical key is ",{"type":50,"tag":65,"props":440,"children":442},{"className":441},[],[443],{"type":56,"value":444},"pressure",{"type":56,"value":446},". What is the VTK array name?",{"type":50,"tag":338,"props":448,"children":449},{},[450,455],{"type":50,"tag":360,"props":451,"children":452},{},[453],{"type":56,"value":454},"WSS field name",{"type":50,"tag":360,"props":456,"children":457},{},[458,459,465],{"type":56,"value":438},{"type":50,"tag":65,"props":460,"children":462},{"className":461},[],[463],{"type":56,"value":464},"shear_stress",{"type":56,"value":466}," (N, 3). Is it a single vector or separate scalar components?",{"type":50,"tag":338,"props":468,"children":469},{},[470,475],{"type":50,"tag":360,"props":471,"children":472},{},[473],{"type":56,"value":474},"Sign conventions",{"type":50,"tag":360,"props":476,"children":477},{},[478],{"type":56,"value":479},"Compare field ranges with DrivAerML. Are normals, WSS, or pressure flipped?",{"type":50,"tag":338,"props":481,"children":482},{},[483,488],{"type":50,"tag":360,"props":484,"children":485},{},[486],{"type":56,"value":487},"Extra arrays",{"type":50,"tag":360,"props":489,"children":490},{},[491,493,499,501,507],{"type":56,"value":492},"Are there explicit ",{"type":50,"tag":65,"props":494,"children":496},{"className":495},[],[497],{"type":56,"value":498},"Normals",{"type":56,"value":500}," or ",{"type":50,"tag":65,"props":502,"children":504},{"className":503},[],[505],{"type":56,"value":506},"Area",{"type":56,"value":508}," arrays? DrivAerML has none — remove them if present.",{"type":50,"tag":338,"props":510,"children":511},{},[512,517],{"type":50,"tag":360,"props":513,"children":514},{},[515],{"type":56,"value":516},"STL files",{"type":50,"tag":360,"props":518,"children":519},{},[520],{"type":56,"value":521},"Are separate STL geometry files available? If not, the surface mesh itself is the geometry.",{"type":50,"tag":338,"props":523,"children":524},{},[525,530],{"type":50,"tag":360,"props":526,"children":527},{},[528],{"type":56,"value":529},"Coordinate frame & scale",{"type":50,"tag":360,"props":531,"children":532},{},[533,535,540],{"type":56,"value":534},"Compare ",{"type":50,"tag":65,"props":536,"children":538},{"className":537},[],[539],{"type":56,"value":172},{"type":56,"value":541}," and units against the training dataset. Matters only for geometry-referenced checkpoints (e.g. DrivAerML-trained). See \"Match geometry orientation and scale\".",{"type":50,"tag":338,"props":543,"children":544},{},[545,550],{"type":50,"tag":360,"props":546,"children":547},{},[548],{"type":56,"value":549},"Inference domain",{"type":50,"tag":360,"props":551,"children":552},{},[553,555,560,562,567],{"type":56,"value":554},"Surface (",{"type":50,"tag":65,"props":556,"children":558},{"className":557},[],[559],{"type":56,"value":373},{"type":56,"value":561},") or volume (",{"type":50,"tag":65,"props":563,"children":565},{"className":564},[],[566],{"type":56,"value":381},{"type":56,"value":568},")?",{"type":50,"tag":74,"props":570,"children":572},{"id":571},"step-2-write-the-adapter-class",[573],{"type":56,"value":574},"Step 2: Write the adapter class",{"type":50,"tag":59,"props":576,"children":577},{},[578,580,585],{"type":56,"value":579},"Subclass ",{"type":50,"tag":65,"props":581,"children":583},{"className":582},[],[584],{"type":56,"value":70},{"type":56,"value":586}," with these methods:",{"type":50,"tag":176,"props":588,"children":590},{"className":178,"code":589,"language":180,"meta":181,"style":181},"from pathlib import Path\nfrom physicsnemo.cfd.evaluation.datasets.adapter_registry import DatasetAdapter, register_adapter\nfrom physicsnemo.cfd.evaluation.datasets.schema import CanonicalCase\n\nclass MyDatasetAdapter(DatasetAdapter):\n    def __init__(self, root: str, **kwargs):\n        self._root = Path(root)\n\n    @classmethod\n    def inference_domain_from_kwargs(cls, kwargs=None):\n        return \"surface\"  # or \"volume\"\n\n    def list_cases(self):\n        # Return list of case ID strings\n        ...\n\n    def load_case(self, case_id: str) -> CanonicalCase:\n        # 1. Read the mesh file\n        # 2. Build ground_truth dict with canonical keys:\n        #    - \"pressure\": np.float32 array\n        #    - \"shear_stress\": np.float32 array of shape (N, 3)\n        #    For volume: \"pressure\", \"velocity\" (N,3), \"turbulent_viscosity\"\n        # 3. Return CanonicalCase(case_id, mesh_path, mesh_type, ground_truth, inference_domain)\n        ...\n",[591],{"type":50,"tag":65,"props":592,"children":593},{"__ignoreMap":181},[594,602,610,618,625,633,641,649,656,664,672,680,687,695,703,711,719,728,737,746,755,764,773,782],{"type":50,"tag":187,"props":595,"children":596},{"class":189,"line":190},[597],{"type":50,"tag":187,"props":598,"children":599},{},[600],{"type":56,"value":601},"from pathlib import Path\n",{"type":50,"tag":187,"props":603,"children":604},{"class":189,"line":199},[605],{"type":50,"tag":187,"props":606,"children":607},{},[608],{"type":56,"value":609},"from physicsnemo.cfd.evaluation.datasets.adapter_registry import DatasetAdapter, register_adapter\n",{"type":50,"tag":187,"props":611,"children":612},{"class":189,"line":208},[613],{"type":50,"tag":187,"props":614,"children":615},{},[616],{"type":56,"value":617},"from physicsnemo.cfd.evaluation.datasets.schema import CanonicalCase\n",{"type":50,"tag":187,"props":619,"children":620},{"class":189,"line":218},[621],{"type":50,"tag":187,"props":622,"children":623},{"emptyLinePlaceholder":212},[624],{"type":56,"value":215},{"type":50,"tag":187,"props":626,"children":627},{"class":189,"line":227},[628],{"type":50,"tag":187,"props":629,"children":630},{},[631],{"type":56,"value":632},"class MyDatasetAdapter(DatasetAdapter):\n",{"type":50,"tag":187,"props":634,"children":635},{"class":189,"line":236},[636],{"type":50,"tag":187,"props":637,"children":638},{},[639],{"type":56,"value":640},"    def __init__(self, root: str, **kwargs):\n",{"type":50,"tag":187,"props":642,"children":643},{"class":189,"line":245},[644],{"type":50,"tag":187,"props":645,"children":646},{},[647],{"type":56,"value":648},"        self._root = Path(root)\n",{"type":50,"tag":187,"props":650,"children":651},{"class":189,"line":254},[652],{"type":50,"tag":187,"props":653,"children":654},{"emptyLinePlaceholder":212},[655],{"type":56,"value":215},{"type":50,"tag":187,"props":657,"children":658},{"class":189,"line":263},[659],{"type":50,"tag":187,"props":660,"children":661},{},[662],{"type":56,"value":663},"    @classmethod\n",{"type":50,"tag":187,"props":665,"children":666},{"class":189,"line":272},[667],{"type":50,"tag":187,"props":668,"children":669},{},[670],{"type":56,"value":671},"    def inference_domain_from_kwargs(cls, kwargs=None):\n",{"type":50,"tag":187,"props":673,"children":674},{"class":189,"line":281},[675],{"type":50,"tag":187,"props":676,"children":677},{},[678],{"type":56,"value":679},"        return \"surface\"  # or \"volume\"\n",{"type":50,"tag":187,"props":681,"children":682},{"class":189,"line":290},[683],{"type":50,"tag":187,"props":684,"children":685},{"emptyLinePlaceholder":212},[686],{"type":56,"value":215},{"type":50,"tag":187,"props":688,"children":689},{"class":189,"line":299},[690],{"type":50,"tag":187,"props":691,"children":692},{},[693],{"type":56,"value":694},"    def list_cases(self):\n",{"type":50,"tag":187,"props":696,"children":697},{"class":189,"line":308},[698],{"type":50,"tag":187,"props":699,"children":700},{},[701],{"type":56,"value":702},"        # Return list of case ID strings\n",{"type":50,"tag":187,"props":704,"children":705},{"class":189,"line":317},[706],{"type":50,"tag":187,"props":707,"children":708},{},[709],{"type":56,"value":710},"        ...\n",{"type":50,"tag":187,"props":712,"children":714},{"class":189,"line":713},16,[715],{"type":50,"tag":187,"props":716,"children":717},{"emptyLinePlaceholder":212},[718],{"type":56,"value":215},{"type":50,"tag":187,"props":720,"children":722},{"class":189,"line":721},17,[723],{"type":50,"tag":187,"props":724,"children":725},{},[726],{"type":56,"value":727},"    def load_case(self, case_id: str) -> CanonicalCase:\n",{"type":50,"tag":187,"props":729,"children":731},{"class":189,"line":730},18,[732],{"type":50,"tag":187,"props":733,"children":734},{},[735],{"type":56,"value":736},"        # 1. Read the mesh file\n",{"type":50,"tag":187,"props":738,"children":740},{"class":189,"line":739},19,[741],{"type":50,"tag":187,"props":742,"children":743},{},[744],{"type":56,"value":745},"        # 2. Build ground_truth dict with canonical keys:\n",{"type":50,"tag":187,"props":747,"children":749},{"class":189,"line":748},20,[750],{"type":50,"tag":187,"props":751,"children":752},{},[753],{"type":56,"value":754},"        #    - \"pressure\": np.float32 array\n",{"type":50,"tag":187,"props":756,"children":758},{"class":189,"line":757},21,[759],{"type":50,"tag":187,"props":760,"children":761},{},[762],{"type":56,"value":763},"        #    - \"shear_stress\": np.float32 array of shape (N, 3)\n",{"type":50,"tag":187,"props":765,"children":767},{"class":189,"line":766},22,[768],{"type":50,"tag":187,"props":769,"children":770},{},[771],{"type":56,"value":772},"        #    For volume: \"pressure\", \"velocity\" (N,3), \"turbulent_viscosity\"\n",{"type":50,"tag":187,"props":774,"children":776},{"class":189,"line":775},23,[777],{"type":50,"tag":187,"props":778,"children":779},{},[780],{"type":56,"value":781},"        # 3. Return CanonicalCase(case_id, mesh_path, mesh_type, ground_truth, inference_domain)\n",{"type":50,"tag":187,"props":783,"children":784},{"class":189,"line":30},[785],{"type":50,"tag":187,"props":786,"children":787},{},[788],{"type":56,"value":710},{"type":50,"tag":790,"props":791,"children":793},"h3",{"id":792},"map-source-arrays-to-canonical-keys",[794],{"type":56,"value":795},"Map source arrays to canonical keys",{"type":50,"tag":59,"props":797,"children":798},{},[799,805,807,813,815,820],{"type":50,"tag":65,"props":800,"children":802},{"className":801},[],[803],{"type":56,"value":804},"ground_truth",{"type":56,"value":806}," must use the framework's canonical keys, but source files\nrarely use those names. The canonical vocabulary (see ",{"type":50,"tag":65,"props":808,"children":810},{"className":809},[],[811],{"type":56,"value":812},"schema.py",{"type":56,"value":814}," \u002F\n",{"type":50,"tag":65,"props":816,"children":818},{"className":817},[],[819],{"type":56,"value":125},{"type":56,"value":821},") is:",{"type":50,"tag":330,"props":823,"children":824},{},[825,846],{"type":50,"tag":334,"props":826,"children":827},{},[828],{"type":50,"tag":338,"props":829,"children":830},{},[831,836,841],{"type":50,"tag":342,"props":832,"children":833},{},[834],{"type":56,"value":835},"Canonical key",{"type":50,"tag":342,"props":837,"children":838},{},[839],{"type":56,"value":840},"Shape",{"type":50,"tag":342,"props":842,"children":843},{},[844],{"type":56,"value":845},"Domain",{"type":50,"tag":353,"props":847,"children":848},{},[849,870,891,912],{"type":50,"tag":338,"props":850,"children":851},{},[852,860,865],{"type":50,"tag":360,"props":853,"children":854},{},[855],{"type":50,"tag":65,"props":856,"children":858},{"className":857},[],[859],{"type":56,"value":444},{"type":50,"tag":360,"props":861,"children":862},{},[863],{"type":56,"value":864},"(N,)",{"type":50,"tag":360,"props":866,"children":867},{},[868],{"type":56,"value":869},"surface, volume",{"type":50,"tag":338,"props":871,"children":872},{},[873,881,886],{"type":50,"tag":360,"props":874,"children":875},{},[876],{"type":50,"tag":65,"props":877,"children":879},{"className":878},[],[880],{"type":56,"value":464},{"type":50,"tag":360,"props":882,"children":883},{},[884],{"type":56,"value":885},"(N, 3)",{"type":50,"tag":360,"props":887,"children":888},{},[889],{"type":56,"value":890},"surface",{"type":50,"tag":338,"props":892,"children":893},{},[894,903,907],{"type":50,"tag":360,"props":895,"children":896},{},[897],{"type":50,"tag":65,"props":898,"children":900},{"className":899},[],[901],{"type":56,"value":902},"velocity",{"type":50,"tag":360,"props":904,"children":905},{},[906],{"type":56,"value":885},{"type":50,"tag":360,"props":908,"children":909},{},[910],{"type":56,"value":911},"volume",{"type":50,"tag":338,"props":913,"children":914},{},[915,924,928],{"type":50,"tag":360,"props":916,"children":917},{},[918],{"type":50,"tag":65,"props":919,"children":921},{"className":920},[],[922],{"type":56,"value":923},"turbulent_viscosity",{"type":50,"tag":360,"props":925,"children":926},{},[927],{"type":56,"value":864},{"type":50,"tag":360,"props":929,"children":930},{},[931],{"type":56,"value":911},{"type":50,"tag":59,"props":933,"children":934},{},[935],{"type":56,"value":936},"Build an explicit rename map from the source names you found in Step 1:",{"type":50,"tag":176,"props":938,"children":940},{"className":178,"code":939,"language":180,"meta":181,"style":181},"RENAME = {\"pMean\": \"pressure\", \"wallShearStress\": \"shear_stress\"}\nground_truth = {\n    canon: np.asarray(mesh.cell_data[src], dtype=np.float32)\n    for src, canon in RENAME.items()\n}\n",[941],{"type":50,"tag":65,"props":942,"children":943},{"__ignoreMap":181},[944,952,960,968,976],{"type":50,"tag":187,"props":945,"children":946},{"class":189,"line":190},[947],{"type":50,"tag":187,"props":948,"children":949},{},[950],{"type":56,"value":951},"RENAME = {\"pMean\": \"pressure\", \"wallShearStress\": \"shear_stress\"}\n",{"type":50,"tag":187,"props":953,"children":954},{"class":189,"line":199},[955],{"type":50,"tag":187,"props":956,"children":957},{},[958],{"type":56,"value":959},"ground_truth = {\n",{"type":50,"tag":187,"props":961,"children":962},{"class":189,"line":208},[963],{"type":50,"tag":187,"props":964,"children":965},{},[966],{"type":56,"value":967},"    canon: np.asarray(mesh.cell_data[src], dtype=np.float32)\n",{"type":50,"tag":187,"props":969,"children":970},{"class":189,"line":218},[971],{"type":50,"tag":187,"props":972,"children":973},{},[974],{"type":56,"value":975},"    for src, canon in RENAME.items()\n",{"type":50,"tag":187,"props":977,"children":978},{"class":189,"line":227},[979],{"type":50,"tag":187,"props":980,"children":981},{},[982],{"type":56,"value":983},"}\n",{"type":50,"tag":59,"props":985,"children":986},{},[987,989,994,995,1000,1002,1008,1010,1016,1018,1023],{"type":56,"value":988},"When names are ambiguous, disambiguate by: component count (a 3-comp\nfield is ",{"type":50,"tag":65,"props":990,"children":992},{"className":991},[],[993],{"type":56,"value":902},{"type":56,"value":500},{"type":50,"tag":65,"props":996,"children":998},{"className":997},[],[999],{"type":56,"value":464},{"type":56,"value":1001},"), dtype\u002Fvalue range, and —\ndecisively — ",{"type":50,"tag":1003,"props":1004,"children":1005},"strong",{},[1006],{"type":56,"value":1007},"what the model's training data called each field",{"type":56,"value":1009}," (see\n\"Why conventions must match the training data\"). Do not confuse this\nsource→canonical map with the separate canonical→VTK-name map in\n",{"type":50,"tag":65,"props":1011,"children":1013},{"className":1012},[],[1014],{"type":56,"value":1015},"output.mesh_field_names",{"type":56,"value":1017}," (Step 4), which controls the ",{"type":50,"tag":160,"props":1019,"children":1020},{},[1021],{"type":56,"value":1022},"written",{"type":56,"value":1024}," arrays.",{"type":50,"tag":790,"props":1026,"children":1028},{"id":1027},"common-transformations-in-load_case",[1029,1031],{"type":56,"value":1030},"Common transformations in ",{"type":50,"tag":65,"props":1032,"children":1034},{"className":1033},[],[1035],{"type":56,"value":1036},"load_case",{"type":50,"tag":59,"props":1038,"children":1039},{},[1040,1045,1047,1053,1055,1061,1063,1068,1070,1075,1077,1082,1084,1089],{"type":50,"tag":1003,"props":1041,"children":1042},{},[1043],{"type":56,"value":1044},"Reading non-PyVista source formats",{"type":56,"value":1046},": ",{"type":50,"tag":65,"props":1048,"children":1050},{"className":1049},[],[1051],{"type":56,"value":1052},"pv.read",{"type":56,"value":1054}," handles VTK-family\nfiles, but CFD ground truth often ships as CGNS, OpenFOAM cases, Ensight,\nTecplot, HDF5\u002F",{"type":50,"tag":65,"props":1056,"children":1058},{"className":1057},[],[1059],{"type":56,"value":1060},".npz",{"type":56,"value":1062},", or CSV point clouds. Only ",{"type":50,"tag":160,"props":1064,"children":1065},{},[1066],{"type":56,"value":1067},"reading",{"type":56,"value":1069}," changes — the\ntarget is still a canonical ",{"type":50,"tag":65,"props":1071,"children":1073},{"className":1072},[],[1074],{"type":56,"value":373},{"type":56,"value":1076},"\u002F",{"type":50,"tag":65,"props":1078,"children":1080},{"className":1079},[],[1081],{"type":56,"value":381},{"type":56,"value":1083}," mesh plus a ",{"type":50,"tag":65,"props":1085,"children":1087},{"className":1086},[],[1088],{"type":56,"value":804},{"type":56,"value":1090},"\ndict:",{"type":50,"tag":176,"props":1092,"children":1094},{"className":178,"code":1093,"language":180,"meta":181,"style":181},"# meshio covers many formats (CGNS, Ensight, ...); wrap to PyVista:\nimport meshio, pyvista as pv\nmesh = pv.wrap(meshio.read(src_path))\n\n# OpenFOAM case directory:\nmesh = pv.OpenFOAMReader(case_foam_file).read()\n\n# Raw arrays (HDF5 \u002F npz \u002F CSV): build the mesh, then attach fields:\ncloud = pv.PolyData(points_xyz)          # (N, 3) float array\ncloud[\"pressure\"] = p_values             # attach source arrays\n",[1095],{"type":50,"tag":65,"props":1096,"children":1097},{"__ignoreMap":181},[1098,1106,1114,1122,1129,1137,1145,1152,1160,1168],{"type":50,"tag":187,"props":1099,"children":1100},{"class":189,"line":190},[1101],{"type":50,"tag":187,"props":1102,"children":1103},{},[1104],{"type":56,"value":1105},"# meshio covers many formats (CGNS, Ensight, ...); wrap to PyVista:\n",{"type":50,"tag":187,"props":1107,"children":1108},{"class":189,"line":199},[1109],{"type":50,"tag":187,"props":1110,"children":1111},{},[1112],{"type":56,"value":1113},"import meshio, pyvista as pv\n",{"type":50,"tag":187,"props":1115,"children":1116},{"class":189,"line":208},[1117],{"type":50,"tag":187,"props":1118,"children":1119},{},[1120],{"type":56,"value":1121},"mesh = pv.wrap(meshio.read(src_path))\n",{"type":50,"tag":187,"props":1123,"children":1124},{"class":189,"line":218},[1125],{"type":50,"tag":187,"props":1126,"children":1127},{"emptyLinePlaceholder":212},[1128],{"type":56,"value":215},{"type":50,"tag":187,"props":1130,"children":1131},{"class":189,"line":227},[1132],{"type":50,"tag":187,"props":1133,"children":1134},{},[1135],{"type":56,"value":1136},"# OpenFOAM case directory:\n",{"type":50,"tag":187,"props":1138,"children":1139},{"class":189,"line":236},[1140],{"type":50,"tag":187,"props":1141,"children":1142},{},[1143],{"type":56,"value":1144},"mesh = pv.OpenFOAMReader(case_foam_file).read()\n",{"type":50,"tag":187,"props":1146,"children":1147},{"class":189,"line":245},[1148],{"type":50,"tag":187,"props":1149,"children":1150},{"emptyLinePlaceholder":212},[1151],{"type":56,"value":215},{"type":50,"tag":187,"props":1153,"children":1154},{"class":189,"line":254},[1155],{"type":50,"tag":187,"props":1156,"children":1157},{},[1158],{"type":56,"value":1159},"# Raw arrays (HDF5 \u002F npz \u002F CSV): build the mesh, then attach fields:\n",{"type":50,"tag":187,"props":1161,"children":1162},{"class":189,"line":263},[1163],{"type":50,"tag":187,"props":1164,"children":1165},{},[1166],{"type":56,"value":1167},"cloud = pv.PolyData(points_xyz)          # (N, 3) float array\n",{"type":50,"tag":187,"props":1169,"children":1170},{"class":189,"line":272},[1171],{"type":50,"tag":187,"props":1172,"children":1173},{},[1174],{"type":56,"value":1175},"cloud[\"pressure\"] = p_values             # attach source arrays\n",{"type":50,"tag":59,"props":1177,"children":1178},{},[1179,1184,1186,1191,1193,1198],{"type":50,"tag":1003,"props":1180,"children":1181},{},[1182],{"type":56,"value":1183},"Format conversion",{"type":56,"value":1185}," (legacy ",{"type":50,"tag":65,"props":1187,"children":1189},{"className":1188},[],[1190],{"type":56,"value":388},{"type":56,"value":1192}," → ",{"type":50,"tag":65,"props":1194,"children":1196},{"className":1195},[],[1197],{"type":56,"value":373},{"type":56,"value":1199},"):",{"type":50,"tag":176,"props":1201,"children":1203},{"className":178,"code":1202,"language":180,"meta":181,"style":181},"mesh = pv.read(vtk_path).extract_surface()\nmesh.save(vtp_path)\n",[1204],{"type":50,"tag":65,"props":1205,"children":1206},{"__ignoreMap":181},[1207,1215],{"type":50,"tag":187,"props":1208,"children":1209},{"class":189,"line":190},[1210],{"type":50,"tag":187,"props":1211,"children":1212},{},[1213],{"type":56,"value":1214},"mesh = pv.read(vtk_path).extract_surface()\n",{"type":50,"tag":187,"props":1216,"children":1217},{"class":189,"line":199},[1218],{"type":50,"tag":187,"props":1219,"children":1220},{},[1221],{"type":56,"value":1222},"mesh.save(vtp_path)\n",{"type":50,"tag":59,"props":1224,"children":1225},{},[1226],{"type":50,"tag":1003,"props":1227,"children":1228},{},[1229],{"type":56,"value":1230},"Combining separate WSS scalars into a vector:",{"type":50,"tag":176,"props":1232,"children":1234},{"className":178,"code":1233,"language":180,"meta":181,"style":181},"wss = np.stack([mesh.cell_data[\"WSSx\"], mesh.cell_data[\"WSSy\"], mesh.cell_data[\"WSSz\"]], axis=1)\n",[1235],{"type":50,"tag":65,"props":1236,"children":1237},{"__ignoreMap":181},[1238],{"type":50,"tag":187,"props":1239,"children":1240},{"class":189,"line":190},[1241],{"type":50,"tag":187,"props":1242,"children":1243},{},[1244],{"type":56,"value":1233},{"type":50,"tag":59,"props":1246,"children":1247},{},[1248,1253],{"type":50,"tag":1003,"props":1249,"children":1250},{},[1251],{"type":56,"value":1252},"Removing explicit Normals\u002FArea",{"type":56,"value":1254}," (DrivAerML convention):",{"type":50,"tag":176,"props":1256,"children":1258},{"className":178,"code":1257,"language":180,"meta":181,"style":181},"for key in [\"Normals\", \"Area\"]:\n    if key in mesh.cell_data:\n        del mesh.cell_data[key]\n",[1259],{"type":50,"tag":65,"props":1260,"children":1261},{"__ignoreMap":181},[1262,1270,1278],{"type":50,"tag":187,"props":1263,"children":1264},{"class":189,"line":190},[1265],{"type":50,"tag":187,"props":1266,"children":1267},{},[1268],{"type":56,"value":1269},"for key in [\"Normals\", \"Area\"]:\n",{"type":50,"tag":187,"props":1271,"children":1272},{"class":189,"line":199},[1273],{"type":50,"tag":187,"props":1274,"children":1275},{},[1276],{"type":56,"value":1277},"    if key in mesh.cell_data:\n",{"type":50,"tag":187,"props":1279,"children":1280},{"class":189,"line":208},[1281],{"type":50,"tag":187,"props":1282,"children":1283},{},[1284],{"type":56,"value":1285},"        del mesh.cell_data[key]\n",{"type":50,"tag":59,"props":1287,"children":1288},{},[1289,1294],{"type":50,"tag":1003,"props":1290,"children":1291},{},[1292],{"type":56,"value":1293},"Creating STL from surface mesh",{"type":56,"value":1295}," (when no STL is shipped):",{"type":50,"tag":176,"props":1297,"children":1299},{"className":178,"code":1298,"language":180,"meta":181,"style":181},"mesh.extract_surface().triangulate().save(stl_path)\n",[1300],{"type":50,"tag":65,"props":1301,"children":1302},{"__ignoreMap":181},[1303],{"type":50,"tag":187,"props":1304,"children":1305},{"class":189,"line":190},[1306],{"type":50,"tag":187,"props":1307,"children":1308},{},[1309],{"type":56,"value":1298},{"type":50,"tag":59,"props":1311,"children":1312},{},[1313,1315,1321],{"type":56,"value":1314},"The STL must be named ",{"type":50,"tag":65,"props":1316,"children":1318},{"className":1317},[],[1319],{"type":56,"value":1320},"drivaer_{int(case_id)}.stl",{"type":56,"value":1322}," in the same directory\nas the VTP for the model wrappers to find it.",{"type":50,"tag":790,"props":1324,"children":1326},{"id":1325},"match-geometry-orientation-and-scale",[1327],{"type":56,"value":1328},"Match geometry orientation and scale",{"type":50,"tag":59,"props":1330,"children":1331},{},[1332,1334,1339,1341,1347,1349,1355],{"type":56,"value":1333},"Geometry-referenced models (e.g. DoMINO) normalize the mesh\u002FSTL\ncoordinates against a ",{"type":50,"tag":1003,"props":1335,"children":1336},{},[1337],{"type":56,"value":1338},"fixed bounding box baked into the checkpoint\nfrom its training dataset",{"type":56,"value":1340},": DoMINO reads\n",{"type":50,"tag":65,"props":1342,"children":1344},{"className":1343},[],[1345],{"type":56,"value":1346},"cfg.data.bounding_box_surface.min\u002Fmax",{"type":56,"value":1348}," (and ",{"type":50,"tag":65,"props":1350,"children":1352},{"className":1351},[],[1353],{"type":56,"value":1354},"bounding_box.min\u002Fmax",{"type":56,"value":1356}," for\nvolume) and maps every coordinate into that box. If the new dataset's\ngeometry sits in a different frame, origin, or unit scale, it lands in\nthe wrong normalized space — predictions are wrong even when field names\nand signs are correct.",{"type":50,"tag":59,"props":1358,"children":1359},{},[1360,1365],{"type":50,"tag":1003,"props":1361,"children":1362},{},[1363],{"type":56,"value":1364},"This only matters when the checkpoint was trained on a specific\ngeometry-referenced dataset (e.g. DrivAerML).",{"type":56,"value":1366}," For\nscale\u002Ftranslation-invariant models, or when the model was trained on\nthis same dataset, skip it.",{"type":50,"tag":59,"props":1368,"children":1369},{},[1370,1372,1377],{"type":56,"value":1371},"Match three things to the training dataset (DrivAerML reference bounds\nbelow, in ",{"type":50,"tag":1003,"props":1373,"children":1374},{},[1375],{"type":56,"value":1376},"meters",{"type":56,"value":1378},", from the DoMINO config):",{"type":50,"tag":330,"props":1380,"children":1381},{},[1382,1403],{"type":50,"tag":334,"props":1383,"children":1384},{},[1385],{"type":50,"tag":338,"props":1386,"children":1387},{},[1388,1393,1398],{"type":50,"tag":342,"props":1389,"children":1390},{},[1391],{"type":56,"value":1392},"Box",{"type":50,"tag":342,"props":1394,"children":1395},{},[1396],{"type":56,"value":1397},"min (x, y, z)",{"type":50,"tag":342,"props":1399,"children":1400},{},[1401],{"type":56,"value":1402},"max (x, y, z)",{"type":50,"tag":353,"props":1404,"children":1405},{},[1406,1424],{"type":50,"tag":338,"props":1407,"children":1408},{},[1409,1414,1419],{"type":50,"tag":360,"props":1410,"children":1411},{},[1412],{"type":56,"value":1413},"Surface",{"type":50,"tag":360,"props":1415,"children":1416},{},[1417],{"type":56,"value":1418},"-1.5, -1.4, -0.32",{"type":50,"tag":360,"props":1420,"children":1421},{},[1422],{"type":56,"value":1423},"5.0, 1.4, 1.4",{"type":50,"tag":338,"props":1425,"children":1426},{},[1427,1432,1437],{"type":50,"tag":360,"props":1428,"children":1429},{},[1430],{"type":56,"value":1431},"Volume",{"type":50,"tag":360,"props":1433,"children":1434},{},[1435],{"type":56,"value":1436},"-3.5, -2.25, -0.32",{"type":50,"tag":360,"props":1438,"children":1439},{},[1440],{"type":56,"value":1441},"8.5, 2.25, 3.00",{"type":50,"tag":86,"props":1443,"children":1444},{},[1445,1455,1472],{"type":50,"tag":90,"props":1446,"children":1447},{},[1448,1453],{"type":50,"tag":1003,"props":1449,"children":1450},{},[1451],{"type":56,"value":1452},"Orientation \u002F axes",{"type":56,"value":1454},": same convention — x streamwise (length), y\nwidth, z up. Permute or rotate if the new data uses a different\nup-axis or flipped sign.",{"type":50,"tag":90,"props":1456,"children":1457},{},[1458,1463,1465,1470],{"type":50,"tag":1003,"props":1459,"children":1460},{},[1461],{"type":56,"value":1462},"Origin \u002F position",{"type":56,"value":1464},": the bounding box should ",{"type":50,"tag":160,"props":1466,"children":1467},{},[1468],{"type":56,"value":1469},"start",{"type":56,"value":1471}," near the same\n(x, y, z) minimum, so the geometry falls inside the model's domain\nbox.",{"type":50,"tag":90,"props":1473,"children":1474},{},[1475,1480],{"type":50,"tag":1003,"props":1476,"children":1477},{},[1478],{"type":56,"value":1479},"Scale \u002F units",{"type":56,"value":1481},": extents must be the same order of magnitude.\nMillimetre data must be scaled to meters (×0.001).",{"type":50,"tag":59,"props":1483,"children":1484},{},[1485,1487,1492,1494,1499,1501,1506],{"type":56,"value":1486},"Check ",{"type":50,"tag":65,"props":1488,"children":1490},{"className":1489},[],[1491],{"type":56,"value":172},{"type":56,"value":1493}," and transform in ",{"type":50,"tag":65,"props":1495,"children":1497},{"className":1496},[],[1498],{"type":56,"value":1036},{"type":56,"value":1500}," ",{"type":50,"tag":1003,"props":1502,"children":1503},{},[1504],{"type":56,"value":1505},"before",{"type":56,"value":1507}," saving the prepared VTP\u002FSTL:",{"type":50,"tag":176,"props":1509,"children":1511},{"className":178,"code":1510,"language":180,"meta":181,"style":181},"b = mesh.bounds  # (xmin, xmax, ymin, ymax, zmin, zmax)\n# ~1000x larger extents => mm; scale to meters. A swapped axis range => reorient.\nmesh.points *= 0.001\nmesh.points += np.array([x_off, y_off, z_off], dtype=np.float32)  # translate to match origin\n",[1512],{"type":50,"tag":65,"props":1513,"children":1514},{"__ignoreMap":181},[1515,1523,1531,1539],{"type":50,"tag":187,"props":1516,"children":1517},{"class":189,"line":190},[1518],{"type":50,"tag":187,"props":1519,"children":1520},{},[1521],{"type":56,"value":1522},"b = mesh.bounds  # (xmin, xmax, ymin, ymax, zmin, zmax)\n",{"type":50,"tag":187,"props":1524,"children":1525},{"class":189,"line":199},[1526],{"type":50,"tag":187,"props":1527,"children":1528},{},[1529],{"type":56,"value":1530},"# ~1000x larger extents => mm; scale to meters. A swapped axis range => reorient.\n",{"type":50,"tag":187,"props":1532,"children":1533},{"class":189,"line":208},[1534],{"type":50,"tag":187,"props":1535,"children":1536},{},[1537],{"type":56,"value":1538},"mesh.points *= 0.001\n",{"type":50,"tag":187,"props":1540,"children":1541},{"class":189,"line":218},[1542],{"type":50,"tag":187,"props":1543,"children":1544},{},[1545],{"type":56,"value":1546},"mesh.points += np.array([x_off, y_off, z_off], dtype=np.float32)  # translate to match origin\n",{"type":50,"tag":790,"props":1548,"children":1550},{"id":1549},"caching-pattern",[1551],{"type":56,"value":1552},"Caching pattern",{"type":50,"tag":59,"props":1554,"children":1555},{},[1556],{"type":56,"value":1557},"Do expensive conversions lazily and cache:",{"type":50,"tag":176,"props":1559,"children":1561},{"className":178,"code":1560,"language":180,"meta":181,"style":181},"def _prepare_case(self, case_id):\n    prepared_path = self._root \u002F \"_prepared\" \u002F f\"{case_id}.vtp\"\n    if not prepared_path.exists():\n        # ... convert and save\n    return str(prepared_path)\n",[1562],{"type":50,"tag":65,"props":1563,"children":1564},{"__ignoreMap":181},[1565,1573,1581,1589,1597],{"type":50,"tag":187,"props":1566,"children":1567},{"class":189,"line":190},[1568],{"type":50,"tag":187,"props":1569,"children":1570},{},[1571],{"type":56,"value":1572},"def _prepare_case(self, case_id):\n",{"type":50,"tag":187,"props":1574,"children":1575},{"class":189,"line":199},[1576],{"type":50,"tag":187,"props":1577,"children":1578},{},[1579],{"type":56,"value":1580},"    prepared_path = self._root \u002F \"_prepared\" \u002F f\"{case_id}.vtp\"\n",{"type":50,"tag":187,"props":1582,"children":1583},{"class":189,"line":208},[1584],{"type":50,"tag":187,"props":1585,"children":1586},{},[1587],{"type":56,"value":1588},"    if not prepared_path.exists():\n",{"type":50,"tag":187,"props":1590,"children":1591},{"class":189,"line":218},[1592],{"type":50,"tag":187,"props":1593,"children":1594},{},[1595],{"type":56,"value":1596},"        # ... convert and save\n",{"type":50,"tag":187,"props":1598,"children":1599},{"class":189,"line":227},[1600],{"type":50,"tag":187,"props":1601,"children":1602},{},[1603],{"type":56,"value":1604},"    return str(prepared_path)\n",{"type":50,"tag":74,"props":1606,"children":1608},{"id":1607},"step-3-register-and-test",[1609],{"type":56,"value":1610},"Step 3: Register and test",{"type":50,"tag":176,"props":1612,"children":1614},{"className":178,"code":1613,"language":180,"meta":181,"style":181},"register_adapter(\"my_dataset\", MyDatasetAdapter)\n\nadapter = MyDatasetAdapter(root=\"\u002Fpath\u002Fto\u002Fdata\")\ncases = adapter.list_cases()\ncase = adapter.load_case(cases[0])\nassert case.ground_truth is not None\nassert \"pressure\" in case.ground_truth\n",[1615],{"type":50,"tag":65,"props":1616,"children":1617},{"__ignoreMap":181},[1618,1626,1633,1641,1649,1657,1665],{"type":50,"tag":187,"props":1619,"children":1620},{"class":189,"line":190},[1621],{"type":50,"tag":187,"props":1622,"children":1623},{},[1624],{"type":56,"value":1625},"register_adapter(\"my_dataset\", MyDatasetAdapter)\n",{"type":50,"tag":187,"props":1627,"children":1628},{"class":189,"line":199},[1629],{"type":50,"tag":187,"props":1630,"children":1631},{"emptyLinePlaceholder":212},[1632],{"type":56,"value":215},{"type":50,"tag":187,"props":1634,"children":1635},{"class":189,"line":208},[1636],{"type":50,"tag":187,"props":1637,"children":1638},{},[1639],{"type":56,"value":1640},"adapter = MyDatasetAdapter(root=\"\u002Fpath\u002Fto\u002Fdata\")\n",{"type":50,"tag":187,"props":1642,"children":1643},{"class":189,"line":218},[1644],{"type":50,"tag":187,"props":1645,"children":1646},{},[1647],{"type":56,"value":1648},"cases = adapter.list_cases()\n",{"type":50,"tag":187,"props":1650,"children":1651},{"class":189,"line":227},[1652],{"type":50,"tag":187,"props":1653,"children":1654},{},[1655],{"type":56,"value":1656},"case = adapter.load_case(cases[0])\n",{"type":50,"tag":187,"props":1658,"children":1659},{"class":189,"line":236},[1660],{"type":50,"tag":187,"props":1661,"children":1662},{},[1663],{"type":56,"value":1664},"assert case.ground_truth is not None\n",{"type":50,"tag":187,"props":1666,"children":1667},{"class":189,"line":245},[1668],{"type":50,"tag":187,"props":1669,"children":1670},{},[1671],{"type":56,"value":1672},"assert \"pressure\" in case.ground_truth\n",{"type":50,"tag":74,"props":1674,"children":1676},{"id":1675},"step-4-run-inference-and-benchmark",[1677],{"type":56,"value":1678},"Step 4: Run inference and benchmark",{"type":50,"tag":59,"props":1680,"children":1681},{},[1682],{"type":56,"value":1683},"Build a config and run:",{"type":50,"tag":176,"props":1685,"children":1687},{"className":178,"code":1686,"language":180,"meta":181,"style":181},"from physicsnemo.cfd.evaluation.config import Config\nfrom physicsnemo.cfd.evaluation.benchmarks.engine import run_benchmark\n\nconfig = Config.from_dict({\n    \"run\": {\"device\": \"cuda:0\", \"output_dir\": \"results\"},\n    \"model\": {\"name\": \"\u003Cmodel_name>\", \"inference_domain\": \"\u003Csurface|volume>\", ...},\n    \"dataset\": {\"name\": \"my_dataset\", \"root\": \"\u002Fpath\u002Fto\u002Fdata\", \"case_ids\": cases[:2]},\n    \"output\": {\n        \"ground_truth_mesh_field_names\": {\"pressure\": \"\u003Cvtk_gt_name>\", \"shear_stress\": \"\u003Cvtk_gt_name>\"},\n        \"mesh_field_names\": {\"pressure\": \"\u003Cvtk_pred_name>\", \"shear_stress\": \"\u003Cvtk_pred_name>\"},\n    },\n    \"metrics\": [\"l2_pressure\", \"l2_shear_stress\", \"drag\", \"lift\"],\n    \"reports\": {\"enabled\": False},\n})\nresults = run_benchmark(config)\n",[1688],{"type":50,"tag":65,"props":1689,"children":1690},{"__ignoreMap":181},[1691,1699,1707,1714,1722,1730,1738,1746,1754,1762,1770,1778,1786,1794,1802],{"type":50,"tag":187,"props":1692,"children":1693},{"class":189,"line":190},[1694],{"type":50,"tag":187,"props":1695,"children":1696},{},[1697],{"type":56,"value":1698},"from physicsnemo.cfd.evaluation.config import Config\n",{"type":50,"tag":187,"props":1700,"children":1701},{"class":189,"line":199},[1702],{"type":50,"tag":187,"props":1703,"children":1704},{},[1705],{"type":56,"value":1706},"from physicsnemo.cfd.evaluation.benchmarks.engine import run_benchmark\n",{"type":50,"tag":187,"props":1708,"children":1709},{"class":189,"line":208},[1710],{"type":50,"tag":187,"props":1711,"children":1712},{"emptyLinePlaceholder":212},[1713],{"type":56,"value":215},{"type":50,"tag":187,"props":1715,"children":1716},{"class":189,"line":218},[1717],{"type":50,"tag":187,"props":1718,"children":1719},{},[1720],{"type":56,"value":1721},"config = Config.from_dict({\n",{"type":50,"tag":187,"props":1723,"children":1724},{"class":189,"line":227},[1725],{"type":50,"tag":187,"props":1726,"children":1727},{},[1728],{"type":56,"value":1729},"    \"run\": {\"device\": \"cuda:0\", \"output_dir\": \"results\"},\n",{"type":50,"tag":187,"props":1731,"children":1732},{"class":189,"line":236},[1733],{"type":50,"tag":187,"props":1734,"children":1735},{},[1736],{"type":56,"value":1737},"    \"model\": {\"name\": \"\u003Cmodel_name>\", \"inference_domain\": \"\u003Csurface|volume>\", ...},\n",{"type":50,"tag":187,"props":1739,"children":1740},{"class":189,"line":245},[1741],{"type":50,"tag":187,"props":1742,"children":1743},{},[1744],{"type":56,"value":1745},"    \"dataset\": {\"name\": \"my_dataset\", \"root\": \"\u002Fpath\u002Fto\u002Fdata\", \"case_ids\": cases[:2]},\n",{"type":50,"tag":187,"props":1747,"children":1748},{"class":189,"line":254},[1749],{"type":50,"tag":187,"props":1750,"children":1751},{},[1752],{"type":56,"value":1753},"    \"output\": {\n",{"type":50,"tag":187,"props":1755,"children":1756},{"class":189,"line":263},[1757],{"type":50,"tag":187,"props":1758,"children":1759},{},[1760],{"type":56,"value":1761},"        \"ground_truth_mesh_field_names\": {\"pressure\": \"\u003Cvtk_gt_name>\", \"shear_stress\": \"\u003Cvtk_gt_name>\"},\n",{"type":50,"tag":187,"props":1763,"children":1764},{"class":189,"line":272},[1765],{"type":50,"tag":187,"props":1766,"children":1767},{},[1768],{"type":56,"value":1769},"        \"mesh_field_names\": {\"pressure\": \"\u003Cvtk_pred_name>\", \"shear_stress\": \"\u003Cvtk_pred_name>\"},\n",{"type":50,"tag":187,"props":1771,"children":1772},{"class":189,"line":281},[1773],{"type":50,"tag":187,"props":1774,"children":1775},{},[1776],{"type":56,"value":1777},"    },\n",{"type":50,"tag":187,"props":1779,"children":1780},{"class":189,"line":290},[1781],{"type":50,"tag":187,"props":1782,"children":1783},{},[1784],{"type":56,"value":1785},"    \"metrics\": [\"l2_pressure\", \"l2_shear_stress\", \"drag\", \"lift\"],\n",{"type":50,"tag":187,"props":1787,"children":1788},{"class":189,"line":299},[1789],{"type":50,"tag":187,"props":1790,"children":1791},{},[1792],{"type":56,"value":1793},"    \"reports\": {\"enabled\": False},\n",{"type":50,"tag":187,"props":1795,"children":1796},{"class":189,"line":308},[1797],{"type":50,"tag":187,"props":1798,"children":1799},{},[1800],{"type":56,"value":1801},"})\n",{"type":50,"tag":187,"props":1803,"children":1804},{"class":189,"line":317},[1805],{"type":50,"tag":187,"props":1806,"children":1807},{},[1808],{"type":56,"value":1809},"results = run_benchmark(config)\n",{"type":50,"tag":74,"props":1811,"children":1813},{"id":1812},"step-5-make-permanent-optional",[1814],{"type":56,"value":1815},"Step 5: Make permanent (optional)",{"type":50,"tag":59,"props":1817,"children":1818},{},[1819,1821,1827,1829,1835],{"type":56,"value":1820},"Save the adapter to\n",{"type":50,"tag":65,"props":1822,"children":1824},{"className":1823},[],[1825],{"type":56,"value":1826},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fadapters\u002F\u003Cname>.py",{"type":56,"value":1828}," and register in\n",{"type":50,"tag":65,"props":1830,"children":1832},{"className":1831},[],[1833],{"type":56,"value":1834},"adapters\u002F__init__.py",{"type":56,"value":1836},":",{"type":50,"tag":176,"props":1838,"children":1840},{"className":178,"code":1839,"language":180,"meta":181,"style":181},"from physicsnemo.cfd.evaluation.datasets.adapters.\u003Cname> import MyDatasetAdapter\nregister_adapter(\"my_dataset\", MyDatasetAdapter)\n",[1841],{"type":50,"tag":65,"props":1842,"children":1843},{"__ignoreMap":181},[1844,1852],{"type":50,"tag":187,"props":1845,"children":1846},{"class":189,"line":190},[1847],{"type":50,"tag":187,"props":1848,"children":1849},{},[1850],{"type":56,"value":1851},"from physicsnemo.cfd.evaluation.datasets.adapters.\u003Cname> import MyDatasetAdapter\n",{"type":50,"tag":187,"props":1853,"children":1854},{"class":189,"line":199},[1855],{"type":50,"tag":187,"props":1856,"children":1857},{},[1858],{"type":56,"value":1625},{"type":50,"tag":74,"props":1860,"children":1862},{"id":1861},"why-conventions-must-match-the-training-data",[1863],{"type":56,"value":1864},"Why conventions must match the training data",{"type":50,"tag":59,"props":1866,"children":1867},{},[1868],{"type":56,"value":1869},"The field name mappings, sign conventions, and format conversions in the\nadapter exist because the model checkpoint was trained on a specific\ndataset (e.g., DrivAerML) with specific conventions. The adapter bridges\nthe gap between the new dataset's conventions and the training data's\nconventions — not some abstract standard. If a model is retrained\ndirectly on the new dataset, the adapter would not need these\ntransformations. When writing an adapter, always ask: \"What conventions\ndid the model's training data use?\" and map to those.",{"type":50,"tag":74,"props":1871,"children":1873},{"id":1872},"gotchas",[1874],{"type":56,"value":1875},"Gotchas",{"type":50,"tag":86,"props":1877,"children":1878},{},[1879,1942,1983,2012,2030,2055],{"type":50,"tag":90,"props":1880,"children":1881},{},[1882,1887,1889,1895,1897,1903,1905,1911,1912,1918,1919,1925,1927,1933,1934,1940],{"type":50,"tag":1003,"props":1883,"children":1884},{},[1885],{"type":56,"value":1886},"DistributedManager",{"type":56,"value":1888},": Model wrappers call\n",{"type":50,"tag":65,"props":1890,"children":1892},{"className":1891},[],[1893],{"type":56,"value":1894},"DistributedManager.initialize()",{"type":56,"value":1896},". In notebooks without ",{"type":50,"tag":65,"props":1898,"children":1900},{"className":1899},[],[1901],{"type":56,"value":1902},"torchrun",{"type":56,"value":1904},",\nset env vars first: ",{"type":50,"tag":65,"props":1906,"children":1908},{"className":1907},[],[1909],{"type":56,"value":1910},"WORLD_SIZE=1",{"type":56,"value":375},{"type":50,"tag":65,"props":1913,"children":1915},{"className":1914},[],[1916],{"type":56,"value":1917},"RANK=0",{"type":56,"value":375},{"type":50,"tag":65,"props":1920,"children":1922},{"className":1921},[],[1923],{"type":56,"value":1924},"LOCAL_RANK=0",{"type":56,"value":1926},",\n",{"type":50,"tag":65,"props":1928,"children":1930},{"className":1929},[],[1931],{"type":56,"value":1932},"MASTER_ADDR=localhost",{"type":56,"value":375},{"type":50,"tag":65,"props":1935,"children":1937},{"className":1936},[],[1938],{"type":56,"value":1939},"MASTER_PORT=12355",{"type":56,"value":1941},".",{"type":50,"tag":90,"props":1943,"children":1944},{},[1945,1950,1952,1958,1960,1966,1968,1974,1976,1981],{"type":50,"tag":1003,"props":1946,"children":1947},{},[1948],{"type":56,"value":1949},"STL naming",{"type":56,"value":1951},": DoMINO looks for ",{"type":50,"tag":65,"props":1953,"children":1955},{"className":1954},[],[1956],{"type":56,"value":1957},"drivaer_{tag}.stl",{"type":56,"value":1959},", GeoTransolver\nlooks for ",{"type":50,"tag":65,"props":1961,"children":1963},{"className":1962},[],[1964],{"type":56,"value":1965},"drivaer_{tag}_single_solid.stl",{"type":56,"value":1967}," then ",{"type":50,"tag":65,"props":1969,"children":1971},{"className":1970},[],[1972],{"type":56,"value":1973},"*.stl",{"type":56,"value":1975},". Both now fall\nback to any ",{"type":50,"tag":65,"props":1977,"children":1979},{"className":1978},[],[1980],{"type":56,"value":1973},{"type":56,"value":1982}," in the directory.",{"type":50,"tag":90,"props":1984,"children":1985},{},[1986,1991,1993,1998,2000,2005,2006,2011],{"type":50,"tag":1003,"props":1987,"children":1988},{},[1989],{"type":56,"value":1990},"VTP vs VTK",{"type":56,"value":1992},": Model wrappers use VTK XML readers internally. Legacy\n",{"type":50,"tag":65,"props":1994,"children":1996},{"className":1995},[],[1997],{"type":56,"value":388},{"type":56,"value":1999}," files must be converted to ",{"type":50,"tag":65,"props":2001,"children":2003},{"className":2002},[],[2004],{"type":56,"value":373},{"type":56,"value":1076},{"type":50,"tag":65,"props":2007,"children":2009},{"className":2008},[],[2010],{"type":56,"value":381},{"type":56,"value":1941},{"type":50,"tag":90,"props":2013,"children":2014},{},[2015,2020,2022,2028],{"type":50,"tag":1003,"props":2016,"children":2017},{},[2018],{"type":56,"value":2019},"Checkpoint loading",{"type":56,"value":2021},": Some wrappers need\n",{"type":50,"tag":65,"props":2023,"children":2025},{"className":2024},[],[2026],{"type":56,"value":2027},"trusted_torch_load_context()",{"type":56,"value":2029}," for PyTorch 2.6+ checkpoint\ncompatibility.",{"type":50,"tag":90,"props":2031,"children":2032},{},[2033,2038,2039,2045,2047,2053],{"type":50,"tag":1003,"props":2034,"children":2035},{},[2036],{"type":56,"value":2037},"Domain-scoped metrics",{"type":56,"value":1046},{"type":50,"tag":65,"props":2040,"children":2042},{"className":2041},[],[2043],{"type":56,"value":2044},"l2_pressure",{"type":56,"value":2046}," resolves to different\nimplementations for surface vs volume based on ",{"type":50,"tag":65,"props":2048,"children":2050},{"className":2049},[],[2051],{"type":56,"value":2052},"inference_domain",{"type":56,"value":2054},". Use\nthe same metric name for both.",{"type":50,"tag":90,"props":2056,"children":2057},{},[2058,2063],{"type":50,"tag":1003,"props":2059,"children":2060},{},[2061],{"type":56,"value":2062},"Geometry frame",{"type":56,"value":2064},": geometry-referenced checkpoints (DoMINO) assume\nthe training dataset's coordinate frame and scale. A mm-vs-m or\nflipped-axis mismatch produces wrong predictions with no error raised.\nSee \"Match geometry orientation and scale\".",{"type":50,"tag":2066,"props":2067,"children":2068},"style",{},[2069],{"type":56,"value":2070},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":2072,"total":208},[2073,2087,2095],{"slug":2074,"name":2074,"fn":2075,"description":2076,"org":2077,"tags":2078,"stars":26,"repoUrl":27,"updatedAt":2086},"physicsnemo-cfd-create-custom-metric","create custom metrics for CFD benchmarking","Create a custom metric for the PhysicsNeMo CFD benchmarking workflow. Use when the user wants to add a new evaluation metric, implement a custom error measure, compute force coefficients, or extend the benchmark with domain-specific quantities.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2079,2082,2083,2084,2085],{"name":2080,"slug":2081,"type":15},"Analysis","analysis",{"name":17,"slug":18,"type":15},{"name":9,"slug":8,"type":15},{"name":24,"slug":25,"type":15},{"name":21,"slug":22,"type":15},"2026-07-14T05:33:34.319957",{"slug":4,"name":4,"fn":5,"description":6,"org":2088,"tags":2089,"stars":26,"repoUrl":27,"updatedAt":28},{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2090,2091,2092,2093,2094],{"name":17,"slug":18,"type":15},{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},{"name":24,"slug":25,"type":15},{"name":21,"slug":22,"type":15},{"slug":2096,"name":2096,"fn":2097,"description":2098,"org":2099,"tags":2100,"stars":26,"repoUrl":27,"updatedAt":2108},"physicsnemo-cfd-create-model-wrapper","create model wrappers for CFD benchmarking","Create a new model wrapper for the PhysicsNeMo CFD benchmarking workflow. Use when the user wants to add a new CFD model, write a CFDModel wrapper, integrate a new neural network architecture, or run a custom model through the benchmarking pipeline.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2101,2102,2105,2106,2107],{"name":17,"slug":18,"type":15},{"name":2103,"slug":2104,"type":15},"Deep Learning","deep-learning",{"name":9,"slug":8,"type":15},{"name":24,"slug":25,"type":15},{"name":21,"slug":22,"type":15},"2026-07-14T05:33:33.080019",{"items":2110,"total":2268},[2111,2129,2146,2157,2169,2183,2196,2210,2223,2234,2248,2257],{"slug":2112,"name":2112,"fn":2113,"description":2114,"org":2115,"tags":2116,"stars":2126,"repoUrl":2127,"updatedAt":2128},"nemoclaw-user-guide","retrieve NemoClaw documentation and configuration","Guides human users' AI agents to the NemoClaw docs MCP server and canonical Fern documentation in Markdown form. Use when users ask how to install, configure, operate, troubleshoot, secure, or learn NemoClaw with an AI coding assistant. Trigger keywords - nemoclaw docs, use nemoclaw with ai agent, nemoclaw mcp docs, nemoclaw install help, nemoclaw quickstart, nemoclaw markdown docs, llms.txt, agent skills.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2117,2120,2123],{"name":2118,"slug":2119,"type":15},"Documentation","documentation",{"name":2121,"slug":2122,"type":15},"MCP","mcp",{"name":2124,"slug":2125,"type":15},"Search","search",21777,"https:\u002F\u002Fgithub.com\u002FNVIDIA\u002FNemoClaw","2026-07-20T06:00:01.461044",{"slug":2130,"name":2130,"fn":2131,"description":2132,"org":2133,"tags":2134,"stars":2143,"repoUrl":2144,"updatedAt":2145},"mcore-build-and-dependency","manage Megatron-LM development environments","Container-based dev environment setup and dependency management for Megatron-LM. Covers acquiring and launching the CI container, uv package management, and updating uv.lock.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2135,2138,2141],{"name":2136,"slug":2137,"type":15},"Containers","containers",{"name":2139,"slug":2140,"type":15},"Deployment","deployment",{"name":2142,"slug":180,"type":15},"Python",17049,"https:\u002F\u002Fgithub.com\u002FNVIDIA\u002FMegatron-LM","2026-07-27T06:06:11.249662",{"slug":2147,"name":2147,"fn":2148,"description":2149,"org":2150,"tags":2151,"stars":2143,"repoUrl":2144,"updatedAt":2156},"mcore-bump-base-image","update NVIDIA PyTorch base images","Bump the NVIDIA PyTorch base image (`nvcr.io\u002Fnvidia\u002Fpytorch:YY.MM-py3`) used by Megatron-LM CI. Covers the two pin sites (GitHub CI in `docker\u002F.ngc_version.dev` and GitLab CI in `.gitlab\u002Fstages\u002F01.build.yml`), the post-bump CI loop (re-run functional tests, refresh golden values, mark broken tests), and the gotchas that bit PRs",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2152,2155],{"name":2153,"slug":2154,"type":15},"CI\u002FCD","ci-cd",{"name":2139,"slug":2140,"type":15},"2026-07-14T05:25:59.97109",{"slug":2158,"name":2158,"fn":2159,"description":2160,"org":2161,"tags":2162,"stars":2143,"repoUrl":2144,"updatedAt":2168},"mcore-cicd","manage CI\u002FCD pipelines for Megatron-LM","CI\u002FCD reference for Megatron-LM. Covers CI pipeline structure, PR scope labels, triggering internal GitLab CI (which force-pushes the current branch to a pull-request\u002FBRANCH ref — always dry-run and verify the destination first; never run against shared or protected branches), and CI failure investigation.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2163,2164,2165],{"name":2153,"slug":2154,"type":15},{"name":2139,"slug":2140,"type":15},{"name":2166,"slug":2167,"type":15},"GitHub","github","2026-07-27T06:06:12.278222",{"slug":2170,"name":2170,"fn":2171,"description":2172,"org":2173,"tags":2174,"stars":2143,"repoUrl":2144,"updatedAt":2182},"mcore-create-issue","investigate CI failures and create issues","Investigate a failing GitHub Actions run or job and create a GitHub issue for the failure.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2175,2178,2179],{"name":2176,"slug":2177,"type":15},"Debugging","debugging",{"name":2166,"slug":2167,"type":15},{"name":2180,"slug":2181,"type":15},"Triage","triage","2026-07-14T05:25:57.442089",{"slug":2184,"name":2184,"fn":2185,"description":2186,"org":2187,"tags":2188,"stars":2143,"repoUrl":2144,"updatedAt":2195},"mcore-linting-and-formatting","lint and format Megatron-LM code","Linting and formatting for Megatron-LM. Covers running autoformat.sh, tools (ruff, black, isort, pylint, mypy), and code style rules.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2189,2192],{"name":2190,"slug":2191,"type":15},"Best Practices","best-practices",{"name":2193,"slug":2194,"type":15},"Code Analysis","code-analysis","2026-07-14T05:25:56.18433",{"slug":2197,"name":2197,"fn":2198,"description":2199,"org":2200,"tags":2201,"stars":2143,"repoUrl":2144,"updatedAt":2209},"mcore-migrate-gpt-to-hybrid","migrate Megatron-LM models to HybridModel","Migration guide for moving Megatron Core GPTModel checkpoints, model providers, training commands, and layer mappings to HybridModel.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2202,2205,2208],{"name":2203,"slug":2204,"type":15},"Machine Learning","machine-learning",{"name":2206,"slug":2207,"type":15},"Migration","migration",{"name":9,"slug":8,"type":15},"2026-07-17T06:07:11.777011",{"slug":2211,"name":2211,"fn":2212,"description":2213,"org":2214,"tags":2215,"stars":2143,"repoUrl":2144,"updatedAt":2222},"mcore-onboard-gb200-1node-tests","onboard functional tests for GB200","Onboard 1-node GitHub MR functional tests for GB200 from existing mr-scoped 2-node tests.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2216,2219],{"name":2217,"slug":2218,"type":15},"QA","qa",{"name":2220,"slug":2221,"type":15},"Testing","testing","2026-07-14T05:25:53.673039",{"slug":2224,"name":2224,"fn":2225,"description":2226,"org":2227,"tags":2228,"stars":2143,"repoUrl":2144,"updatedAt":2233},"mcore-run-on-slurm","launch distributed training jobs on SLURM","How to launch distributed Megatron-LM training jobs on a SLURM cluster. Covers a minimal sbatch skeleton, environment-variable setup for torch.distributed.run, CUDA_DEVICE_MAX_CONNECTIONS rules across hardware and parallelism modes, container conventions, monitoring, and per-rank failure diagnosis.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2229,2230],{"name":2139,"slug":2140,"type":15},{"name":2231,"slug":2232,"type":15},"Infrastructure","infrastructure","2026-07-14T05:25:49.362534",{"slug":2235,"name":2235,"fn":2236,"description":2237,"org":2238,"tags":2239,"stars":2143,"repoUrl":2144,"updatedAt":2247},"mcore-split-pr","split pull requests to reduce review load","Split a PR into multiple PRs to reduce the number of required CODEOWNERS reviewer groups.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2240,2243,2244],{"name":2241,"slug":2242,"type":15},"Code Review","code-review",{"name":2166,"slug":2167,"type":15},{"name":2245,"slug":2246,"type":15},"Pull Requests","pull-requests","2026-07-14T05:26:01.226578",{"slug":2249,"name":2249,"fn":2250,"description":2251,"org":2252,"tags":2253,"stars":2143,"repoUrl":2144,"updatedAt":2256},"mcore-testing","run and manage Megatron-LM tests","Test system for Megatron-LM. Covers test layout, recipe YAML structure, adding and running unit and functional tests, golden values, marker filters, and CI parity.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2254,2255],{"name":2217,"slug":2218,"type":15},{"name":2220,"slug":2221,"type":15},"2026-07-14T05:25:54.928983",{"slug":2258,"name":2258,"fn":2259,"description":2260,"org":2261,"tags":2262,"stars":2143,"repoUrl":2144,"updatedAt":2267},"nightly-sync","manage nightly main-to-dev sync workflows","Domain knowledge for the nightly main-to-dev sync workflow. Covers merge strategy, CI architecture, failure investigation, and known issues.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2263,2266],{"name":2264,"slug":2265,"type":15},"Automation","automation",{"name":2153,"slug":2154,"type":15},"2026-07-30T05:29:03.275638",496]