Requires COMSOL Multiphysics 6.4. Every table, catalog, gotcha and test in this repository was built and measured against 6.4 (6.4.0.293 Linux, 6.4.0.429 Windows, 6.4.0.378 macOS). Other versions are unverified: the CLI prints a warning when it finds one,
doctorflags it, and COMSOL_VERSIONS.md explains what would break. A COMSOL install and license are required for everything except Layer A linting and knowledge-base search.
Comsol-support is a deterministic automation and quality-assurance layer designed to connect LLMs (e.g., Claude, Codex, Gemini) with COMSOL Multiphysics workflows. For example, comsol-support can be used to automate meshing (a complex but easily verifiable problem), simplify results post-processing, and audit and debug complex models.
COMSOL model-code generation, execution, and linting tools for
COMSOL Multiphysics 6.4: compile Java builders to .mph, load / mutate /
mesh / solve existing models, lint units and dimensions, and look up the
API from a scraped knowledge base — all deterministic, driven from a CLI
and an MCP server. The calling agent (human or LLM) plans and composes;
comsol-support does the typed Java glue, the linting, the ontology
lookups, and the telemetry plumbing.
Design goal: deterministic floor, agentic ceiling. The agent that uses this repo should never have to guess a COMSOL API string — every type name and property key it emits should be something it looked up in the knowledge base or copied from a known-good fragment, and every unit it writes should be checked before the solver sees it. That is the goal the tooling is built toward; the section What is actually checked says precisely how far it is realised today, because the gap between "looked up" and "verified" is where silent modeling errors live.
Status: 793 tests collected (766 in the default sweep;
27 real-COMSOL tests opt-in via -m real_comsol); zero external
runtime dependencies (stdlib only). Verified against COMSOL 6.4.0.293 on
Linux, 6.4.0.429 on Windows, and 6.4.0.378 on macOS (Apple Silicon —
see docs/macos-setup.md). Install on any of the
three: INSTALL.md; port detail in
docs/platform-setup.md. Every layer is
deterministic — nothing in this repository calls an LLM; the modeling
practice a calling agent is expected to follow is written down in
docs/modeling-practice.md.
| You want to… | Reach for | Doc |
|---|---|---|
Compile a .java builder to an unsolved .mph (or solve too) |
comsol-support mphgen |
docs/mphgen.md |
Load an existing .mph, optionally mutate, solve, save |
comsol-support edit-mph |
docs/mphedit.md |
| Build a mesh on an existing model, with census + per-feature records | comsol-support edit-mph --mesh <tag> |
docs/meshing.md |
Read-only introspection of an .mph (never saves) |
comsol-support query-mph |
docs/query-mph.md |
| Compile + run any COMSOL-dependent Java class, tee full log | comsol-support run-harness |
docs/run-harness.md |
| Check COMSOL license-seat availability (bounded probe) | comsol-support license-status |
docs/run-harness.md |
Lint a .java (units / descriptions) or .mph (runtime + dimensional) |
comsol-support check |
docs/linting.md |
| Periodic "still alive" event during a long solve | import SolverHeartbeat |
docs/solver-progress.md |
| Check this machine can run comsol-support | comsol-support doctor |
INSTALL.md |
| Search a recurring symptom against the gotcha catalog | comsol-support gotcha-search |
docs/known-gotchas.md |
| Look up an API type / property key (Javadoc + Reference Manual) | comsol-support search "<query>" [--stage …] |
MCP search_api is the same backend |
| Stage-by-stage modeling practice (mindsets, invariants, pitfalls, API quick-refs) | read | docs/modeling-practice.md |
| Tag / selection conventions for builders and mutators | TagRegistry, SelectionAlgebra |
docs/facade-conventions.md |
| Ingest a telemetry JSONL into the DB | comsol-support ingest-telemetry |
|
| Populate the knowledge base / fragments / slot catalog | comsol-support scrape {javadoc,refmanual,corpus,slots} |
Full CLI reference: comsol-support --help.
Source checkout only. Install is
git clone+uv sync(orpip install -e .). A wheel built frompyproject.tomldoes not carry the Java sources, the probe library ordocs/known-gotchas.md, so an installed wheel cannot work;comsol-support doctorreports it as a blocking problem andscripts/wheel_smoke.sh(run in CI) pins that the failure is explicit. Do not publish wheels.Running from another directory.
uv run …resolves thecomsol_supportpackage relative to the current project, so invoking it from outside this repo raisesModuleNotFoundError: comsol_support. Either run from the repo root, or pass the project explicitly:uv --project /path/to/comsol-support run comsol-support …. The installed console scriptcomsol-supportdoes not depend on cwd. The COMSOL install path is auto-discovered per platform (see COMSOL_VERSIONS.md for the probed roots) and can be overridden anywhere via theCOMSOL_PATHenvironment variable.Which database. The knowledge base, fragments, telemetry and catalogs live in one SQLite file:
data/comsol.dbinside the checkout, the same file from whatever directory you run in (before 1.0 it was cwd-relative, so every new directory silently got an empty one).COMSOL_DBor--dboverride it. Populate it once withcomsol-support scrape javadoc+comsol-support scrape refmanual(~14k rows from the on-disk 6.4 docs);comsol-support doctorreports what it contains. The MCP server still takes the path fromCOMSOL_DB.Names. The package, CLI and import name are
comsol-support/comsol_supportsince 1.0. The pre-1.0 names still work — thecomsol-agentcommand is an alias andimport comsol_agentresolves to the same modules — so existing scripts and campaign workspaces are unaffected; prefer the new names.
Full setup instructions for Linux, macOS, and Windows — including the knowledge-base build and what the expected failure counts look like — are in INSTALL.md. The short version:
# Install everything: deps, Java, ontology, corpus
./scripts/install.sh # Linux / macOS
.\scripts\install.ps1 # Windows (PowerShell)
# Check the machine can run it (works before installing, any platform)
python scripts/doctor.py
# …or just the Python package (editable, dev deps)
uv sync
# Generate a pre-solve .mph from a Java builder
comsol-support mphgen --builder MyBuilder.java --output model_unsolved.mph
# Solve it via edit-mph (no mutator; pure load → solve → save)
comsol-support edit-mph \
--input model_unsolved.mph \
--output model_solved.mph \
--solve std1
# Lint the solved .mph (Layer A + B + C)
comsol-support check model_solved.mph
# Each step writes sibling sidecars:
# model_solved.mph.telemetry.jsonl (live JSONL of solve events)
# model_solved.mph.mphedit.json (provenance + digest)
# model_solved.mph.jvm.log (raw JVM stream, crash-safe tee)
# model_solved.mph.units.json (linting findings)
# model_solved.mph.descriptions.json (description findings)A campaign that varies one parameter across many runs of the same reference model:
for pitch in 0.030 0.040 0.050; do
comsol-support edit-mph \
--input reference.mph \
--output sweep_p${pitch}.mph \
--mutator SetSlotPitch.java \
--arg pitch=${pitch} \
--solve std1
doneThe mutator is one method:
public class SetSlotPitch {
public static Model mutate(Model m, Map<String,String> args) {
double p = Double.parseDouble(args.getOrDefault("pitch", "0.04"));
m.param().set("slot_pitch", p + "[m]");
return m;
}
}Three layers, all deterministic. Planning belongs to whatever agent calls the CLI / MCP tools; nothing in this repo talks to an LLM.
L3 Fragment library (mined from the bundled model corpus, schema-backed)
L2 Typed Java facade (TagRegistry, SelectionAlgebra,
ModelExporter, ModelChecker,
SlotHarvester, SolverHeartbeat,
SolverTelemetry, probes/)
L1 com.comsol.model.* (vendor API)
An earlier design placed an LLM-driven build orchestrator above these
layers. It was removed before 1.0 — every consumer reached COMSOL
through the verbs above instead. The modeling practice it encoded is
now in docs/modeling-practice.md and its tag and selection
conventions in docs/facade-conventions.md. A data/comsol.db
created by that version upgrades in place: the retired tables are
dropped the next time it is opened.
The honest map of where deterministic verification exists today and where the calling agent is still trusted:
| Surface | Checked how | Outcome |
|---|---|---|
Unit literals in .java .set(...) calls |
Layer A regex (six typo patterns, missing / placeholder descriptions) | findings in *.units.json / *.descriptions.json; check exits 0 unless --exit-code-on-warnings |
Global parameters in a saved .mph |
Layer B: ModelChecker.java calls ParamBase.evaluateUnit per parameter |
same; component variables have no evaluateUnit in the API (G-VARIABLE-EVALUATEUNIT-MISSING), so they are covered only by Layer C |
| Expression dimensions vs expected units | Layer C: pure-Python symbolic dimensional analysis against the corpus-derived slot catalog | W1/W2/W3 findings; the catalog has confidence tiers, so a "mismatch" against a low-confidence slot is a hint, not a verdict |
| Type names / property keys the agent is about to emit | Lookup, not enforcement. comsol-support search / MCP search_api answer from the Javadoc + Reference Manual scrape (~14k rows); nothing rejects a Java file that uses an unknown string |
the agent's discipline — docs/modeling-practice.md gives the ladder (fragment → adapted exemplar → free synthesis with every .create type and .set key looked up first) |
| Tags, selection names, expressions, values, method arguments | Not checked against any table. TagRegistry / SelectionAlgebra make tag and selection construction structured for code that opts into them |
docs/facade-conventions.md |
| Solver / mesh outcomes | Telemetry stream (halt_reason, error_detail, mid-run *_error events, mesh census) |
digest + sidecars; check_health budgets are opt-in |
So: linting and telemetry are enforced by code; API-name correctness
is retrievable by code but applied by the agent. Missing databases
and lookup failures fail open (empty result plus a hint naming the
scrape verbs), never by blocking a build.
- Layer A — pure-Python regex over
.javasource. Catches the six common unit-typo patterns ([m]/[s], missing quantity, addition inside brackets, non-ASCII, …) plus missing / placeholder.set()descriptions. Runs in milliseconds. No COMSOL required. - Layer B — runtime check on the saved
.mphviaModelChecker.java. Walksmodel.param()andmodel.variable(), callsParamBase.evaluateUnitper param, produces structured sidecars. - Layer C — independent symbolic dimensional analysis, pure Python (stdlib only; the Wolfram bridge was retired before 1.0). Tokenizes + parses COMSOL expressions, propagates dimensions as rational-exponent vectors over the SI base dimensions, and checks dimension-equality compatibility against catalog-derived expected units. Catches W1 (non-integer exponent on unit base), W2/W3 (deduced-vs-expected mismatch), scope-conflicts, unknown units.
Slot expected-unit catalog (5-axis key: physics_type × sdim × feature_type × feature_scope × slot_property × comsol_version)
promotes Layer C from descriptive to contract-checking. Phase 1:
harvest from the .mph corpus. Phase 2: aggregate with confidence
tiering.
data/comsol.db (or $COMSOL_DB) carries 7 user tables + 2 FTS5
virtuals + sync triggers:
| Table | Role |
|---|---|
knowledge (+ FTS5) |
Javadoc + RefManual ontology — the search / search_api backend |
fragments |
Corpus-mined Java code blocks |
telemetry_events (+ FTS5) |
Solver events from the TELEMETRY: stream, keyed by a caller-chosen run id |
slot_expected_units |
Layer C prescriptive catalog |
variable_declared_units |
SymbolResolver seed |
native_interfaces |
Native-preference catalog |
domain_synonyms |
Intent-keyword → domain mapping |
Migrations are additive ALTER TABLE ADD COLUMN in try/except so
rolling upgrades stay backward-compatible. Files created before
2026-08 may also carry the retired orchestrator's builds /
checkpoints objects; init_db drops them on open (they never held
rows; nothing kept references them).
Ten JSON-RPC stdio tools: search_api, get_fragment,
search_fragments, get_solver_telemetry, get_solver_results,
search_telemetry, list_physics_options,
list_studies_for_physics, list_default_plots_for_physics,
search_gotchas. Launch directly or via Claude Code's --mcp-config
(command: python comsol_support/mcp_server.py, env: {COMSOL_DB: …}).
No auth — runs inside a Claude Code subprocess.
| Doc | Topic |
|---|---|
| docs/modeling-practice.md | Stage-by-stage modeling practice: working discipline, native-first ladders, mindsets / invariants / pitfalls / API quick-refs |
| docs/facade-conventions.md | TagRegistry + SelectionAlgebra: tag discipline, prefix table, selection discipline, API tables |
| docs/mphgen.md | Builder → .mph generator |
| docs/mphedit.md | Load → mutate → save (sister to mphgen) |
| docs/meshing.md | Mesh doctrine measured on a 2,773-domain laminate; --mesh verb and probes |
| docs/query-mph.md | Read-only query(Model, Map) introspection (never saves) |
| docs/run-harness.md | Generic harness runner + license-status probe |
| docs/linting.md | Three-layer linting architecture |
| docs/solver-progress.md | Why there's no per-step solver callback, and the polling-heartbeat workaround |
| docs/known-gotchas.md | COMSOL 6.4 + headless-solve pitfalls catalog (33 measured entries) |
| INSTALL.md | Start here — install on Linux / macOS / Windows |
| docs/platform-setup.md | Windows / macOS / Linux port notes + verification record |
| docs/macos-setup.md | macOS (Apple Silicon) verification record |
| MANIFEST.md | Repository map: modules, database schema, MCP tools, data flow |
| CHANGELOG.md | Change history |
| CONTRIBUTING.md | Why the project exists; what contributions are wanted; what to run before a PR |
| CODE_OF_CONDUCT.md · SECURITY.md | Contributor Covenant 2.1; what the tools execute on your machine and how to report a vulnerability |
| docs/data-provenance.md | What derives from the COMSOL install, what the repo ships (scrapers, never data), the CI guard |
| COMSOL_VERSIONS.md | 6.4 as target and ceiling; install auto-discovery; what is read from the install |
| gaps.md | Roadmap and known limitations |
For long-running campaign workspaces, copy from templates/:
findings register schema, campaign-state JSON schema, finding-ID
convention, and workspace-directory layout.
comsol_support/
java/ # COMSOL-aware Java: TagRegistry, SelectionAlgebra,
# ModelExporter (build / edit / mesh), ModelChecker,
# SlotHarvester, SolverTelemetry, SolverHeartbeat,
# NodeTreeProbe, LicenseProbe, CorpusBatchConverter,
# probes/ (MeshStats, FeatureProblem, MeshSelection)
cli.py # 13-subcommand entry point
doctor.py # cross-platform install preflight (`comsol-support doctor`)
mphgen.py # builder → .mph
edit_mph.py # existing .mph → mutate / mesh → save
query_mph.py # read-only query contract
run_harness.py # generic harness runner
linting.py # three-layer lint orchestration
dimensional.py # Layer C symbolic analysis (pure Python)
slot_catalog.py # prescriptive slot catalog
gotcha_search.py # known-gotchas search
telemetry.py # telemetry stream / digest / health / DB ingest
jvm_slot.py # advisory exclusive-JVM slot + leak scan
knowledge_format.py # one rendering for CLI search + MCP search_api
… # scrapers, catalogs, MCP server, etc.
scripts/ # install.sh / install.ps1 / doctor.py (pre-install preflight)
skill/ # the Claude skill (copy to ~/.claude/skills/comsol-support/)
templates/ # campaign-state primitives (findings register, etc.)
docs/ # design docs (see table above)
tests/ # 35 test files, 793 tests collected
data/ # one SQLite DB (knowledge + fragments + telemetry + catalogs)
# Full default sweep (~70 s, 766 tests; real-COMSOL tests deselected):
uv run pytest -q
# The lint self-test — would-have-caught regressions to Layer A/B/C,
# plus compile-every-Java-source against the full COMSOL classpath:
uv run pytest tests/test_lint_selftest.py
# Real-COMSOL E2E (gated; requires a live COMSOL install + license seat):
COMSOL_E2E=1 uv run pytest -m real_comsolCI (.github/workflows/ci.yml) runs on
every push and pull request, on hosted Linux, macOS and Windows
runners (Python 3.10–3.13 on Linux) without COMSOL: the pure-Python suite (the COMSOL-dependent
tests skip — 755 run, 11 skip), the four Java sources that compile
without COMSOL (SolverHeartbeat, SolverTelemetry, TagRegistry,
SelectionAlgebra — compiled with a bare JDK by
tests/test_standalone_java.py, which also drives SelectionAlgebra
and TagRegistry through Java probes; SolverHeartbeat likewise in
tests/test_solver_heartbeat.py), the full ruff F rule set, the provenance guard
(no COMSOL-derived data, copied documentation prose or site-specific
strings tracked — docs/data-provenance.md),
doctor, and the installed-wheel smoke.
What hosted CI cannot check — every real-COMSOL path and the
compile-every-Java-source-against-the-COMSOL-classpath self-test in
tests/test_lint_selftest.py (the check that would have caught the
historical SlotHarvester regression) — is covered by
.github/workflows/real-comsol.yml,
which runs doctor, license-status, the full sweep, the
gated real_comsol suite and an mphgen → mesh → probe → lint smoke on
a self-hosted runner labelled comsol. It is inert (manual trigger)
until such a runner is registered; until then, run those by hand on a
machine with COMSOL 6.4 before a release: uv run pytest -q there, plus
COMSOL_E2E=1 uv run pytest -m real_comsol with a license seat. The
reference-model tests additionally need a model that is not in this
repository: COMSOL_REFERENCE_MPH (a solved .mph, for
test_linting_runtime.py) and COMSOL_REFERENCE_JAVA (a builder
source, for the Layer A false-positive check). Both skip cleanly when
unset.
The repo ships a Claude skill at skill/SKILL.md.
Register it by copying it to your user skills directory —
~/.claude/skills/comsol-support/SKILL.md (Linux/macOS) or
%USERPROFILE%\.claude\skills\comsol-support\SKILL.md (Windows).
Any Claude Code session that mentions COMSOL, .mph, FlException,
study.run, mphgen, or edit-mph then surfaces the skill
automatically with usage hints and doc pointers.
- Python 3.10+ — stdlib only at runtime;
pytestfor dev. - COMSOL Multiphysics 6.4 — verified on
6.4.0.293(Linux),6.4.0.429(Windows),6.4.0.378(macOS). JARs and the bundled JDK are discovered automatically; the native-library search path (LD_LIBRARY_PATHon Linux,DYLD_LIBRARY_PATHon macOS,PATHon Windows) is set to includelib/{plat}+lib/{plat}/ext+ext/*/{plat}. Other 6.x installs are untested — docs/platform-setup.md. pdftotext(poppler) — only forscrape refmanual.
No web server, no daemon, no Docker, no LLM transport. Everything is a CLI subprocess or a single Python process.
Headless-COMSOL competence is scarce and mostly locked in individual experts; this project exists to make it durable and teachable — a curated gotcha catalog, task-oriented docs, and a reference architecture for reliable simulation automation. CONTRIBUTING.md says what contributions do that best (measured gotchas first), the pull-request process, and what to run before opening one; the community standard is the Contributor Covenant and vulnerabilities go through SECURITY.md.
Licensed under the Apache License 2.0 (LICENSE,
NOTICE) — patent grant, corporate standard, and compatible
with the surrounding ecosystem (MPh, FEABench) and academic use.
Contributions are accepted under the same license (Apache-2.0 §5). The
knowledge base (data/comsol.db) is derived from COMSOL's own
documentation and is not distributed — the scrapers are; CI enforces
this (docs/data-provenance.md).
Actively used from long-running modeling campaigns through the verbs
in the table above; the real-COMSOL paths have been exercised on Linux,
Windows and macOS 6.4 installs (see the verification records linked
above). The most mature parts are the linting / dimensional analysis,
the run harnesses with their telemetry, and the knowledge base; API
lookup is retrieval the agent must apply, not a gate (see
What is actually checked). Improvement backlog and known limitations are tracked in
gaps.md; linter false-positive backlog in
LINT_BACKLOG.md.
comsol-support was authored by Jeremy Adams at Commonwealth Fusion Systems.