Skip to content

feat(fp_mcp): expose set_param_str tool for KNAMEQ / MODELG=3 equilibrium load - #231

Open
HengyuLi-Ozaki-lab wants to merge 1 commit into
k-yoshimi:developfrom
HengyuLi-Ozaki-lab:pr/fp-mcp-set-param-str
Open

feat(fp_mcp): expose set_param_str tool for KNAMEQ / MODELG=3 equilibrium load#231
HengyuLi-Ozaki-lab wants to merge 1 commit into
k-yoshimi:developfrom
HengyuLi-Ozaki-lab:pr/fp-mcp-set-param-str

Conversation

@HengyuLi-Ozaki-lab

Copy link
Copy Markdown
Collaborator

Summary

Exposes set_param_str as an fp_mcp MCP tool. This is pure wiring — every layer below the MCP server already supports it:

layer status
fp/fp_api.f90:155 fp_api_set_param_strBIND(C, NAME="fp_set_param_str") — already an exported symbol
fp/fp_param_registry.f90:171-182 fp_param_set_str with CASE ("KNAMEQ")
python/fplib/fplib.py:163 Fplib.set_param_str()
python/mcp-servers/fp_mcp/server.py ← the only gap

No .so rebuild is required — the symbol is already in a stock libfpapi.so:

$ nm -gU fp/libfpapi.so | grep set_param_str
0000000000369458 T _fp_set_param_str

Why

fp_param_registry.f90's own comment states the intent:

Required because the FP namelist /FP/ inherits KNAMEQ from plcomm (the equilibrium-data file name used by the MODELG=3 eq_load path on fp_iter01). Without this entry point the Layer 1 fixture cannot point at a real EQDSK; the default KNAMEQ='eqdata' (from pl_init) is missing in cwd, so eq_load fails silently and downstream BESEKNX trips with NCALC=-2.

Until now that entry point was unreachable from an MCP client, so fp_mcp could not use the MODELG=3 equilibrium-load path at all. eq_mcp and tr_mcp both already expose set_param_str; this brings fp_mcp to parity.

Changes

  • str added to the SupportedValue union, so set_params({"KNAMEQ": ...}) routes to the string setter instead of dying in float()
  • KNAMEQ registered in PARAMETER_REGISTRY as type: "str"
  • handle_set_param_str() handler, error-mapped through the existing _wrap_fplib_error (FplibError → rebuild hint, registry rc=1invalid parameter)
  • tool registration + --print-tools (9 → 10 tools), instructions string, docstrings
  • README.md: tool table, --print-tools output, and two corrections — §8.3 described MODELG=3 as "analytic" when it is the file-loading path, and §8.4 said string parameters were unsupported

Verification

Against a stock build (gfortran 15.2.0, macOS arm64):

  • set_param_str("KNAMEQ", "eq.bin") accepted; set_param_str("KNAMFP", ...) correctly rejected with rc=1 (the registry has exactly one CASE)
  • Positive path: an equilibrium written by eq_mcp's save tool is loaded by FP — # OLD FILE (eq.bin) IS ASSIGNED FOR INPUT., and eqgetb writes the file's geometry back into FP (DEVICE, RR, RA, BB = 3.60E+00 1.20E+00 4.50E+00, matching the perturbed EQ run rather than FP's own defaults). Mesh metrics move accordingly (rsrhon 0.805→1.0264, bpm 0.3506→0.1829, ql 2.2961→7.0134), and global integrals scale with the volume ratio (stored energy 5.369→10.341 MJ = 1.93× vs TVOL 28.42→53.12 = 1.87×)
  • Negative control: pointing KNAMEQ at a missing file reproduces exactly the chain the registry comment predicts — XX FROPEN (EQ): FILE NOT FOUNDeq_load: eqload: ierr=7XX FPMESH:EQLOAD:IERR=7XX fp_prep: fp_mesh failed
  • Stability: 5/5 fresh subprocesses, 7.07–7.30 s, bit-identical states
  • Tests: 45 passed, 55 subtests. Two existing tests pinned the old contract (test_rejects_string_value, test_print_tools_lists_nine) and were rewritten to pin the new one; added coverage for the registry entry, the handler, and the error mapping against an older .so

Note for reviewers

Independent of the companion PR that adds the dup2(2,1) stdout guard to fp_mcp — the two touch different regions of server.py and either can merge first.

One finding worth recording, though it needs no change here: FP's six get_state profile arrays are local per-unit-volume moments, so with MODELD=0 and no wave/current-drive model they are bit-identical across different equilibria. The equilibrium enters only through the volume element and mesh metrics, which fp_state_t does not currently expose. The geometry-dependent globals the code already computes (rtotalIP, stored energy, total collision/absorption power, TVOL) are written to unit 6 but not returned in the state. Happy to open a separate issue/PR for that if it is of interest.

🤖 Generated with Claude Code

…rium load

Every layer below MCP already supported string-valued fp parameters;
only the MCP server did not, so `KNAMEQ` was unreachable from any MCP
client:

  * C ABI      fp/fp_api.f90:155 — fp_api_set_param_str is
               BIND(C, NAME="fp_set_param_str"), and the symbol is
               present in the built library
               (nm -gU fp/libfpapi.so | grep set_param_str ->
                T _fp_set_param_str). No .so rebuild is required.
  * registry   fp/fp_param_registry.f90:171-182 — fp_param_set_str
               has CASE ("KNAMEQ").
  * wrapper    python/fplib/fplib.py:163 — Fplib.set_param_str().
  * MCP        (this commit) — the gap.

This unlocks EQ->FP: eq_mcp's `save` writes an equilibrium file, and fp
can now be pointed at it with MODELG=3 + set_param_str("KNAMEQ", path),
the same file-handoff shape EQ->TR already uses. Without it, MODELG=3
falls back to the pl_init default KNAMEQ='eqdata', eq_load fails, and
BESEKNX trips with NCALC=-2 (see the comment block in
fp_param_registry.f90).

Changes mirror eq_mcp/server.py and tr_mcp/server.py:
  * new `set_param_str` tool + handle_set_param_str() handler;
  * `str` added to the SupportedValue union and to _apply_bulk_params,
    so set_params({"KNAMEQ": "eq.bin"}) also routes correctly instead
    of failing the float() coercion;
  * KNAMEQ added to PARAMETER_REGISTRY as type "str";
  * --print-tools, server instructions and docstrings updated (9 -> 10
    tools).

Tests: two tests pinned the old numeric-only contract and were updated
to pin the new one (string -> set_param_str; 10 tools). Added coverage
for the registry entry, the handler, and the old-.so error mapping.
45 passed, 55 subtests passed.

Live-verified against the built libfpapi.so:
  handle_set_param_str('KNAMEQ', 'eq.bin')  -> ok
  handle_set_param_str('KNAMFP', 'x.bin')   -> rc=1 (registry has one CASE)
  handle_set_params({'MODELG': 3, 'KNAMEQ': 'eq.bin'}) -> 2 parameter(s)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant