[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-nvidia-physicsnemo-cfd-create-model-wrapper":3,"mdc-s6f1dk-key":44,"related-repo-nvidia-physicsnemo-cfd-create-model-wrapper":2442,"related-org-nvidia-physicsnemo-cfd-create-model-wrapper":2480},{"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-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},"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},"Benchmarking","benchmarking","tag",{"name":17,"slug":18,"type":15},"Deep Learning","deep-learning",{"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:33.080019","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-model-wrapper","---\nname: physicsnemo-cfd-create-model-wrapper\ndescription: >-\n  Create a new model wrapper for the PhysicsNeMo CFD benchmarking workflow.\n  Use when the user wants to add a new CFD model, write a CFDModel wrapper,\n  integrate a new neural network architecture, or run a custom model through\n  the benchmarking pipeline.\nlicense: Apache-2.0\n---\n\n# Create a Model Wrapper\n\nGuide the user through adding a new CFD model to the benchmarking workflow\nby writing a `CFDModel` subclass.\n\n## First: gather whatever context already exists\n\nSpend a little effort up front collecting any existing artifacts that\nreveal how the model actually behaves — but **do not block on them**.\nLook (without stopping to ask the user) for:\n\n- the **training\u002Finference script** and any\n  preprocessing\u002Fnormalization utilities it imports;\n- the model's **config files** (YAML\u002FJSON hyperparameters, channel lists,\n  stats paths);\n- the model class's **docstrings and signatures** (forward inputs\u002Foutputs,\n  expected shapes).\n\nUse these to pin down four things the wrapper must mirror exactly:\n\n- **Normalization** — which scheme and which stats (see Normalization\n  below).\n- **Inputs** — what the model actually consumes (coordinates only, extra\n  fields, geometry\u002FSTL).\n- **Output fields and order** — which variables the forward pass returns\n  and in what channel order.\n- **I\u002FO shapes and dtypes** — so `prepare_inputs`\u002F`decode_outputs` match\n  the trained graph.\n\nIf some or all of these aren't available, that's fine — **proceed\nanyway**: reconstruct from the checkpoint and stats file, pick the most\nlikely option, and build the wrapper now. Do **not** stop and wait for\nanswers before writing code. Just avoid *silently* guessing: a wrong\nnormalization scheme or channel order produces plausible-looking but\nwrong predictions. So state each such assumption inline, and after\ndelivering the wrapper, **raise the still-uncertain choices as explicit\nopen items for the user to confirm** — e.g. normalization scheme, input\ntier, and output fields\u002Fchannel order. This keeps you unblocked while\nmaking the risky decisions visible.\n\n## Reference files to read first\n\nStart with the complete, ready-to-adapt templates bundled with this skill\n— they are always available even when the PhysicsNeMo-CFD source tree is\nnot on disk:\n\n- `references\u002Fexample_wrapper.py` — full surface **and** volume\n  `CFDModel` reference implementations (load, prepare_inputs, predict,\n  decode_outputs, registration). Copy and adapt one of these.\n- `assets\u002Fglobal_stats.example.json` — sample mean\u002Fstd stats for both\n  surface and volume.\n\nWhen the PhysicsNeMo-CFD repo *is* present, also read these for the live\ninterface (verify paths against the actual tree):\n\n- `physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fmodel_registry.py` — base class and\n  registry\n- `physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fschema.py` — `CanonicalCase`,\n  `build_predictions_dict`\n- `physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fwrappers\u002Fsurface_baseline.py` —\n  simplest concrete surface wrapper\n- `physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fwrappers\u002Fvolume_baseline.py` —\n  simplest concrete volume wrapper\n- `physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fwrappers\u002F__init__.py` — how wrappers\n  are registered\n- `physicsnemo\u002Fcfd\u002Fevaluation\u002Fcommon\u002Fio.py` — mesh loading and\n  normalization stats helpers\n- `workflows\u002Fbenchmarking\u002Fnotebooks\u002Fadding_a_new_model.ipynb` —\n  end-to-end tutorial\n\n## The `CFDModel` interface\n\nEvery wrapper must set two class variables and implement four methods:\n\n| Member | Purpose |\n|--------|---------|\n| `INFERENCE_DOMAIN` | `\"surface\"` or `\"volume\"` — which mesh manifold |\n| `OUTPUT_LOCATION` | `\"point\"` or `\"cell\"` — where predictions live on the mesh |\n| `output_location` (property) | Instance-level access to `OUTPUT_LOCATION` |\n| `load(checkpoint_path, stats_path, device, **kwargs)` | Load weights and stats; return `self` |\n| `prepare_inputs(case: CanonicalCase)` | Convert canonical case into model-specific tensors\u002Fgraphs |\n| `predict(model_input)` | Run forward pass; return raw output |\n| `decode_outputs(raw_output, case, model_input=None)` | Denormalize and map to canonical predictions dict |\n\nThe engine calls `load` once, then `prepare_inputs → predict →\ndecode_outputs(raw, case, model_input)` per case (`model_input` is the\ndict from `prepare_inputs`; use when decode must mirror inference\ngeometry).\n\n## Step 1: Write the wrapper class\n\n**Always generate a new, complete wrapper class for the requested\nmodel.** Existing wrappers (e.g. `surface_baseline.py`) are *references\nto read*, not substitutes — when the user asks to write or create a\nwrapper, produce a full new class file even if similar ones already\nexist. Do not stop at \"a wrapper already exists\" or offer to reuse one\nin place of writing the requested one. Always tell the user how to\nregister it: `register_model(...)` at import for a quick test, and an\nentry in `wrappers\u002F__init__.py` to make it permanent (Step 6).\n\n### Anti-patterns (do not do these)\n\n- **Reusing an existing wrapper instead of writing the requested one** —\n  even if `git status` shows a similar file, write the new class.\n- **Assuming mean-std normalization** — confirm the scheme; a wrong\n  inverse gives plausible-but-wrong fields.\n- **Hardcoding `pressure` + `shear_stress`** — pass only the fields the\n  model predicts; add custom ones via `**extra`.\n- **Skipping `__init__.py`** — registering only inline and never\n  mentioning permanent registration.\n- **Echoing the interface table or full reference file back** — wastes\n  tokens; reference, don't repeat.\n\n**Copy `references\u002Fexample_wrapper.py` and adapt it** — it has full\nsurface and volume implementations. Don't hand-write from scratch or\npaste the whole template back to the user. The class skeleton is just:\n\n```python\nclass MyModelWrapper(CFDModel):\n    INFERENCE_DOMAIN: ClassVar[InferenceDomain] = \"surface\"  # or \"volume\"\n    OUTPUT_LOCATION: ClassVar[OutputLocation] = \"cell\"        # or \"point\"\n\n    @property\n    def output_location(self): return self.OUTPUT_LOCATION\n    def load(self, checkpoint_path, stats_path, device, **kwargs): ...   # weights + stats; return self\n    def prepare_inputs(self, case): ...                                  # CanonicalCase -> model input\n    def predict(self, model_input): ...                                  # forward pass -> raw output\n    def decode_outputs(self, raw_output, case, model_input=None): ...    # denormalize -> build_predictions_dict(...)\n```\n\nKeep responses terse: state the few model-specific decisions\n(normalization scheme, input tier, output fields) and the file you\nwrote — don't echo the interface table or the full reference file back.\n\n### Key implementation considerations\n\n**Normalization** (match the training script exactly): Most trained\nmodels normalize inputs\u002Foutputs, and `decode_outputs` must apply the\n*inverse* of whatever the model was trained with. First identify the\nscheme:\n\n- **Mean-std (z-score)**: `x_norm = (x - mean) \u002F std` → inverse\n  `x = x_norm * std + mean`. This is the repo's built-in format. Use\n  `load_global_stats(stats_path)` from\n  `physicsnemo\u002Fcfd\u002Fevaluation\u002Fcommon\u002Fio.py`; it reads `mean`\u002F`std_dev`\n  JSON and returns `mean`\u002F`std` tensors.\n- **Min-max**: `x_norm = (x - min) \u002F (max - min)` → inverse\n  `x = x_norm * (max - min) + min`. There is **no built-in helper** for\n  this — store `min`\u002F`max` (e.g. in your stats JSON) and apply the\n  inverse yourself in `decode_outputs`. Do not feed a min-max file to\n  `load_global_stats`; the keys won't match.\n\nConfirm the scheme from the training\u002Finference script or the stats file\nrather than assuming mean-std. Applying the wrong inverse yields\nwrong-but-plausible fields that still pass shape checks.\n\n**Inputs** (handle the model's actual input tier): `prepare_inputs`\nreceives a `CanonicalCase`. Pull what the model needs:\n\n- **Point cloud only**: coordinates from `case.mesh_path` (vtp\u002Fvtu) via\n  `pv.read`, as in the example — sufficient for many geometry-only\n  models.\n- **Extra field inputs** (e.g. inlet\u002Ffreestream velocity, Reynolds\n  number): read from `case.metadata` (or `case.ground_truth` for field\n  arrays). Broadcast\u002Fconcatenate them onto the per-point features as the\n  training script did.\n- **Geometry\u002FSTL** (e.g. SDF or BVH-based models): the STL\u002Fgeometry path\n  is typically on `case.metadata`; load it in `prepare_inputs` and build\n  the geometric encoding the model expects.\n\nInspect `case.metadata` and `case.ground_truth` keys for a real case\nearly — the dataset adapter decides what is available.\n\n**Outputs** (predict only what the model produces, plus extras):\n`build_predictions_dict` takes `pressure`, `shear_stress`, `velocity`,\n`turbulent_viscosity` (all optional) **and arbitrary `**extra`\nfields**. So:\n\n- A model that predicts only WSS magnitude, or no WSS at all, simply\n  omits the missing keys — pass only what it produces.\n- Extra\u002Fnon-standard outputs (e.g. `stagnation_pressure`, `temperature`,\n  `mach`) are passed as keyword args: `build_predictions_dict(pressure=p,\n  mach=m, temperature=t)`. Each becomes a prediction variable.\n- For a custom field to appear in the written mesh and metrics, add a\n  matching entry to `output.mesh_field_names` in the config (Step 4) and\n  a corresponding metric if you want it scored.\n\n**Output shape**: `pressure` must be `(N,)` float32. `shear_stress` must\nbe `(N, 3)` float32 for surface. Volume fields: `velocity` is `(N, 3)`,\n`turbulent_viscosity` is `(N,)`. Custom scalar fields are `(N,)`, vector\nfields `(N, k)`.\n\n**Output location**: If `OUTPUT_LOCATION = \"cell\"`, return N =\n`mesh.n_cells` values. If `\"point\"`, return N = `mesh.n_points` values.\n\n**Batching**: For large meshes, `prepare_inputs` may need to subsample\nor batch. Use `kwargs` passed through `load()` (e.g.,\n`batch_resolution`, `geometry_sampling`) to control this.\n\n## Step 2: Create checkpoint and stats files\n\nYour model needs a checkpoint file and optionally a `global_stats.json`:\n\n```python\n# Checkpoint: save your model's state dict\ntorch.save(model.state_dict(), \"checkpoint.pt\")\n\n# Stats: JSON with mean\u002Fstd_dev for denormalization\n# Surface format:\n{\n    \"mean\": {\"pressure\": [0.0], \"shear_stress\": [0.0, 0.0, 0.0]},\n    \"std_dev\": {\"pressure\": [1.0], \"shear_stress\": [1.0, 1.0, 1.0]}\n}\n# Volume format:\n{\n    \"mean\": {\"pressure\": [0.0], \"velocity\": [0.0, 0.0, 0.0], \"turbulent_viscosity\": [0.0]},\n    \"std_dev\": {\"pressure\": [1.0], \"velocity\": [1.0, 1.0, 1.0], \"turbulent_viscosity\": [1.0]}\n}\n```\n\nThis `mean`\u002F`std_dev` layout is what `load_global_stats()` expects\n(mean-std models). If your model was trained with **min-max**\nnormalization, this helper does not apply — persist `min`\u002F`max` per\nfield in your own JSON and apply the inverse manually in\n`decode_outputs` (see Normalization above).\n\n## Step 3: Register and test\n\n```python\nregister_model(\"my_model\", MyModelWrapper)\n\n# Load a case from any registered dataset adapter\nfrom physicsnemo.cfd.evaluation.datasets.adapters.drivaerml import DrivAerMLAdapter\nadapter = DrivAerMLAdapter(root=\"\u002Fpath\u002Fto\u002Fdata\", inference_domain=\"surface\")\ncase = adapter.load_case(adapter.list_cases()[0])\n\n# Run the full inference pipeline\nwrapper = MyModelWrapper()\nwrapper.load(checkpoint_path=\"checkpoint.pt\", stats_path=\"global_stats.json\", device=\"cuda:0\")\nmodel_input = wrapper.prepare_inputs(case)\nraw_output = wrapper.predict(model_input)\npredictions = wrapper.decode_outputs(raw_output, case, model_input)\n\nassert \"pressure\" in predictions\nassert predictions[\"pressure\"].shape[0] > 0\n```\n\n## Step 4: Run the full benchmark\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\", \"metrics_cache\": {\"enabled\": False}},\n    \"benchmark\": {\n        \"mode\": \"matrix\",\n        \"models\": [{\n            \"name\": \"my_model\",\n            \"inference_domain\": \"surface\",\n            \"checkpoint\": \"\u002Fpath\u002Fto\u002Fcheckpoint.pt\",\n            \"stats_path\": \"\u002Fpath\u002Fto\u002Fglobal_stats.json\",\n            \"kwargs\": {},\n        }],\n        \"datasets\": [{\n            \"name\": \"drivaerml\",\n            \"root\": \"\u002Fpath\u002Fto\u002Fdrivaerml\u002Fdata\",\n            \"case_ids\": [\"run_1\", \"run_11\"],\n            \"kwargs\": {\"align_ground_truth_to_model\": True, \"inference_domain\": \"surface\"},\n        }],\n        \"reproducibility\": {\"log_env\": False, \"save_artifacts\": True},\n    },\n    \"output\": {\"mesh_field_names\": {\"pressure\": \"pMeanTrimPred\", \"shear_stress\": \"wallShearStressMeanTrimPred\"}},\n    \"metrics\": [\"l2_pressure\", \"l2_shear_stress\", \"l2_pressure_area_weighted\", \"drag\", \"lift\"],\n    \"reports\": {\"enabled\": False},\n})\nresults = run_benchmark(config)\n```\n\nResults are written to `benchmark_results.json` (a JSON list of dicts,\none per model×dataset combo).\n\n## Step 5: Visualize predictions\n\n```python\nfrom physicsnemo.cfd.postprocessing_tools.visualization.utils import plot_fields, plot_field_comparisons\n\n# Just the predicted fields (no GT comparison):\nplotter = plot_fields(mesh, fields=[\"pMeanTrimPred\"], view=\"xy\", dtype=\"cell\", window_size=[1800, 600])\nplotter.screenshot(\"predicted_pressure.png\")\nplotter.close()\n\n# Side-by-side with GT (GT | Pred | Error):\nplotter = plot_field_comparisons(mesh, true_fields=[\"pMeanTrim\"], pred_fields=[\"pMeanTrimPred\"],\n                                  view=\"xy\", dtype=\"cell\", window_size=[1800, 600])\nplotter.screenshot(\"comparison.png\")\nplotter.close()\n```\n\n## Step 6: Make permanent (optional)\n\nSave the wrapper to\n`physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fwrappers\u002Fmy_model.py` and register in\n`wrappers\u002F__init__.py`:\n\n```python\nfrom physicsnemo.cfd.evaluation.models.wrappers.my_model import MyModelWrapper\nregister_model(\"my_model\", MyModelWrapper)\n```\n\nThen use `model.name: my_model` in any YAML config.\n\n## Gotchas\n\n- **DistributedManager**: Model wrappers may 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- **`weights_only=True`**: Use this flag with `torch.load()` for safe\n  deserialization (PyTorch 2.6+ default).\n- **Domain matching**: The engine checks that `model.INFERENCE_DOMAIN`\n  matches the dataset adapter's `inference_domain_from_kwargs()`.\n  Mismatches are skipped in matrix mode or raise in single mode.\n- **GT alignment**: When `align_ground_truth_to_model: true` in dataset\n  kwargs, the engine converts GT data to match `OUTPUT_LOCATION` (point\n  ↔ cell). This is automatic — the wrapper just needs correct class\n  vars.\n- **Results JSON format**: `benchmark_results.json` is a plain\n  `list[dict]`, not `{\"results\": [...]}`. Iterate directly:\n  `for combo in report:`.\n\n## Related resources\n\n- `references\u002Fexample_wrapper.py` — complete surface + volume `CFDModel`\n  templates to copy and adapt (bundled; available without the repo on\n  disk).\n- `assets\u002Fglobal_stats.example.json` — sample mean\u002Fstd stats layout for\n  surface and volume.\n",{"data":45,"body":46},{"name":4,"description":6,"license":29},{"type":47,"children":48},"root",[49,58,73,80,93,134,139,198,232,238,243,282,294,388,401,406,593,629,635,676,683,772,789,893,898,904,927,1061,1066,1089,1168,1187,1246,1302,1381,1422,1470,1476,1489,1609,1662,1668,1802,1808,2039,2052,2058,2158,2164,2183,2205,2218,2224,2400,2406,2436],{"type":50,"tag":51,"props":52,"children":54},"element","h1",{"id":53},"create-a-model-wrapper",[55],{"type":56,"value":57},"text","Create a Model Wrapper",{"type":50,"tag":59,"props":60,"children":61},"p",{},[62,64,71],{"type":56,"value":63},"Guide the user through adding a new CFD model to the benchmarking workflow\nby writing a ",{"type":50,"tag":65,"props":66,"children":68},"code",{"className":67},[],[69],{"type":56,"value":70},"CFDModel",{"type":56,"value":72}," subclass.",{"type":50,"tag":74,"props":75,"children":77},"h2",{"id":76},"first-gather-whatever-context-already-exists",[78],{"type":56,"value":79},"First: gather whatever context already exists",{"type":50,"tag":59,"props":81,"children":82},{},[83,85,91],{"type":56,"value":84},"Spend a little effort up front collecting any existing artifacts that\nreveal how the model actually behaves — but ",{"type":50,"tag":86,"props":87,"children":88},"strong",{},[89],{"type":56,"value":90},"do not block on them",{"type":56,"value":92},".\nLook (without stopping to ask the user) for:",{"type":50,"tag":94,"props":95,"children":96},"ul",{},[97,110,122],{"type":50,"tag":98,"props":99,"children":100},"li",{},[101,103,108],{"type":56,"value":102},"the ",{"type":50,"tag":86,"props":104,"children":105},{},[106],{"type":56,"value":107},"training\u002Finference script",{"type":56,"value":109}," and any\npreprocessing\u002Fnormalization utilities it imports;",{"type":50,"tag":98,"props":111,"children":112},{},[113,115,120],{"type":56,"value":114},"the model's ",{"type":50,"tag":86,"props":116,"children":117},{},[118],{"type":56,"value":119},"config files",{"type":56,"value":121}," (YAML\u002FJSON hyperparameters, channel lists,\nstats paths);",{"type":50,"tag":98,"props":123,"children":124},{},[125,127,132],{"type":56,"value":126},"the model class's ",{"type":50,"tag":86,"props":128,"children":129},{},[130],{"type":56,"value":131},"docstrings and signatures",{"type":56,"value":133}," (forward inputs\u002Foutputs,\nexpected shapes).",{"type":50,"tag":59,"props":135,"children":136},{},[137],{"type":56,"value":138},"Use these to pin down four things the wrapper must mirror exactly:",{"type":50,"tag":94,"props":140,"children":141},{},[142,152,162,172],{"type":50,"tag":98,"props":143,"children":144},{},[145,150],{"type":50,"tag":86,"props":146,"children":147},{},[148],{"type":56,"value":149},"Normalization",{"type":56,"value":151}," — which scheme and which stats (see Normalization\nbelow).",{"type":50,"tag":98,"props":153,"children":154},{},[155,160],{"type":50,"tag":86,"props":156,"children":157},{},[158],{"type":56,"value":159},"Inputs",{"type":56,"value":161}," — what the model actually consumes (coordinates only, extra\nfields, geometry\u002FSTL).",{"type":50,"tag":98,"props":163,"children":164},{},[165,170],{"type":50,"tag":86,"props":166,"children":167},{},[168],{"type":56,"value":169},"Output fields and order",{"type":56,"value":171}," — which variables the forward pass returns\nand in what channel order.",{"type":50,"tag":98,"props":173,"children":174},{},[175,180,182,188,190,196],{"type":50,"tag":86,"props":176,"children":177},{},[178],{"type":56,"value":179},"I\u002FO shapes and dtypes",{"type":56,"value":181}," — so ",{"type":50,"tag":65,"props":183,"children":185},{"className":184},[],[186],{"type":56,"value":187},"prepare_inputs",{"type":56,"value":189},"\u002F",{"type":50,"tag":65,"props":191,"children":193},{"className":192},[],[194],{"type":56,"value":195},"decode_outputs",{"type":56,"value":197}," match\nthe trained graph.",{"type":50,"tag":59,"props":199,"children":200},{},[201,203,208,210,215,217,223,225,230],{"type":56,"value":202},"If some or all of these aren't available, that's fine — ",{"type":50,"tag":86,"props":204,"children":205},{},[206],{"type":56,"value":207},"proceed\nanyway",{"type":56,"value":209},": reconstruct from the checkpoint and stats file, pick the most\nlikely option, and build the wrapper now. Do ",{"type":50,"tag":86,"props":211,"children":212},{},[213],{"type":56,"value":214},"not",{"type":56,"value":216}," stop and wait for\nanswers before writing code. Just avoid ",{"type":50,"tag":218,"props":219,"children":220},"em",{},[221],{"type":56,"value":222},"silently",{"type":56,"value":224}," guessing: a wrong\nnormalization scheme or channel order produces plausible-looking but\nwrong predictions. So state each such assumption inline, and after\ndelivering the wrapper, ",{"type":50,"tag":86,"props":226,"children":227},{},[228],{"type":56,"value":229},"raise the still-uncertain choices as explicit\nopen items for the user to confirm",{"type":56,"value":231}," — e.g. normalization scheme, input\ntier, and output fields\u002Fchannel order. This keeps you unblocked while\nmaking the risky decisions visible.",{"type":50,"tag":74,"props":233,"children":235},{"id":234},"reference-files-to-read-first",[236],{"type":56,"value":237},"Reference files to read first",{"type":50,"tag":59,"props":239,"children":240},{},[241],{"type":56,"value":242},"Start with the complete, ready-to-adapt templates bundled with this skill\n— they are always available even when the PhysicsNeMo-CFD source tree is\nnot on disk:",{"type":50,"tag":94,"props":244,"children":245},{},[246,271],{"type":50,"tag":98,"props":247,"children":248},{},[249,255,257,262,264,269],{"type":50,"tag":65,"props":250,"children":252},{"className":251},[],[253],{"type":56,"value":254},"references\u002Fexample_wrapper.py",{"type":56,"value":256}," — full surface ",{"type":50,"tag":86,"props":258,"children":259},{},[260],{"type":56,"value":261},"and",{"type":56,"value":263}," volume\n",{"type":50,"tag":65,"props":265,"children":267},{"className":266},[],[268],{"type":56,"value":70},{"type":56,"value":270}," reference implementations (load, prepare_inputs, predict,\ndecode_outputs, registration). Copy and adapt one of these.",{"type":50,"tag":98,"props":272,"children":273},{},[274,280],{"type":50,"tag":65,"props":275,"children":277},{"className":276},[],[278],{"type":56,"value":279},"assets\u002Fglobal_stats.example.json",{"type":56,"value":281}," — sample mean\u002Fstd stats for both\nsurface and volume.",{"type":50,"tag":59,"props":283,"children":284},{},[285,287,292],{"type":56,"value":286},"When the PhysicsNeMo-CFD repo ",{"type":50,"tag":218,"props":288,"children":289},{},[290],{"type":56,"value":291},"is",{"type":56,"value":293}," present, also read these for the live\ninterface (verify paths against the actual tree):",{"type":50,"tag":94,"props":295,"children":296},{},[297,308,333,344,355,366,377],{"type":50,"tag":98,"props":298,"children":299},{},[300,306],{"type":50,"tag":65,"props":301,"children":303},{"className":302},[],[304],{"type":56,"value":305},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fmodel_registry.py",{"type":56,"value":307}," — base class and\nregistry",{"type":50,"tag":98,"props":309,"children":310},{},[311,317,319,325,327],{"type":50,"tag":65,"props":312,"children":314},{"className":313},[],[315],{"type":56,"value":316},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fdatasets\u002Fschema.py",{"type":56,"value":318}," — ",{"type":50,"tag":65,"props":320,"children":322},{"className":321},[],[323],{"type":56,"value":324},"CanonicalCase",{"type":56,"value":326},",\n",{"type":50,"tag":65,"props":328,"children":330},{"className":329},[],[331],{"type":56,"value":332},"build_predictions_dict",{"type":50,"tag":98,"props":334,"children":335},{},[336,342],{"type":50,"tag":65,"props":337,"children":339},{"className":338},[],[340],{"type":56,"value":341},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fwrappers\u002Fsurface_baseline.py",{"type":56,"value":343}," —\nsimplest concrete surface wrapper",{"type":50,"tag":98,"props":345,"children":346},{},[347,353],{"type":50,"tag":65,"props":348,"children":350},{"className":349},[],[351],{"type":56,"value":352},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fwrappers\u002Fvolume_baseline.py",{"type":56,"value":354}," —\nsimplest concrete volume wrapper",{"type":50,"tag":98,"props":356,"children":357},{},[358,364],{"type":50,"tag":65,"props":359,"children":361},{"className":360},[],[362],{"type":56,"value":363},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fwrappers\u002F__init__.py",{"type":56,"value":365}," — how wrappers\nare registered",{"type":50,"tag":98,"props":367,"children":368},{},[369,375],{"type":50,"tag":65,"props":370,"children":372},{"className":371},[],[373],{"type":56,"value":374},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fcommon\u002Fio.py",{"type":56,"value":376}," — mesh loading and\nnormalization stats helpers",{"type":50,"tag":98,"props":378,"children":379},{},[380,386],{"type":50,"tag":65,"props":381,"children":383},{"className":382},[],[384],{"type":56,"value":385},"workflows\u002Fbenchmarking\u002Fnotebooks\u002Fadding_a_new_model.ipynb",{"type":56,"value":387}," —\nend-to-end tutorial",{"type":50,"tag":74,"props":389,"children":391},{"id":390},"the-cfdmodel-interface",[392,394,399],{"type":56,"value":393},"The ",{"type":50,"tag":65,"props":395,"children":397},{"className":396},[],[398],{"type":56,"value":70},{"type":56,"value":400}," interface",{"type":50,"tag":59,"props":402,"children":403},{},[404],{"type":56,"value":405},"Every wrapper must set two class variables and implement four methods:",{"type":50,"tag":407,"props":408,"children":409},"table",{},[410,429],{"type":50,"tag":411,"props":412,"children":413},"thead",{},[414],{"type":50,"tag":415,"props":416,"children":417},"tr",{},[418,424],{"type":50,"tag":419,"props":420,"children":421},"th",{},[422],{"type":56,"value":423},"Member",{"type":50,"tag":419,"props":425,"children":426},{},[427],{"type":56,"value":428},"Purpose",{"type":50,"tag":430,"props":431,"children":432},"tbody",{},[433,465,495,519,542,559,576],{"type":50,"tag":415,"props":434,"children":435},{},[436,446],{"type":50,"tag":437,"props":438,"children":439},"td",{},[440],{"type":50,"tag":65,"props":441,"children":443},{"className":442},[],[444],{"type":56,"value":445},"INFERENCE_DOMAIN",{"type":50,"tag":437,"props":447,"children":448},{},[449,455,457,463],{"type":50,"tag":65,"props":450,"children":452},{"className":451},[],[453],{"type":56,"value":454},"\"surface\"",{"type":56,"value":456}," or ",{"type":50,"tag":65,"props":458,"children":460},{"className":459},[],[461],{"type":56,"value":462},"\"volume\"",{"type":56,"value":464}," — which mesh manifold",{"type":50,"tag":415,"props":466,"children":467},{},[468,477],{"type":50,"tag":437,"props":469,"children":470},{},[471],{"type":50,"tag":65,"props":472,"children":474},{"className":473},[],[475],{"type":56,"value":476},"OUTPUT_LOCATION",{"type":50,"tag":437,"props":478,"children":479},{},[480,486,487,493],{"type":50,"tag":65,"props":481,"children":483},{"className":482},[],[484],{"type":56,"value":485},"\"point\"",{"type":56,"value":456},{"type":50,"tag":65,"props":488,"children":490},{"className":489},[],[491],{"type":56,"value":492},"\"cell\"",{"type":56,"value":494}," — where predictions live on the mesh",{"type":50,"tag":415,"props":496,"children":497},{},[498,509],{"type":50,"tag":437,"props":499,"children":500},{},[501,507],{"type":50,"tag":65,"props":502,"children":504},{"className":503},[],[505],{"type":56,"value":506},"output_location",{"type":56,"value":508}," (property)",{"type":50,"tag":437,"props":510,"children":511},{},[512,514],{"type":56,"value":513},"Instance-level access to ",{"type":50,"tag":65,"props":515,"children":517},{"className":516},[],[518],{"type":56,"value":476},{"type":50,"tag":415,"props":520,"children":521},{},[522,531],{"type":50,"tag":437,"props":523,"children":524},{},[525],{"type":50,"tag":65,"props":526,"children":528},{"className":527},[],[529],{"type":56,"value":530},"load(checkpoint_path, stats_path, device, **kwargs)",{"type":50,"tag":437,"props":532,"children":533},{},[534,536],{"type":56,"value":535},"Load weights and stats; return ",{"type":50,"tag":65,"props":537,"children":539},{"className":538},[],[540],{"type":56,"value":541},"self",{"type":50,"tag":415,"props":543,"children":544},{},[545,554],{"type":50,"tag":437,"props":546,"children":547},{},[548],{"type":50,"tag":65,"props":549,"children":551},{"className":550},[],[552],{"type":56,"value":553},"prepare_inputs(case: CanonicalCase)",{"type":50,"tag":437,"props":555,"children":556},{},[557],{"type":56,"value":558},"Convert canonical case into model-specific tensors\u002Fgraphs",{"type":50,"tag":415,"props":560,"children":561},{},[562,571],{"type":50,"tag":437,"props":563,"children":564},{},[565],{"type":50,"tag":65,"props":566,"children":568},{"className":567},[],[569],{"type":56,"value":570},"predict(model_input)",{"type":50,"tag":437,"props":572,"children":573},{},[574],{"type":56,"value":575},"Run forward pass; return raw output",{"type":50,"tag":415,"props":577,"children":578},{},[579,588],{"type":50,"tag":437,"props":580,"children":581},{},[582],{"type":50,"tag":65,"props":583,"children":585},{"className":584},[],[586],{"type":56,"value":587},"decode_outputs(raw_output, case, model_input=None)",{"type":50,"tag":437,"props":589,"children":590},{},[591],{"type":56,"value":592},"Denormalize and map to canonical predictions dict",{"type":50,"tag":59,"props":594,"children":595},{},[596,598,604,606,612,614,620,622,627],{"type":56,"value":597},"The engine calls ",{"type":50,"tag":65,"props":599,"children":601},{"className":600},[],[602],{"type":56,"value":603},"load",{"type":56,"value":605}," once, then ",{"type":50,"tag":65,"props":607,"children":609},{"className":608},[],[610],{"type":56,"value":611},"prepare_inputs → predict → decode_outputs(raw, case, model_input)",{"type":56,"value":613}," per case (",{"type":50,"tag":65,"props":615,"children":617},{"className":616},[],[618],{"type":56,"value":619},"model_input",{"type":56,"value":621}," is the\ndict from ",{"type":50,"tag":65,"props":623,"children":625},{"className":624},[],[626],{"type":56,"value":187},{"type":56,"value":628},"; use when decode must mirror inference\ngeometry).",{"type":50,"tag":74,"props":630,"children":632},{"id":631},"step-1-write-the-wrapper-class",[633],{"type":56,"value":634},"Step 1: Write the wrapper class",{"type":50,"tag":59,"props":636,"children":637},{},[638,643,645,651,653,658,660,666,668,674],{"type":50,"tag":86,"props":639,"children":640},{},[641],{"type":56,"value":642},"Always generate a new, complete wrapper class for the requested\nmodel.",{"type":56,"value":644}," Existing wrappers (e.g. ",{"type":50,"tag":65,"props":646,"children":648},{"className":647},[],[649],{"type":56,"value":650},"surface_baseline.py",{"type":56,"value":652},") are ",{"type":50,"tag":218,"props":654,"children":655},{},[656],{"type":56,"value":657},"references\nto read",{"type":56,"value":659},", not substitutes — when the user asks to write or create a\nwrapper, produce a full new class file even if similar ones already\nexist. Do not stop at \"a wrapper already exists\" or offer to reuse one\nin place of writing the requested one. Always tell the user how to\nregister it: ",{"type":50,"tag":65,"props":661,"children":663},{"className":662},[],[664],{"type":56,"value":665},"register_model(...)",{"type":56,"value":667}," at import for a quick test, and an\nentry in ",{"type":50,"tag":65,"props":669,"children":671},{"className":670},[],[672],{"type":56,"value":673},"wrappers\u002F__init__.py",{"type":56,"value":675}," to make it permanent (Step 6).",{"type":50,"tag":677,"props":678,"children":680},"h3",{"id":679},"anti-patterns-do-not-do-these",[681],{"type":56,"value":682},"Anti-patterns (do not do these)",{"type":50,"tag":94,"props":684,"children":685},{},[686,704,714,746,762],{"type":50,"tag":98,"props":687,"children":688},{},[689,694,696,702],{"type":50,"tag":86,"props":690,"children":691},{},[692],{"type":56,"value":693},"Reusing an existing wrapper instead of writing the requested one",{"type":56,"value":695}," —\neven if ",{"type":50,"tag":65,"props":697,"children":699},{"className":698},[],[700],{"type":56,"value":701},"git status",{"type":56,"value":703}," shows a similar file, write the new class.",{"type":50,"tag":98,"props":705,"children":706},{},[707,712],{"type":50,"tag":86,"props":708,"children":709},{},[710],{"type":56,"value":711},"Assuming mean-std normalization",{"type":56,"value":713}," — confirm the scheme; a wrong\ninverse gives plausible-but-wrong fields.",{"type":50,"tag":98,"props":715,"children":716},{},[717,736,738,744],{"type":50,"tag":86,"props":718,"children":719},{},[720,722,728,730],{"type":56,"value":721},"Hardcoding ",{"type":50,"tag":65,"props":723,"children":725},{"className":724},[],[726],{"type":56,"value":727},"pressure",{"type":56,"value":729}," + ",{"type":50,"tag":65,"props":731,"children":733},{"className":732},[],[734],{"type":56,"value":735},"shear_stress",{"type":56,"value":737}," — pass only the fields the\nmodel predicts; add custom ones via ",{"type":50,"tag":65,"props":739,"children":741},{"className":740},[],[742],{"type":56,"value":743},"**extra",{"type":56,"value":745},".",{"type":50,"tag":98,"props":747,"children":748},{},[749,760],{"type":50,"tag":86,"props":750,"children":751},{},[752,754],{"type":56,"value":753},"Skipping ",{"type":50,"tag":65,"props":755,"children":757},{"className":756},[],[758],{"type":56,"value":759},"__init__.py",{"type":56,"value":761}," — registering only inline and never\nmentioning permanent registration.",{"type":50,"tag":98,"props":763,"children":764},{},[765,770],{"type":50,"tag":86,"props":766,"children":767},{},[768],{"type":56,"value":769},"Echoing the interface table or full reference file back",{"type":56,"value":771}," — wastes\ntokens; reference, don't repeat.",{"type":50,"tag":59,"props":773,"children":774},{},[775,787],{"type":50,"tag":86,"props":776,"children":777},{},[778,780,785],{"type":56,"value":779},"Copy ",{"type":50,"tag":65,"props":781,"children":783},{"className":782},[],[784],{"type":56,"value":254},{"type":56,"value":786}," and adapt it",{"type":56,"value":788}," — it has full\nsurface and volume implementations. Don't hand-write from scratch or\npaste the whole template back to the user. The class skeleton is just:",{"type":50,"tag":790,"props":791,"children":796},"pre",{"className":792,"code":793,"language":794,"meta":795,"style":795},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","class MyModelWrapper(CFDModel):\n    INFERENCE_DOMAIN: ClassVar[InferenceDomain] = \"surface\"  # or \"volume\"\n    OUTPUT_LOCATION: ClassVar[OutputLocation] = \"cell\"        # or \"point\"\n\n    @property\n    def output_location(self): return self.OUTPUT_LOCATION\n    def load(self, checkpoint_path, stats_path, device, **kwargs): ...   # weights + stats; return self\n    def prepare_inputs(self, case): ...                                  # CanonicalCase -> model input\n    def predict(self, model_input): ...                                  # forward pass -> raw output\n    def decode_outputs(self, raw_output, case, model_input=None): ...    # denormalize -> build_predictions_dict(...)\n","python","",[797],{"type":50,"tag":65,"props":798,"children":799},{"__ignoreMap":795},[800,811,820,829,839,848,857,866,875,884],{"type":50,"tag":801,"props":802,"children":805},"span",{"class":803,"line":804},"line",1,[806],{"type":50,"tag":801,"props":807,"children":808},{},[809],{"type":56,"value":810},"class MyModelWrapper(CFDModel):\n",{"type":50,"tag":801,"props":812,"children":814},{"class":803,"line":813},2,[815],{"type":50,"tag":801,"props":816,"children":817},{},[818],{"type":56,"value":819},"    INFERENCE_DOMAIN: ClassVar[InferenceDomain] = \"surface\"  # or \"volume\"\n",{"type":50,"tag":801,"props":821,"children":823},{"class":803,"line":822},3,[824],{"type":50,"tag":801,"props":825,"children":826},{},[827],{"type":56,"value":828},"    OUTPUT_LOCATION: ClassVar[OutputLocation] = \"cell\"        # or \"point\"\n",{"type":50,"tag":801,"props":830,"children":832},{"class":803,"line":831},4,[833],{"type":50,"tag":801,"props":834,"children":836},{"emptyLinePlaceholder":835},true,[837],{"type":56,"value":838},"\n",{"type":50,"tag":801,"props":840,"children":842},{"class":803,"line":841},5,[843],{"type":50,"tag":801,"props":844,"children":845},{},[846],{"type":56,"value":847},"    @property\n",{"type":50,"tag":801,"props":849,"children":851},{"class":803,"line":850},6,[852],{"type":50,"tag":801,"props":853,"children":854},{},[855],{"type":56,"value":856},"    def output_location(self): return self.OUTPUT_LOCATION\n",{"type":50,"tag":801,"props":858,"children":860},{"class":803,"line":859},7,[861],{"type":50,"tag":801,"props":862,"children":863},{},[864],{"type":56,"value":865},"    def load(self, checkpoint_path, stats_path, device, **kwargs): ...   # weights + stats; return self\n",{"type":50,"tag":801,"props":867,"children":869},{"class":803,"line":868},8,[870],{"type":50,"tag":801,"props":871,"children":872},{},[873],{"type":56,"value":874},"    def prepare_inputs(self, case): ...                                  # CanonicalCase -> model input\n",{"type":50,"tag":801,"props":876,"children":878},{"class":803,"line":877},9,[879],{"type":50,"tag":801,"props":880,"children":881},{},[882],{"type":56,"value":883},"    def predict(self, model_input): ...                                  # forward pass -> raw output\n",{"type":50,"tag":801,"props":885,"children":887},{"class":803,"line":886},10,[888],{"type":50,"tag":801,"props":889,"children":890},{},[891],{"type":56,"value":892},"    def decode_outputs(self, raw_output, case, model_input=None): ...    # denormalize -> build_predictions_dict(...)\n",{"type":50,"tag":59,"props":894,"children":895},{},[896],{"type":56,"value":897},"Keep responses terse: state the few model-specific decisions\n(normalization scheme, input tier, output fields) and the file you\nwrote — don't echo the interface table or the full reference file back.",{"type":50,"tag":677,"props":899,"children":901},{"id":900},"key-implementation-considerations",[902],{"type":56,"value":903},"Key implementation considerations",{"type":50,"tag":59,"props":905,"children":906},{},[907,911,913,918,920,925],{"type":50,"tag":86,"props":908,"children":909},{},[910],{"type":56,"value":149},{"type":56,"value":912}," (match the training script exactly): Most trained\nmodels normalize inputs\u002Foutputs, and ",{"type":50,"tag":65,"props":914,"children":916},{"className":915},[],[917],{"type":56,"value":195},{"type":56,"value":919}," must apply the\n",{"type":50,"tag":218,"props":921,"children":922},{},[923],{"type":56,"value":924},"inverse",{"type":56,"value":926}," of whatever the model was trained with. First identify the\nscheme:",{"type":50,"tag":94,"props":928,"children":929},{},[930,1000],{"type":50,"tag":98,"props":931,"children":932},{},[933,938,940,946,948,954,956,962,964,969,971,977,978,984,986,991,992,998],{"type":50,"tag":86,"props":934,"children":935},{},[936],{"type":56,"value":937},"Mean-std (z-score)",{"type":56,"value":939},": ",{"type":50,"tag":65,"props":941,"children":943},{"className":942},[],[944],{"type":56,"value":945},"x_norm = (x - mean) \u002F std",{"type":56,"value":947}," → inverse\n",{"type":50,"tag":65,"props":949,"children":951},{"className":950},[],[952],{"type":56,"value":953},"x = x_norm * std + mean",{"type":56,"value":955},". This is the repo's built-in format. Use\n",{"type":50,"tag":65,"props":957,"children":959},{"className":958},[],[960],{"type":56,"value":961},"load_global_stats(stats_path)",{"type":56,"value":963}," from\n",{"type":50,"tag":65,"props":965,"children":967},{"className":966},[],[968],{"type":56,"value":374},{"type":56,"value":970},"; it reads ",{"type":50,"tag":65,"props":972,"children":974},{"className":973},[],[975],{"type":56,"value":976},"mean",{"type":56,"value":189},{"type":50,"tag":65,"props":979,"children":981},{"className":980},[],[982],{"type":56,"value":983},"std_dev",{"type":56,"value":985},"\nJSON and returns ",{"type":50,"tag":65,"props":987,"children":989},{"className":988},[],[990],{"type":56,"value":976},{"type":56,"value":189},{"type":50,"tag":65,"props":993,"children":995},{"className":994},[],[996],{"type":56,"value":997},"std",{"type":56,"value":999}," tensors.",{"type":50,"tag":98,"props":1001,"children":1002},{},[1003,1008,1009,1015,1016,1022,1024,1029,1031,1037,1038,1044,1046,1051,1053,1059],{"type":50,"tag":86,"props":1004,"children":1005},{},[1006],{"type":56,"value":1007},"Min-max",{"type":56,"value":939},{"type":50,"tag":65,"props":1010,"children":1012},{"className":1011},[],[1013],{"type":56,"value":1014},"x_norm = (x - min) \u002F (max - min)",{"type":56,"value":947},{"type":50,"tag":65,"props":1017,"children":1019},{"className":1018},[],[1020],{"type":56,"value":1021},"x = x_norm * (max - min) + min",{"type":56,"value":1023},". There is ",{"type":50,"tag":86,"props":1025,"children":1026},{},[1027],{"type":56,"value":1028},"no built-in helper",{"type":56,"value":1030}," for\nthis — store ",{"type":50,"tag":65,"props":1032,"children":1034},{"className":1033},[],[1035],{"type":56,"value":1036},"min",{"type":56,"value":189},{"type":50,"tag":65,"props":1039,"children":1041},{"className":1040},[],[1042],{"type":56,"value":1043},"max",{"type":56,"value":1045}," (e.g. in your stats JSON) and apply the\ninverse yourself in ",{"type":50,"tag":65,"props":1047,"children":1049},{"className":1048},[],[1050],{"type":56,"value":195},{"type":56,"value":1052},". Do not feed a min-max file to\n",{"type":50,"tag":65,"props":1054,"children":1056},{"className":1055},[],[1057],{"type":56,"value":1058},"load_global_stats",{"type":56,"value":1060},"; the keys won't match.",{"type":50,"tag":59,"props":1062,"children":1063},{},[1064],{"type":56,"value":1065},"Confirm the scheme from the training\u002Finference script or the stats file\nrather than assuming mean-std. Applying the wrong inverse yields\nwrong-but-plausible fields that still pass shape checks.",{"type":50,"tag":59,"props":1067,"children":1068},{},[1069,1073,1075,1080,1082,1087],{"type":50,"tag":86,"props":1070,"children":1071},{},[1072],{"type":56,"value":159},{"type":56,"value":1074}," (handle the model's actual input tier): ",{"type":50,"tag":65,"props":1076,"children":1078},{"className":1077},[],[1079],{"type":56,"value":187},{"type":56,"value":1081},"\nreceives a ",{"type":50,"tag":65,"props":1083,"children":1085},{"className":1084},[],[1086],{"type":56,"value":324},{"type":56,"value":1088},". Pull what the model needs:",{"type":50,"tag":94,"props":1090,"children":1091},{},[1092,1118,1144],{"type":50,"tag":98,"props":1093,"children":1094},{},[1095,1100,1102,1108,1110,1116],{"type":50,"tag":86,"props":1096,"children":1097},{},[1098],{"type":56,"value":1099},"Point cloud only",{"type":56,"value":1101},": coordinates from ",{"type":50,"tag":65,"props":1103,"children":1105},{"className":1104},[],[1106],{"type":56,"value":1107},"case.mesh_path",{"type":56,"value":1109}," (vtp\u002Fvtu) via\n",{"type":50,"tag":65,"props":1111,"children":1113},{"className":1112},[],[1114],{"type":56,"value":1115},"pv.read",{"type":56,"value":1117},", as in the example — sufficient for many geometry-only\nmodels.",{"type":50,"tag":98,"props":1119,"children":1120},{},[1121,1126,1128,1134,1136,1142],{"type":50,"tag":86,"props":1122,"children":1123},{},[1124],{"type":56,"value":1125},"Extra field inputs",{"type":56,"value":1127}," (e.g. inlet\u002Ffreestream velocity, Reynolds\nnumber): read from ",{"type":50,"tag":65,"props":1129,"children":1131},{"className":1130},[],[1132],{"type":56,"value":1133},"case.metadata",{"type":56,"value":1135}," (or ",{"type":50,"tag":65,"props":1137,"children":1139},{"className":1138},[],[1140],{"type":56,"value":1141},"case.ground_truth",{"type":56,"value":1143}," for field\narrays). Broadcast\u002Fconcatenate them onto the per-point features as the\ntraining script did.",{"type":50,"tag":98,"props":1145,"children":1146},{},[1147,1152,1154,1159,1161,1166],{"type":50,"tag":86,"props":1148,"children":1149},{},[1150],{"type":56,"value":1151},"Geometry\u002FSTL",{"type":56,"value":1153}," (e.g. SDF or BVH-based models): the STL\u002Fgeometry path\nis typically on ",{"type":50,"tag":65,"props":1155,"children":1157},{"className":1156},[],[1158],{"type":56,"value":1133},{"type":56,"value":1160},"; load it in ",{"type":50,"tag":65,"props":1162,"children":1164},{"className":1163},[],[1165],{"type":56,"value":187},{"type":56,"value":1167}," and build\nthe geometric encoding the model expects.",{"type":50,"tag":59,"props":1169,"children":1170},{},[1171,1173,1178,1180,1185],{"type":56,"value":1172},"Inspect ",{"type":50,"tag":65,"props":1174,"children":1176},{"className":1175},[],[1177],{"type":56,"value":1133},{"type":56,"value":1179}," and ",{"type":50,"tag":65,"props":1181,"children":1183},{"className":1182},[],[1184],{"type":56,"value":1141},{"type":56,"value":1186}," keys for a real case\nearly — the dataset adapter decides what is available.",{"type":50,"tag":59,"props":1188,"children":1189},{},[1190,1195,1197,1202,1204,1209,1211,1216,1217,1223,1224,1230,1232,1244],{"type":50,"tag":86,"props":1191,"children":1192},{},[1193],{"type":56,"value":1194},"Outputs",{"type":56,"value":1196}," (predict only what the model produces, plus extras):\n",{"type":50,"tag":65,"props":1198,"children":1200},{"className":1199},[],[1201],{"type":56,"value":332},{"type":56,"value":1203}," takes ",{"type":50,"tag":65,"props":1205,"children":1207},{"className":1206},[],[1208],{"type":56,"value":727},{"type":56,"value":1210},", ",{"type":50,"tag":65,"props":1212,"children":1214},{"className":1213},[],[1215],{"type":56,"value":735},{"type":56,"value":1210},{"type":50,"tag":65,"props":1218,"children":1220},{"className":1219},[],[1221],{"type":56,"value":1222},"velocity",{"type":56,"value":326},{"type":50,"tag":65,"props":1225,"children":1227},{"className":1226},[],[1228],{"type":56,"value":1229},"turbulent_viscosity",{"type":56,"value":1231}," (all optional) ",{"type":50,"tag":86,"props":1233,"children":1234},{},[1235,1237,1242],{"type":56,"value":1236},"and arbitrary ",{"type":50,"tag":65,"props":1238,"children":1240},{"className":1239},[],[1241],{"type":56,"value":743},{"type":56,"value":1243},"\nfields",{"type":56,"value":1245},". So:",{"type":50,"tag":94,"props":1247,"children":1248},{},[1249,1254,1289],{"type":50,"tag":98,"props":1250,"children":1251},{},[1252],{"type":56,"value":1253},"A model that predicts only WSS magnitude, or no WSS at all, simply\nomits the missing keys — pass only what it produces.",{"type":50,"tag":98,"props":1255,"children":1256},{},[1257,1259,1265,1266,1272,1273,1279,1281,1287],{"type":56,"value":1258},"Extra\u002Fnon-standard outputs (e.g. ",{"type":50,"tag":65,"props":1260,"children":1262},{"className":1261},[],[1263],{"type":56,"value":1264},"stagnation_pressure",{"type":56,"value":1210},{"type":50,"tag":65,"props":1267,"children":1269},{"className":1268},[],[1270],{"type":56,"value":1271},"temperature",{"type":56,"value":326},{"type":50,"tag":65,"props":1274,"children":1276},{"className":1275},[],[1277],{"type":56,"value":1278},"mach",{"type":56,"value":1280},") are passed as keyword args: ",{"type":50,"tag":65,"props":1282,"children":1284},{"className":1283},[],[1285],{"type":56,"value":1286},"build_predictions_dict(pressure=p, mach=m, temperature=t)",{"type":56,"value":1288},". Each becomes a prediction variable.",{"type":50,"tag":98,"props":1290,"children":1291},{},[1292,1294,1300],{"type":56,"value":1293},"For a custom field to appear in the written mesh and metrics, add a\nmatching entry to ",{"type":50,"tag":65,"props":1295,"children":1297},{"className":1296},[],[1298],{"type":56,"value":1299},"output.mesh_field_names",{"type":56,"value":1301}," in the config (Step 4) and\na corresponding metric if you want it scored.",{"type":50,"tag":59,"props":1303,"children":1304},{},[1305,1310,1311,1316,1318,1324,1326,1331,1333,1339,1341,1346,1348,1353,1354,1359,1360,1365,1367,1372,1374,1380],{"type":50,"tag":86,"props":1306,"children":1307},{},[1308],{"type":56,"value":1309},"Output shape",{"type":56,"value":939},{"type":50,"tag":65,"props":1312,"children":1314},{"className":1313},[],[1315],{"type":56,"value":727},{"type":56,"value":1317}," must be ",{"type":50,"tag":65,"props":1319,"children":1321},{"className":1320},[],[1322],{"type":56,"value":1323},"(N,)",{"type":56,"value":1325}," float32. ",{"type":50,"tag":65,"props":1327,"children":1329},{"className":1328},[],[1330],{"type":56,"value":735},{"type":56,"value":1332}," must\nbe ",{"type":50,"tag":65,"props":1334,"children":1336},{"className":1335},[],[1337],{"type":56,"value":1338},"(N, 3)",{"type":56,"value":1340}," float32 for surface. Volume fields: ",{"type":50,"tag":65,"props":1342,"children":1344},{"className":1343},[],[1345],{"type":56,"value":1222},{"type":56,"value":1347}," is ",{"type":50,"tag":65,"props":1349,"children":1351},{"className":1350},[],[1352],{"type":56,"value":1338},{"type":56,"value":326},{"type":50,"tag":65,"props":1355,"children":1357},{"className":1356},[],[1358],{"type":56,"value":1229},{"type":56,"value":1347},{"type":50,"tag":65,"props":1361,"children":1363},{"className":1362},[],[1364],{"type":56,"value":1323},{"type":56,"value":1366},". Custom scalar fields are ",{"type":50,"tag":65,"props":1368,"children":1370},{"className":1369},[],[1371],{"type":56,"value":1323},{"type":56,"value":1373},", vector\nfields ",{"type":50,"tag":65,"props":1375,"children":1377},{"className":1376},[],[1378],{"type":56,"value":1379},"(N, k)",{"type":56,"value":745},{"type":50,"tag":59,"props":1382,"children":1383},{},[1384,1389,1391,1397,1399,1405,1407,1412,1414,1420],{"type":50,"tag":86,"props":1385,"children":1386},{},[1387],{"type":56,"value":1388},"Output location",{"type":56,"value":1390},": If ",{"type":50,"tag":65,"props":1392,"children":1394},{"className":1393},[],[1395],{"type":56,"value":1396},"OUTPUT_LOCATION = \"cell\"",{"type":56,"value":1398},", return N =\n",{"type":50,"tag":65,"props":1400,"children":1402},{"className":1401},[],[1403],{"type":56,"value":1404},"mesh.n_cells",{"type":56,"value":1406}," values. If ",{"type":50,"tag":65,"props":1408,"children":1410},{"className":1409},[],[1411],{"type":56,"value":485},{"type":56,"value":1413},", return N = ",{"type":50,"tag":65,"props":1415,"children":1417},{"className":1416},[],[1418],{"type":56,"value":1419},"mesh.n_points",{"type":56,"value":1421}," values.",{"type":50,"tag":59,"props":1423,"children":1424},{},[1425,1430,1432,1437,1439,1445,1447,1453,1455,1461,1462,1468],{"type":50,"tag":86,"props":1426,"children":1427},{},[1428],{"type":56,"value":1429},"Batching",{"type":56,"value":1431},": For large meshes, ",{"type":50,"tag":65,"props":1433,"children":1435},{"className":1434},[],[1436],{"type":56,"value":187},{"type":56,"value":1438}," may need to subsample\nor batch. Use ",{"type":50,"tag":65,"props":1440,"children":1442},{"className":1441},[],[1443],{"type":56,"value":1444},"kwargs",{"type":56,"value":1446}," passed through ",{"type":50,"tag":65,"props":1448,"children":1450},{"className":1449},[],[1451],{"type":56,"value":1452},"load()",{"type":56,"value":1454}," (e.g.,\n",{"type":50,"tag":65,"props":1456,"children":1458},{"className":1457},[],[1459],{"type":56,"value":1460},"batch_resolution",{"type":56,"value":1210},{"type":50,"tag":65,"props":1463,"children":1465},{"className":1464},[],[1466],{"type":56,"value":1467},"geometry_sampling",{"type":56,"value":1469},") to control this.",{"type":50,"tag":74,"props":1471,"children":1473},{"id":1472},"step-2-create-checkpoint-and-stats-files",[1474],{"type":56,"value":1475},"Step 2: Create checkpoint and stats files",{"type":50,"tag":59,"props":1477,"children":1478},{},[1479,1481,1487],{"type":56,"value":1480},"Your model needs a checkpoint file and optionally a ",{"type":50,"tag":65,"props":1482,"children":1484},{"className":1483},[],[1485],{"type":56,"value":1486},"global_stats.json",{"type":56,"value":1488},":",{"type":50,"tag":790,"props":1490,"children":1492},{"className":792,"code":1491,"language":794,"meta":795,"style":795},"# Checkpoint: save your model's state dict\ntorch.save(model.state_dict(), \"checkpoint.pt\")\n\n# Stats: JSON with mean\u002Fstd_dev for denormalization\n# Surface format:\n{\n    \"mean\": {\"pressure\": [0.0], \"shear_stress\": [0.0, 0.0, 0.0]},\n    \"std_dev\": {\"pressure\": [1.0], \"shear_stress\": [1.0, 1.0, 1.0]}\n}\n# Volume format:\n{\n    \"mean\": {\"pressure\": [0.0], \"velocity\": [0.0, 0.0, 0.0], \"turbulent_viscosity\": [0.0]},\n    \"std_dev\": {\"pressure\": [1.0], \"velocity\": [1.0, 1.0, 1.0], \"turbulent_viscosity\": [1.0]}\n}\n",[1493],{"type":50,"tag":65,"props":1494,"children":1495},{"__ignoreMap":795},[1496,1504,1512,1519,1527,1535,1543,1551,1559,1567,1575,1583,1592,1601],{"type":50,"tag":801,"props":1497,"children":1498},{"class":803,"line":804},[1499],{"type":50,"tag":801,"props":1500,"children":1501},{},[1502],{"type":56,"value":1503},"# Checkpoint: save your model's state dict\n",{"type":50,"tag":801,"props":1505,"children":1506},{"class":803,"line":813},[1507],{"type":50,"tag":801,"props":1508,"children":1509},{},[1510],{"type":56,"value":1511},"torch.save(model.state_dict(), \"checkpoint.pt\")\n",{"type":50,"tag":801,"props":1513,"children":1514},{"class":803,"line":822},[1515],{"type":50,"tag":801,"props":1516,"children":1517},{"emptyLinePlaceholder":835},[1518],{"type":56,"value":838},{"type":50,"tag":801,"props":1520,"children":1521},{"class":803,"line":831},[1522],{"type":50,"tag":801,"props":1523,"children":1524},{},[1525],{"type":56,"value":1526},"# Stats: JSON with mean\u002Fstd_dev for denormalization\n",{"type":50,"tag":801,"props":1528,"children":1529},{"class":803,"line":841},[1530],{"type":50,"tag":801,"props":1531,"children":1532},{},[1533],{"type":56,"value":1534},"# Surface format:\n",{"type":50,"tag":801,"props":1536,"children":1537},{"class":803,"line":850},[1538],{"type":50,"tag":801,"props":1539,"children":1540},{},[1541],{"type":56,"value":1542},"{\n",{"type":50,"tag":801,"props":1544,"children":1545},{"class":803,"line":859},[1546],{"type":50,"tag":801,"props":1547,"children":1548},{},[1549],{"type":56,"value":1550},"    \"mean\": {\"pressure\": [0.0], \"shear_stress\": [0.0, 0.0, 0.0]},\n",{"type":50,"tag":801,"props":1552,"children":1553},{"class":803,"line":868},[1554],{"type":50,"tag":801,"props":1555,"children":1556},{},[1557],{"type":56,"value":1558},"    \"std_dev\": {\"pressure\": [1.0], \"shear_stress\": [1.0, 1.0, 1.0]}\n",{"type":50,"tag":801,"props":1560,"children":1561},{"class":803,"line":877},[1562],{"type":50,"tag":801,"props":1563,"children":1564},{},[1565],{"type":56,"value":1566},"}\n",{"type":50,"tag":801,"props":1568,"children":1569},{"class":803,"line":886},[1570],{"type":50,"tag":801,"props":1571,"children":1572},{},[1573],{"type":56,"value":1574},"# Volume format:\n",{"type":50,"tag":801,"props":1576,"children":1578},{"class":803,"line":1577},11,[1579],{"type":50,"tag":801,"props":1580,"children":1581},{},[1582],{"type":56,"value":1542},{"type":50,"tag":801,"props":1584,"children":1586},{"class":803,"line":1585},12,[1587],{"type":50,"tag":801,"props":1588,"children":1589},{},[1590],{"type":56,"value":1591},"    \"mean\": {\"pressure\": [0.0], \"velocity\": [0.0, 0.0, 0.0], \"turbulent_viscosity\": [0.0]},\n",{"type":50,"tag":801,"props":1593,"children":1595},{"class":803,"line":1594},13,[1596],{"type":50,"tag":801,"props":1597,"children":1598},{},[1599],{"type":56,"value":1600},"    \"std_dev\": {\"pressure\": [1.0], \"velocity\": [1.0, 1.0, 1.0], \"turbulent_viscosity\": [1.0]}\n",{"type":50,"tag":801,"props":1602,"children":1604},{"class":803,"line":1603},14,[1605],{"type":50,"tag":801,"props":1606,"children":1607},{},[1608],{"type":56,"value":1566},{"type":50,"tag":59,"props":1610,"children":1611},{},[1612,1614,1619,1620,1625,1627,1633,1635,1640,1642,1647,1648,1653,1655,1660],{"type":56,"value":1613},"This ",{"type":50,"tag":65,"props":1615,"children":1617},{"className":1616},[],[1618],{"type":56,"value":976},{"type":56,"value":189},{"type":50,"tag":65,"props":1621,"children":1623},{"className":1622},[],[1624],{"type":56,"value":983},{"type":56,"value":1626}," layout is what ",{"type":50,"tag":65,"props":1628,"children":1630},{"className":1629},[],[1631],{"type":56,"value":1632},"load_global_stats()",{"type":56,"value":1634}," expects\n(mean-std models). If your model was trained with ",{"type":50,"tag":86,"props":1636,"children":1637},{},[1638],{"type":56,"value":1639},"min-max",{"type":56,"value":1641},"\nnormalization, this helper does not apply — persist ",{"type":50,"tag":65,"props":1643,"children":1645},{"className":1644},[],[1646],{"type":56,"value":1036},{"type":56,"value":189},{"type":50,"tag":65,"props":1649,"children":1651},{"className":1650},[],[1652],{"type":56,"value":1043},{"type":56,"value":1654}," per\nfield in your own JSON and apply the inverse manually in\n",{"type":50,"tag":65,"props":1656,"children":1658},{"className":1657},[],[1659],{"type":56,"value":195},{"type":56,"value":1661}," (see Normalization above).",{"type":50,"tag":74,"props":1663,"children":1665},{"id":1664},"step-3-register-and-test",[1666],{"type":56,"value":1667},"Step 3: Register and test",{"type":50,"tag":790,"props":1669,"children":1671},{"className":792,"code":1670,"language":794,"meta":795,"style":795},"register_model(\"my_model\", MyModelWrapper)\n\n# Load a case from any registered dataset adapter\nfrom physicsnemo.cfd.evaluation.datasets.adapters.drivaerml import DrivAerMLAdapter\nadapter = DrivAerMLAdapter(root=\"\u002Fpath\u002Fto\u002Fdata\", inference_domain=\"surface\")\ncase = adapter.load_case(adapter.list_cases()[0])\n\n# Run the full inference pipeline\nwrapper = MyModelWrapper()\nwrapper.load(checkpoint_path=\"checkpoint.pt\", stats_path=\"global_stats.json\", device=\"cuda:0\")\nmodel_input = wrapper.prepare_inputs(case)\nraw_output = wrapper.predict(model_input)\npredictions = wrapper.decode_outputs(raw_output, case, model_input)\n\nassert \"pressure\" in predictions\nassert predictions[\"pressure\"].shape[0] > 0\n",[1672],{"type":50,"tag":65,"props":1673,"children":1674},{"__ignoreMap":795},[1675,1683,1690,1698,1706,1714,1722,1729,1737,1745,1753,1761,1769,1777,1784,1793],{"type":50,"tag":801,"props":1676,"children":1677},{"class":803,"line":804},[1678],{"type":50,"tag":801,"props":1679,"children":1680},{},[1681],{"type":56,"value":1682},"register_model(\"my_model\", MyModelWrapper)\n",{"type":50,"tag":801,"props":1684,"children":1685},{"class":803,"line":813},[1686],{"type":50,"tag":801,"props":1687,"children":1688},{"emptyLinePlaceholder":835},[1689],{"type":56,"value":838},{"type":50,"tag":801,"props":1691,"children":1692},{"class":803,"line":822},[1693],{"type":50,"tag":801,"props":1694,"children":1695},{},[1696],{"type":56,"value":1697},"# Load a case from any registered dataset adapter\n",{"type":50,"tag":801,"props":1699,"children":1700},{"class":803,"line":831},[1701],{"type":50,"tag":801,"props":1702,"children":1703},{},[1704],{"type":56,"value":1705},"from physicsnemo.cfd.evaluation.datasets.adapters.drivaerml import DrivAerMLAdapter\n",{"type":50,"tag":801,"props":1707,"children":1708},{"class":803,"line":841},[1709],{"type":50,"tag":801,"props":1710,"children":1711},{},[1712],{"type":56,"value":1713},"adapter = DrivAerMLAdapter(root=\"\u002Fpath\u002Fto\u002Fdata\", inference_domain=\"surface\")\n",{"type":50,"tag":801,"props":1715,"children":1716},{"class":803,"line":850},[1717],{"type":50,"tag":801,"props":1718,"children":1719},{},[1720],{"type":56,"value":1721},"case = adapter.load_case(adapter.list_cases()[0])\n",{"type":50,"tag":801,"props":1723,"children":1724},{"class":803,"line":859},[1725],{"type":50,"tag":801,"props":1726,"children":1727},{"emptyLinePlaceholder":835},[1728],{"type":56,"value":838},{"type":50,"tag":801,"props":1730,"children":1731},{"class":803,"line":868},[1732],{"type":50,"tag":801,"props":1733,"children":1734},{},[1735],{"type":56,"value":1736},"# Run the full inference pipeline\n",{"type":50,"tag":801,"props":1738,"children":1739},{"class":803,"line":877},[1740],{"type":50,"tag":801,"props":1741,"children":1742},{},[1743],{"type":56,"value":1744},"wrapper = MyModelWrapper()\n",{"type":50,"tag":801,"props":1746,"children":1747},{"class":803,"line":886},[1748],{"type":50,"tag":801,"props":1749,"children":1750},{},[1751],{"type":56,"value":1752},"wrapper.load(checkpoint_path=\"checkpoint.pt\", stats_path=\"global_stats.json\", device=\"cuda:0\")\n",{"type":50,"tag":801,"props":1754,"children":1755},{"class":803,"line":1577},[1756],{"type":50,"tag":801,"props":1757,"children":1758},{},[1759],{"type":56,"value":1760},"model_input = wrapper.prepare_inputs(case)\n",{"type":50,"tag":801,"props":1762,"children":1763},{"class":803,"line":1585},[1764],{"type":50,"tag":801,"props":1765,"children":1766},{},[1767],{"type":56,"value":1768},"raw_output = wrapper.predict(model_input)\n",{"type":50,"tag":801,"props":1770,"children":1771},{"class":803,"line":1594},[1772],{"type":50,"tag":801,"props":1773,"children":1774},{},[1775],{"type":56,"value":1776},"predictions = wrapper.decode_outputs(raw_output, case, model_input)\n",{"type":50,"tag":801,"props":1778,"children":1779},{"class":803,"line":1603},[1780],{"type":50,"tag":801,"props":1781,"children":1782},{"emptyLinePlaceholder":835},[1783],{"type":56,"value":838},{"type":50,"tag":801,"props":1785,"children":1787},{"class":803,"line":1786},15,[1788],{"type":50,"tag":801,"props":1789,"children":1790},{},[1791],{"type":56,"value":1792},"assert \"pressure\" in predictions\n",{"type":50,"tag":801,"props":1794,"children":1796},{"class":803,"line":1795},16,[1797],{"type":50,"tag":801,"props":1798,"children":1799},{},[1800],{"type":56,"value":1801},"assert predictions[\"pressure\"].shape[0] > 0\n",{"type":50,"tag":74,"props":1803,"children":1805},{"id":1804},"step-4-run-the-full-benchmark",[1806],{"type":56,"value":1807},"Step 4: Run the full benchmark",{"type":50,"tag":790,"props":1809,"children":1811},{"className":792,"code":1810,"language":794,"meta":795,"style":795},"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\", \"metrics_cache\": {\"enabled\": False}},\n    \"benchmark\": {\n        \"mode\": \"matrix\",\n        \"models\": [{\n            \"name\": \"my_model\",\n            \"inference_domain\": \"surface\",\n            \"checkpoint\": \"\u002Fpath\u002Fto\u002Fcheckpoint.pt\",\n            \"stats_path\": \"\u002Fpath\u002Fto\u002Fglobal_stats.json\",\n            \"kwargs\": {},\n        }],\n        \"datasets\": [{\n            \"name\": \"drivaerml\",\n            \"root\": \"\u002Fpath\u002Fto\u002Fdrivaerml\u002Fdata\",\n            \"case_ids\": [\"run_1\", \"run_11\"],\n            \"kwargs\": {\"align_ground_truth_to_model\": True, \"inference_domain\": \"surface\"},\n        }],\n        \"reproducibility\": {\"log_env\": False, \"save_artifacts\": True},\n    },\n    \"output\": {\"mesh_field_names\": {\"pressure\": \"pMeanTrimPred\", \"shear_stress\": \"wallShearStressMeanTrimPred\"}},\n    \"metrics\": [\"l2_pressure\", \"l2_shear_stress\", \"l2_pressure_area_weighted\", \"drag\", \"lift\"],\n    \"reports\": {\"enabled\": False},\n})\nresults = run_benchmark(config)\n",[1812],{"type":50,"tag":65,"props":1813,"children":1814},{"__ignoreMap":795},[1815,1823,1831,1838,1846,1854,1862,1870,1878,1886,1894,1902,1910,1918,1926,1934,1942,1951,1960,1969,1977,1986,1995,2004,2012,2021,2030],{"type":50,"tag":801,"props":1816,"children":1817},{"class":803,"line":804},[1818],{"type":50,"tag":801,"props":1819,"children":1820},{},[1821],{"type":56,"value":1822},"from physicsnemo.cfd.evaluation.config import Config\n",{"type":50,"tag":801,"props":1824,"children":1825},{"class":803,"line":813},[1826],{"type":50,"tag":801,"props":1827,"children":1828},{},[1829],{"type":56,"value":1830},"from physicsnemo.cfd.evaluation.benchmarks.engine import run_benchmark\n",{"type":50,"tag":801,"props":1832,"children":1833},{"class":803,"line":822},[1834],{"type":50,"tag":801,"props":1835,"children":1836},{"emptyLinePlaceholder":835},[1837],{"type":56,"value":838},{"type":50,"tag":801,"props":1839,"children":1840},{"class":803,"line":831},[1841],{"type":50,"tag":801,"props":1842,"children":1843},{},[1844],{"type":56,"value":1845},"config = Config.from_dict({\n",{"type":50,"tag":801,"props":1847,"children":1848},{"class":803,"line":841},[1849],{"type":50,"tag":801,"props":1850,"children":1851},{},[1852],{"type":56,"value":1853},"    \"run\": {\"device\": \"cuda:0\", \"output_dir\": \"results\", \"metrics_cache\": {\"enabled\": False}},\n",{"type":50,"tag":801,"props":1855,"children":1856},{"class":803,"line":850},[1857],{"type":50,"tag":801,"props":1858,"children":1859},{},[1860],{"type":56,"value":1861},"    \"benchmark\": {\n",{"type":50,"tag":801,"props":1863,"children":1864},{"class":803,"line":859},[1865],{"type":50,"tag":801,"props":1866,"children":1867},{},[1868],{"type":56,"value":1869},"        \"mode\": \"matrix\",\n",{"type":50,"tag":801,"props":1871,"children":1872},{"class":803,"line":868},[1873],{"type":50,"tag":801,"props":1874,"children":1875},{},[1876],{"type":56,"value":1877},"        \"models\": [{\n",{"type":50,"tag":801,"props":1879,"children":1880},{"class":803,"line":877},[1881],{"type":50,"tag":801,"props":1882,"children":1883},{},[1884],{"type":56,"value":1885},"            \"name\": \"my_model\",\n",{"type":50,"tag":801,"props":1887,"children":1888},{"class":803,"line":886},[1889],{"type":50,"tag":801,"props":1890,"children":1891},{},[1892],{"type":56,"value":1893},"            \"inference_domain\": \"surface\",\n",{"type":50,"tag":801,"props":1895,"children":1896},{"class":803,"line":1577},[1897],{"type":50,"tag":801,"props":1898,"children":1899},{},[1900],{"type":56,"value":1901},"            \"checkpoint\": \"\u002Fpath\u002Fto\u002Fcheckpoint.pt\",\n",{"type":50,"tag":801,"props":1903,"children":1904},{"class":803,"line":1585},[1905],{"type":50,"tag":801,"props":1906,"children":1907},{},[1908],{"type":56,"value":1909},"            \"stats_path\": \"\u002Fpath\u002Fto\u002Fglobal_stats.json\",\n",{"type":50,"tag":801,"props":1911,"children":1912},{"class":803,"line":1594},[1913],{"type":50,"tag":801,"props":1914,"children":1915},{},[1916],{"type":56,"value":1917},"            \"kwargs\": {},\n",{"type":50,"tag":801,"props":1919,"children":1920},{"class":803,"line":1603},[1921],{"type":50,"tag":801,"props":1922,"children":1923},{},[1924],{"type":56,"value":1925},"        }],\n",{"type":50,"tag":801,"props":1927,"children":1928},{"class":803,"line":1786},[1929],{"type":50,"tag":801,"props":1930,"children":1931},{},[1932],{"type":56,"value":1933},"        \"datasets\": [{\n",{"type":50,"tag":801,"props":1935,"children":1936},{"class":803,"line":1795},[1937],{"type":50,"tag":801,"props":1938,"children":1939},{},[1940],{"type":56,"value":1941},"            \"name\": \"drivaerml\",\n",{"type":50,"tag":801,"props":1943,"children":1945},{"class":803,"line":1944},17,[1946],{"type":50,"tag":801,"props":1947,"children":1948},{},[1949],{"type":56,"value":1950},"            \"root\": \"\u002Fpath\u002Fto\u002Fdrivaerml\u002Fdata\",\n",{"type":50,"tag":801,"props":1952,"children":1954},{"class":803,"line":1953},18,[1955],{"type":50,"tag":801,"props":1956,"children":1957},{},[1958],{"type":56,"value":1959},"            \"case_ids\": [\"run_1\", \"run_11\"],\n",{"type":50,"tag":801,"props":1961,"children":1963},{"class":803,"line":1962},19,[1964],{"type":50,"tag":801,"props":1965,"children":1966},{},[1967],{"type":56,"value":1968},"            \"kwargs\": {\"align_ground_truth_to_model\": True, \"inference_domain\": \"surface\"},\n",{"type":50,"tag":801,"props":1970,"children":1972},{"class":803,"line":1971},20,[1973],{"type":50,"tag":801,"props":1974,"children":1975},{},[1976],{"type":56,"value":1925},{"type":50,"tag":801,"props":1978,"children":1980},{"class":803,"line":1979},21,[1981],{"type":50,"tag":801,"props":1982,"children":1983},{},[1984],{"type":56,"value":1985},"        \"reproducibility\": {\"log_env\": False, \"save_artifacts\": True},\n",{"type":50,"tag":801,"props":1987,"children":1989},{"class":803,"line":1988},22,[1990],{"type":50,"tag":801,"props":1991,"children":1992},{},[1993],{"type":56,"value":1994},"    },\n",{"type":50,"tag":801,"props":1996,"children":1998},{"class":803,"line":1997},23,[1999],{"type":50,"tag":801,"props":2000,"children":2001},{},[2002],{"type":56,"value":2003},"    \"output\": {\"mesh_field_names\": {\"pressure\": \"pMeanTrimPred\", \"shear_stress\": \"wallShearStressMeanTrimPred\"}},\n",{"type":50,"tag":801,"props":2005,"children":2006},{"class":803,"line":30},[2007],{"type":50,"tag":801,"props":2008,"children":2009},{},[2010],{"type":56,"value":2011},"    \"metrics\": [\"l2_pressure\", \"l2_shear_stress\", \"l2_pressure_area_weighted\", \"drag\", \"lift\"],\n",{"type":50,"tag":801,"props":2013,"children":2015},{"class":803,"line":2014},25,[2016],{"type":50,"tag":801,"props":2017,"children":2018},{},[2019],{"type":56,"value":2020},"    \"reports\": {\"enabled\": False},\n",{"type":50,"tag":801,"props":2022,"children":2024},{"class":803,"line":2023},26,[2025],{"type":50,"tag":801,"props":2026,"children":2027},{},[2028],{"type":56,"value":2029},"})\n",{"type":50,"tag":801,"props":2031,"children":2033},{"class":803,"line":2032},27,[2034],{"type":50,"tag":801,"props":2035,"children":2036},{},[2037],{"type":56,"value":2038},"results = run_benchmark(config)\n",{"type":50,"tag":59,"props":2040,"children":2041},{},[2042,2044,2050],{"type":56,"value":2043},"Results are written to ",{"type":50,"tag":65,"props":2045,"children":2047},{"className":2046},[],[2048],{"type":56,"value":2049},"benchmark_results.json",{"type":56,"value":2051}," (a JSON list of dicts,\none per model×dataset combo).",{"type":50,"tag":74,"props":2053,"children":2055},{"id":2054},"step-5-visualize-predictions",[2056],{"type":56,"value":2057},"Step 5: Visualize predictions",{"type":50,"tag":790,"props":2059,"children":2061},{"className":792,"code":2060,"language":794,"meta":795,"style":795},"from physicsnemo.cfd.postprocessing_tools.visualization.utils import plot_fields, plot_field_comparisons\n\n# Just the predicted fields (no GT comparison):\nplotter = plot_fields(mesh, fields=[\"pMeanTrimPred\"], view=\"xy\", dtype=\"cell\", window_size=[1800, 600])\nplotter.screenshot(\"predicted_pressure.png\")\nplotter.close()\n\n# Side-by-side with GT (GT | Pred | Error):\nplotter = plot_field_comparisons(mesh, true_fields=[\"pMeanTrim\"], pred_fields=[\"pMeanTrimPred\"],\n                                  view=\"xy\", dtype=\"cell\", window_size=[1800, 600])\nplotter.screenshot(\"comparison.png\")\nplotter.close()\n",[2062],{"type":50,"tag":65,"props":2063,"children":2064},{"__ignoreMap":795},[2065,2073,2080,2088,2096,2104,2112,2119,2127,2135,2143,2151],{"type":50,"tag":801,"props":2066,"children":2067},{"class":803,"line":804},[2068],{"type":50,"tag":801,"props":2069,"children":2070},{},[2071],{"type":56,"value":2072},"from physicsnemo.cfd.postprocessing_tools.visualization.utils import plot_fields, plot_field_comparisons\n",{"type":50,"tag":801,"props":2074,"children":2075},{"class":803,"line":813},[2076],{"type":50,"tag":801,"props":2077,"children":2078},{"emptyLinePlaceholder":835},[2079],{"type":56,"value":838},{"type":50,"tag":801,"props":2081,"children":2082},{"class":803,"line":822},[2083],{"type":50,"tag":801,"props":2084,"children":2085},{},[2086],{"type":56,"value":2087},"# Just the predicted fields (no GT comparison):\n",{"type":50,"tag":801,"props":2089,"children":2090},{"class":803,"line":831},[2091],{"type":50,"tag":801,"props":2092,"children":2093},{},[2094],{"type":56,"value":2095},"plotter = plot_fields(mesh, fields=[\"pMeanTrimPred\"], view=\"xy\", dtype=\"cell\", window_size=[1800, 600])\n",{"type":50,"tag":801,"props":2097,"children":2098},{"class":803,"line":841},[2099],{"type":50,"tag":801,"props":2100,"children":2101},{},[2102],{"type":56,"value":2103},"plotter.screenshot(\"predicted_pressure.png\")\n",{"type":50,"tag":801,"props":2105,"children":2106},{"class":803,"line":850},[2107],{"type":50,"tag":801,"props":2108,"children":2109},{},[2110],{"type":56,"value":2111},"plotter.close()\n",{"type":50,"tag":801,"props":2113,"children":2114},{"class":803,"line":859},[2115],{"type":50,"tag":801,"props":2116,"children":2117},{"emptyLinePlaceholder":835},[2118],{"type":56,"value":838},{"type":50,"tag":801,"props":2120,"children":2121},{"class":803,"line":868},[2122],{"type":50,"tag":801,"props":2123,"children":2124},{},[2125],{"type":56,"value":2126},"# Side-by-side with GT (GT | Pred | Error):\n",{"type":50,"tag":801,"props":2128,"children":2129},{"class":803,"line":877},[2130],{"type":50,"tag":801,"props":2131,"children":2132},{},[2133],{"type":56,"value":2134},"plotter = plot_field_comparisons(mesh, true_fields=[\"pMeanTrim\"], pred_fields=[\"pMeanTrimPred\"],\n",{"type":50,"tag":801,"props":2136,"children":2137},{"class":803,"line":886},[2138],{"type":50,"tag":801,"props":2139,"children":2140},{},[2141],{"type":56,"value":2142},"                                  view=\"xy\", dtype=\"cell\", window_size=[1800, 600])\n",{"type":50,"tag":801,"props":2144,"children":2145},{"class":803,"line":1577},[2146],{"type":50,"tag":801,"props":2147,"children":2148},{},[2149],{"type":56,"value":2150},"plotter.screenshot(\"comparison.png\")\n",{"type":50,"tag":801,"props":2152,"children":2153},{"class":803,"line":1585},[2154],{"type":50,"tag":801,"props":2155,"children":2156},{},[2157],{"type":56,"value":2111},{"type":50,"tag":74,"props":2159,"children":2161},{"id":2160},"step-6-make-permanent-optional",[2162],{"type":56,"value":2163},"Step 6: Make permanent (optional)",{"type":50,"tag":59,"props":2165,"children":2166},{},[2167,2169,2175,2177,2182],{"type":56,"value":2168},"Save the wrapper to\n",{"type":50,"tag":65,"props":2170,"children":2172},{"className":2171},[],[2173],{"type":56,"value":2174},"physicsnemo\u002Fcfd\u002Fevaluation\u002Fmodels\u002Fwrappers\u002Fmy_model.py",{"type":56,"value":2176}," and register in\n",{"type":50,"tag":65,"props":2178,"children":2180},{"className":2179},[],[2181],{"type":56,"value":673},{"type":56,"value":1488},{"type":50,"tag":790,"props":2184,"children":2186},{"className":792,"code":2185,"language":794,"meta":795,"style":795},"from physicsnemo.cfd.evaluation.models.wrappers.my_model import MyModelWrapper\nregister_model(\"my_model\", MyModelWrapper)\n",[2187],{"type":50,"tag":65,"props":2188,"children":2189},{"__ignoreMap":795},[2190,2198],{"type":50,"tag":801,"props":2191,"children":2192},{"class":803,"line":804},[2193],{"type":50,"tag":801,"props":2194,"children":2195},{},[2196],{"type":56,"value":2197},"from physicsnemo.cfd.evaluation.models.wrappers.my_model import MyModelWrapper\n",{"type":50,"tag":801,"props":2199,"children":2200},{"class":803,"line":813},[2201],{"type":50,"tag":801,"props":2202,"children":2203},{},[2204],{"type":56,"value":1682},{"type":50,"tag":59,"props":2206,"children":2207},{},[2208,2210,2216],{"type":56,"value":2209},"Then use ",{"type":50,"tag":65,"props":2211,"children":2213},{"className":2212},[],[2214],{"type":56,"value":2215},"model.name: my_model",{"type":56,"value":2217}," in any YAML config.",{"type":50,"tag":74,"props":2219,"children":2221},{"id":2220},"gotchas",[2222],{"type":56,"value":2223},"Gotchas",{"type":50,"tag":94,"props":2225,"children":2226},{},[2227,2288,2310,2336,2361],{"type":50,"tag":98,"props":2228,"children":2229},{},[2230,2235,2237,2243,2245,2251,2253,2259,2260,2266,2267,2273,2274,2280,2281,2287],{"type":50,"tag":86,"props":2231,"children":2232},{},[2233],{"type":56,"value":2234},"DistributedManager",{"type":56,"value":2236},": Model wrappers may call\n",{"type":50,"tag":65,"props":2238,"children":2240},{"className":2239},[],[2241],{"type":56,"value":2242},"DistributedManager.initialize()",{"type":56,"value":2244},". In notebooks without ",{"type":50,"tag":65,"props":2246,"children":2248},{"className":2247},[],[2249],{"type":56,"value":2250},"torchrun",{"type":56,"value":2252},",\nset env vars first: ",{"type":50,"tag":65,"props":2254,"children":2256},{"className":2255},[],[2257],{"type":56,"value":2258},"WORLD_SIZE=1",{"type":56,"value":1210},{"type":50,"tag":65,"props":2261,"children":2263},{"className":2262},[],[2264],{"type":56,"value":2265},"RANK=0",{"type":56,"value":1210},{"type":50,"tag":65,"props":2268,"children":2270},{"className":2269},[],[2271],{"type":56,"value":2272},"LOCAL_RANK=0",{"type":56,"value":326},{"type":50,"tag":65,"props":2275,"children":2277},{"className":2276},[],[2278],{"type":56,"value":2279},"MASTER_ADDR=localhost",{"type":56,"value":1210},{"type":50,"tag":65,"props":2282,"children":2284},{"className":2283},[],[2285],{"type":56,"value":2286},"MASTER_PORT=12355",{"type":56,"value":745},{"type":50,"tag":98,"props":2289,"children":2290},{},[2291,2300,2302,2308],{"type":50,"tag":86,"props":2292,"children":2293},{},[2294],{"type":50,"tag":65,"props":2295,"children":2297},{"className":2296},[],[2298],{"type":56,"value":2299},"weights_only=True",{"type":56,"value":2301},": Use this flag with ",{"type":50,"tag":65,"props":2303,"children":2305},{"className":2304},[],[2306],{"type":56,"value":2307},"torch.load()",{"type":56,"value":2309}," for safe\ndeserialization (PyTorch 2.6+ default).",{"type":50,"tag":98,"props":2311,"children":2312},{},[2313,2318,2320,2326,2328,2334],{"type":50,"tag":86,"props":2314,"children":2315},{},[2316],{"type":56,"value":2317},"Domain matching",{"type":56,"value":2319},": The engine checks that ",{"type":50,"tag":65,"props":2321,"children":2323},{"className":2322},[],[2324],{"type":56,"value":2325},"model.INFERENCE_DOMAIN",{"type":56,"value":2327},"\nmatches the dataset adapter's ",{"type":50,"tag":65,"props":2329,"children":2331},{"className":2330},[],[2332],{"type":56,"value":2333},"inference_domain_from_kwargs()",{"type":56,"value":2335},".\nMismatches are skipped in matrix mode or raise in single mode.",{"type":50,"tag":98,"props":2337,"children":2338},{},[2339,2344,2346,2352,2354,2359],{"type":50,"tag":86,"props":2340,"children":2341},{},[2342],{"type":56,"value":2343},"GT alignment",{"type":56,"value":2345},": When ",{"type":50,"tag":65,"props":2347,"children":2349},{"className":2348},[],[2350],{"type":56,"value":2351},"align_ground_truth_to_model: true",{"type":56,"value":2353}," in dataset\nkwargs, the engine converts GT data to match ",{"type":50,"tag":65,"props":2355,"children":2357},{"className":2356},[],[2358],{"type":56,"value":476},{"type":56,"value":2360}," (point\n↔ cell). This is automatic — the wrapper just needs correct class\nvars.",{"type":50,"tag":98,"props":2362,"children":2363},{},[2364,2369,2370,2375,2377,2383,2385,2391,2393,2399],{"type":50,"tag":86,"props":2365,"children":2366},{},[2367],{"type":56,"value":2368},"Results JSON format",{"type":56,"value":939},{"type":50,"tag":65,"props":2371,"children":2373},{"className":2372},[],[2374],{"type":56,"value":2049},{"type":56,"value":2376}," is a plain\n",{"type":50,"tag":65,"props":2378,"children":2380},{"className":2379},[],[2381],{"type":56,"value":2382},"list[dict]",{"type":56,"value":2384},", not ",{"type":50,"tag":65,"props":2386,"children":2388},{"className":2387},[],[2389],{"type":56,"value":2390},"{\"results\": [...]}",{"type":56,"value":2392},". Iterate directly:\n",{"type":50,"tag":65,"props":2394,"children":2396},{"className":2395},[],[2397],{"type":56,"value":2398},"for combo in report:",{"type":56,"value":745},{"type":50,"tag":74,"props":2401,"children":2403},{"id":2402},"related-resources",[2404],{"type":56,"value":2405},"Related resources",{"type":50,"tag":94,"props":2407,"children":2408},{},[2409,2426],{"type":50,"tag":98,"props":2410,"children":2411},{},[2412,2417,2419,2424],{"type":50,"tag":65,"props":2413,"children":2415},{"className":2414},[],[2416],{"type":56,"value":254},{"type":56,"value":2418}," — complete surface + volume ",{"type":50,"tag":65,"props":2420,"children":2422},{"className":2421},[],[2423],{"type":56,"value":70},{"type":56,"value":2425},"\ntemplates to copy and adapt (bundled; available without the repo on\ndisk).",{"type":50,"tag":98,"props":2427,"children":2428},{},[2429,2434],{"type":50,"tag":65,"props":2430,"children":2432},{"className":2431},[],[2433],{"type":56,"value":279},{"type":56,"value":2435}," — sample mean\u002Fstd stats layout for\nsurface and volume.",{"type":50,"tag":2437,"props":2438,"children":2439},"style",{},[2440],{"type":56,"value":2441},"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":2443,"total":822},[2444,2458,2472],{"slug":2445,"name":2445,"fn":2446,"description":2447,"org":2448,"tags":2449,"stars":26,"repoUrl":27,"updatedAt":2457},"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},[2450,2453,2454,2455,2456],{"name":2451,"slug":2452,"type":15},"Analysis","analysis",{"name":13,"slug":14,"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":2459,"name":2459,"fn":2460,"description":2461,"org":2462,"tags":2463,"stars":26,"repoUrl":27,"updatedAt":2471},"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},[2464,2465,2468,2469,2470],{"name":13,"slug":14,"type":15},{"name":2466,"slug":2467,"type":15},"Datasets","datasets",{"name":9,"slug":8,"type":15},{"name":24,"slug":25,"type":15},{"name":21,"slug":22,"type":15},"2026-07-14T05:33:35.569433",{"slug":4,"name":4,"fn":5,"description":6,"org":2473,"tags":2474,"stars":26,"repoUrl":27,"updatedAt":28},{"slug":8,"name":9,"logoUrl":10,"githubOrg":9},[2475,2476,2477,2478,2479],{"name":13,"slug":14,"type":15},{"name":17,"slug":18,"type":15},{"name":9,"slug":8,"type":15},{"name":24,"slug":25,"type":15},{"name":21,"slug":22,"type":15},{"items":2481,"total":2639},[2482,2500,2517,2528,2540,2554,2567,2581,2594,2605,2619,2628],{"slug":2483,"name":2483,"fn":2484,"description":2485,"org":2486,"tags":2487,"stars":2497,"repoUrl":2498,"updatedAt":2499},"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},[2488,2491,2494],{"name":2489,"slug":2490,"type":15},"Documentation","documentation",{"name":2492,"slug":2493,"type":15},"MCP","mcp",{"name":2495,"slug":2496,"type":15},"Search","search",21777,"https:\u002F\u002Fgithub.com\u002FNVIDIA\u002FNemoClaw","2026-07-20T06:00:01.461044",{"slug":2501,"name":2501,"fn":2502,"description":2503,"org":2504,"tags":2505,"stars":2514,"repoUrl":2515,"updatedAt":2516},"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},[2506,2509,2512],{"name":2507,"slug":2508,"type":15},"Containers","containers",{"name":2510,"slug":2511,"type":15},"Deployment","deployment",{"name":2513,"slug":794,"type":15},"Python",17049,"https:\u002F\u002Fgithub.com\u002FNVIDIA\u002FMegatron-LM","2026-07-27T06:06:11.249662",{"slug":2518,"name":2518,"fn":2519,"description":2520,"org":2521,"tags":2522,"stars":2514,"repoUrl":2515,"updatedAt":2527},"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},[2523,2526],{"name":2524,"slug":2525,"type":15},"CI\u002FCD","ci-cd",{"name":2510,"slug":2511,"type":15},"2026-07-14T05:25:59.97109",{"slug":2529,"name":2529,"fn":2530,"description":2531,"org":2532,"tags":2533,"stars":2514,"repoUrl":2515,"updatedAt":2539},"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},[2534,2535,2536],{"name":2524,"slug":2525,"type":15},{"name":2510,"slug":2511,"type":15},{"name":2537,"slug":2538,"type":15},"GitHub","github","2026-07-27T06:06:12.278222",{"slug":2541,"name":2541,"fn":2542,"description":2543,"org":2544,"tags":2545,"stars":2514,"repoUrl":2515,"updatedAt":2553},"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},[2546,2549,2550],{"name":2547,"slug":2548,"type":15},"Debugging","debugging",{"name":2537,"slug":2538,"type":15},{"name":2551,"slug":2552,"type":15},"Triage","triage","2026-07-14T05:25:57.442089",{"slug":2555,"name":2555,"fn":2556,"description":2557,"org":2558,"tags":2559,"stars":2514,"repoUrl":2515,"updatedAt":2566},"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},[2560,2563],{"name":2561,"slug":2562,"type":15},"Best Practices","best-practices",{"name":2564,"slug":2565,"type":15},"Code Analysis","code-analysis","2026-07-14T05:25:56.18433",{"slug":2568,"name":2568,"fn":2569,"description":2570,"org":2571,"tags":2572,"stars":2514,"repoUrl":2515,"updatedAt":2580},"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},[2573,2576,2579],{"name":2574,"slug":2575,"type":15},"Machine Learning","machine-learning",{"name":2577,"slug":2578,"type":15},"Migration","migration",{"name":9,"slug":8,"type":15},"2026-07-17T06:07:11.777011",{"slug":2582,"name":2582,"fn":2583,"description":2584,"org":2585,"tags":2586,"stars":2514,"repoUrl":2515,"updatedAt":2593},"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},[2587,2590],{"name":2588,"slug":2589,"type":15},"QA","qa",{"name":2591,"slug":2592,"type":15},"Testing","testing","2026-07-14T05:25:53.673039",{"slug":2595,"name":2595,"fn":2596,"description":2597,"org":2598,"tags":2599,"stars":2514,"repoUrl":2515,"updatedAt":2604},"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},[2600,2601],{"name":2510,"slug":2511,"type":15},{"name":2602,"slug":2603,"type":15},"Infrastructure","infrastructure","2026-07-14T05:25:49.362534",{"slug":2606,"name":2606,"fn":2607,"description":2608,"org":2609,"tags":2610,"stars":2514,"repoUrl":2515,"updatedAt":2618},"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},[2611,2614,2615],{"name":2612,"slug":2613,"type":15},"Code Review","code-review",{"name":2537,"slug":2538,"type":15},{"name":2616,"slug":2617,"type":15},"Pull Requests","pull-requests","2026-07-14T05:26:01.226578",{"slug":2620,"name":2620,"fn":2621,"description":2622,"org":2623,"tags":2624,"stars":2514,"repoUrl":2515,"updatedAt":2627},"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},[2625,2626],{"name":2588,"slug":2589,"type":15},{"name":2591,"slug":2592,"type":15},"2026-07-14T05:25:54.928983",{"slug":2629,"name":2629,"fn":2630,"description":2631,"org":2632,"tags":2633,"stars":2514,"repoUrl":2515,"updatedAt":2638},"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},[2634,2637],{"name":2635,"slug":2636,"type":15},"Automation","automation",{"name":2524,"slug":2525,"type":15},"2026-07-30T05:29:03.275638",496]