From 549938f346f69e2c6b5d96e0f09e9ff5c5b858d2 Mon Sep 17 00:00:00 2001 From: Wacc <8445647+jiweiqi@users.noreply.github.com> Date: Mon, 11 May 2026 21:47:07 +0800 Subject: [PATCH] Add COMSOL offline postprocessing guidance --- src/sim_plugin_comsol/_skills/comsol/SKILL.md | 12 + .../offline_postprocessing_exports.md | 232 ++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 src/sim_plugin_comsol/_skills/comsol/base/reference/offline_postprocessing_exports.md diff --git a/src/sim_plugin_comsol/_skills/comsol/SKILL.md b/src/sim_plugin_comsol/_skills/comsol/SKILL.md index 2bc6942..74588ac 100644 --- a/src/sim_plugin_comsol/_skills/comsol/SKILL.md +++ b/src/sim_plugin_comsol/_skills/comsol/SKILL.md @@ -68,6 +68,7 @@ single `mph` line (1.2.x). Always read `base/`, then your active | `base/reference/runtime_introspection.md` | Live-session inspection contract: preferred `sim inspect` targets, compatibility rules, partial results, and raw Java fallbacks. | | `base/reference/java_api_patterns.md` | Stable Java API probing patterns: tags first, properties before `set`, selection checks, and version-safe try/except snippets. | | `base/reference/mph_file_format.md` | `.mph` is a ZIP archive — internal layout, the three `nodeType` variants (compact/solved/preview), the Global Parameter `T="33"` contract, and the stdlib `mph_inspect` reader. Read this when you need to introspect a `.mph` *without* spinning up `comsolmphserver`. | +| `base/reference/offline_postprocessing_exports.md` | Optional pattern for COMSOL-free/Python postprocessing after a solve. Use when the user asks for reusable result artifacts, full-domain VTU field exports, CSV tables, or postprocessing without keeping COMSOL open. | Larger engineering examples do not live in this plugin skill. Keep this plugin-owned content focused on the driver protocol, live introspection, @@ -193,6 +194,17 @@ Use `.mph` archive inspection for saved artifacts and offline comparison. Use live runtime introspection for the current JPype session, especially before changing selections, physics features, studies, and result nodes. +## Optional offline postprocessing exports + +When the user wants Python-friendly postprocessing without keeping COMSOL +open, export reusable data artifacts once from the live or headless COMSOL +session, then process those files offline. Prefer full-domain VTU field data +and CSV/TXT tables over screenshots or slices as the reusable source data. + +See +[`base/reference/offline_postprocessing_exports.md`](base/reference/offline_postprocessing_exports.md) +for the optional bundle layout and headless export snippets. + --- ## Headless `comsolbatch` (not yet implemented) diff --git a/src/sim_plugin_comsol/_skills/comsol/base/reference/offline_postprocessing_exports.md b/src/sim_plugin_comsol/_skills/comsol/base/reference/offline_postprocessing_exports.md new file mode 100644 index 0000000..27a18df --- /dev/null +++ b/src/sim_plugin_comsol/_skills/comsol/base/reference/offline_postprocessing_exports.md @@ -0,0 +1,232 @@ +# Offline postprocessing exports + +Use this optional pattern when the user asks for COMSOL-free postprocessing, +Python-friendly result artifacts, reusable field data, or analysis after the +COMSOL session is closed. COMSOL is still required once to solve the model and +write the exports. After that, Python tools can read the exported artifacts +without a COMSOL install or license. + +This is not a required checkpoint for every solve. Numeric probes remain the +preferred acceptance signal, `inspect_mph()` remains the preferred saved `.mph` +metadata path, and PNG/image exports remain convenience views rather than +primary evidence. + +## Recommended artifact bundle + +```text +/ + model/.mph + exports/manifest.json + exports/mph_summary.json + exports/fields/domain_solution.vtu + exports/fields/boundary_fields.vtu + exports/tables/global_metrics.csv + exports/tables/probes.csv +``` + +Guidance: + +- Use full-domain VTU exports for reusable spatial field data from volume + domains. +- Use boundary/surface VTU exports for fluxes, walls, terminals, and boundary + checks. +- Use CSV/TXT exports for probes, global evaluations, cut lines, and scalar + metrics. +- Use PNG and slice exports only as convenience views, not as the only + reusable data. +- Do not rely on `.mphbin` for offline postprocessing. Treat it as + COMSOL-private binary data. +- If no slice exists yet, export the full dataset first. A slice is a useful + view, but it is a lossy derived artifact. + +## Manifest + +Write a manifest next to the exported files so an offline reader can interpret +the artifacts without reopening the model: + +```json +{ + "model_path": "model/case.mph", + "comsol_version": "6.4", + "mph_summary_path": "exports/mph_summary.json", + "exports": [ + { + "kind": "field", + "path": "exports/fields/domain_solution.vtu", + "dataset": "dset1", + "level": "volume", + "expressions": ["T", "ht.fluxMag"], + "units": ["K", "W/m^2"], + "time_values": [], + "parameter_values": {"power": "10[W]"} + }, + { + "kind": "field", + "path": "exports/fields/boundary_fields.vtu", + "dataset": "dset1", + "level": "surface", + "expressions": ["T", "ht.nteflux"], + "units": ["K", "W/m^2"], + "time_values": [], + "parameter_values": {"power": "10[W]"} + }, + { + "kind": "table", + "path": "exports/tables/global_metrics.csv", + "table": "tbl1", + "expressions": ["maxop1(T)", "intop1(ht.Q)"], + "units": ["K", "W"] + } + ] +} +``` + +Useful fields to record: + +- Model path and COMSOL version. +- Dataset tag used for each export. +- Expressions and units. +- Export file paths and format. +- Time values, parameter values, or sweep cases. +- Path to the `inspect_mph()` summary for saved `.mph` metadata. + +Create the saved `.mph` summary with the stdlib inspector when available: + +```python +import json +from pathlib import Path + +from sim_plugin_comsol.lib import inspect_mph + +summary_path = Path("exports/mph_summary.json") +summary_path.parent.mkdir(parents=True, exist_ok=True) +summary = inspect_mph("model/case.mph") +summary_path.write_text(json.dumps(summary, indent=2), encoding="utf-8") +``` + +## Headless export snippets + +These snippets are for the sim runtime or another already-connected JPype/Java +API context where `model` is provided. Do not call `mph.start()` or create a +second COMSOL client from a snippet. Before using unfamiliar export properties, +inspect the export node with the patterns in +[`java_api_patterns.md`](java_api_patterns.md). + +### Full-domain VTU + +Use a full-domain VTU for reusable volume/domain fields. Include primary +solved variables and derived quantities that are hard to reconstruct offline, +such as flux magnitude, stress invariants, current density, reaction rates, or +heat sources. Record units in the manifest. + +```python +from pathlib import Path +import jpype + +out = Path(r"C:\work\case\exports\fields") +out.mkdir(parents=True, exist_ok=True) +jstr = jpype.JArray(jpype.JString) + +tag = "exp_domain_vtu" +dataset = "dset1" +exports = model.result().export() +if tag not in list(exports.tags()): + exports.create(tag, "Data") + +exp = model.result().export(tag) +exp.set("filename", str(out / "domain_solution.vtu")) +exp.set("data", dataset) +exp.set("exporttype", "vtu") +exp.set("location", "fromdataset") +exp.set("level", "volume") +exp.set("expr", jstr(["T", "ht.fluxMag"])) +exp.set("unit", jstr(["K", "W/m^2"])) +exp.run() + +_result = { + "export": "domain_solution.vtu", + "dataset": dataset, + "level": "volume", + "expressions": ["T", "ht.fluxMag"], +} +``` + +### Boundary/surface VTU + +Use a surface-level VTU for boundary fields, flux checks, walls, inlets, +outlets, terminals, electrodes, and other boundary-facing review. + +```python +from pathlib import Path +import jpype + +out = Path(r"C:\work\case\exports\fields") +out.mkdir(parents=True, exist_ok=True) +jstr = jpype.JArray(jpype.JString) + +tag = "exp_boundary_vtu" +dataset = "dset1" +exports = model.result().export() +if tag not in list(exports.tags()): + exports.create(tag, "Data") + +exp = model.result().export(tag) +exp.set("filename", str(out / "boundary_fields.vtu")) +exp.set("data", dataset) +exp.set("exporttype", "vtu") +exp.set("location", "fromdataset") +exp.set("level", "surface") +exp.set("expr", jstr(["T", "ht.nteflux"])) +exp.set("unit", jstr(["K", "W/m^2"])) +exp.run() + +_result = { + "export": "boundary_fields.vtu", + "dataset": dataset, + "level": "surface", + "expressions": ["T", "ht.nteflux"], +} +``` + +### Existing table to CSV + +Use table exports for probes, global evaluations, cut lines, and scalar +metrics. This assumes a COMSOL table already exists at `table_tag`. +If the table is created from a numerical feature in the same workflow, set the +intended selection first, assign `table_tag`, then call `setResult()` or +`appendResult()` before exporting. Otherwise COMSOL can write only table +metadata. + +```python +from pathlib import Path + +out = Path(r"C:\work\case\exports\tables") +out.mkdir(parents=True, exist_ok=True) + +tag = "exp_global_metrics_csv" +table_tag = "tbl1" +exports = model.result().export() +if tag not in list(exports.tags()): + exports.create(tag, "Table") + +exp = model.result().export(tag) +exp.set("filename", str(out / "global_metrics.csv")) +exp.set("source", "table") +exp.set("table", table_tag) +exp.set("header", "on") +exp.run() + +_result = { + "export": "global_metrics.csv", + "table": table_tag, +} +``` + +## Offline Python readers + +Once the exports exist, postprocess them outside COMSOL: + +- Read CSV/TXT with the Python standard library, pandas, or polars. +- Read VTU with meshio, pyvista, VTK, or ParaView. +- Keep the manifest with the exported files so downstream ingestion and search + know which fields, units, dataset, and sweep point each artifact represents.