Skip to content

d3d: do not divide the F-coil PTDATA by turns_with_sign (PTDATA is the per-turn current) - #449

Merged
torrinba merged 1 commit into
gafusion:masterfrom
tomneiser:fix/d3d-fcoil-turns
Oct 29, 2026
Merged

torrinba merged 1 commit into
gafusion:masterfrom
tomneiser:fix/d3d-fcoil-turns

Conversation

@tomneiser

@tomneiser tomneiser commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

pf_active_coil_current_data in omas/machine_mappings/d3d.py reads the F1A..F9B PTDATA signals and divides current.data (and data_error_upper) by element.0.turns_with_sign (55–58), with the comment that IMAS stores the current per turn. IMAS does store the current per turn, but the DIII-D F-coil PTDATA signal is the power-supply (conductor) current, i.e. already per turn; the amp-turns are PTDATA × turns. After the division the ODS carries F-coil currents 55–58× too small.

Check. A free-boundary Grad-Shafranov refit of the EFIT02 boundary of shot 200000 at t = 3.04 s (FUSE/FRESCO, Green functions = per-turn current × turns_with_sign) converges to F6A = −227.7 kA-turns = −4.14 kA × 55, and likewise on every F-coil (F1A −138, F6B −257, F7B −181 kA-turns), i.e. the undivided PTDATA values (F6A −4.12 kA) within 1–3 %. Started from the divided values the same refit reaches the same solution but needs ~50× more iterations (85 s vs 1.6 s).

History: the division was added in 2cac7d5 (2023-08-29, first in v0.92.0, message "MDS+ stores A-turns"), but its condition tested the path string f'pf_active.coil.{channel}.identifier' (never contains an uppercase F), so it was dead code until 7625664 (2026-04-08, first in v0.96.0) changed it to ods1[...identifier]. ODSs from checkouts before that commit therefore carry the correct PTDATA values; everything exported with v0.96.0 or later carries F-coils 55–58× too small.

Change. The division is removed; the loop keeps only the missing-data warning. turns_with_sign stays as the element turns. E-coils (ECOILA/B, E567/E89 segments) have one turn per element and are not affected.

Where the "A-turns" came from. The EFIT k-file and the EFIT MDS+ tree do hold the F-coil currents in amp-turns for DIII-D (BRSP in OMFIT's samples/k133221.01000 is −53 to −142 kA-turns on F1A..F3A), and OMFIT's omfit_eqdsk.py (ff769052a0b, same day as 2cac7d5) converts those to the IMAS per-turn current by dividing by turns_with_sign on the way in and multiplying on the way out. That pair is correct for an ODS that holds the per-turn current. The PTDATA pointnames F1A..F9B that this mapping reads are the power-supply current, i.e. already per turn, so the division here produces an ODS 55–58× below what the OMFIT converters and the free-boundary solvers expect. A k-file written by OMFIT from an ODS exported with v0.96.0+ comes out with BRSP = PTDATA instead of PTDATA × turns, and update_keqdsk_from_omas.py (which plots ODS current × turns against the k-file) shows the same factor. With this fix the OMFIT side needs no change.

Downstream: FUSE PR ProjectTorreyPines/FUSE.jl#1195 adds a loader repair for ODSs exported with the divided currents.

🤖 Generated with Claude Code

…e per-turn current)

pf_active_coil_current_data reads the F1A..F9B PTDATA signals (ptdata2) and divides current.data and
data_error_upper by element.0.turns_with_sign (55-58). The division was added in 2cac7d5 (2023-08-29, v0.92.0,
'MDS+ stores A-turns') but tested the path string f'pf_active.coil.{channel}.identifier' and never ran until
7625664 (2026-04-08, v0.96.0) made the condition live. The DIII-D F-coil PTDATA signal is the power-supply
(conductor) current, i.e. already the IMAS per-turn current, and the amp-turns are PTDATA * turns: a free-boundary
Grad-Shafranov refit of the EFIT02 boundary of shot 200000 at t = 3.04 s (FUSE/FRESCO, Green functions = per-turn
current * turns_with_sign) converges to F6A = -227.7 kA-turns = -4.14 kA * 55 and likewise on every F-coil
(F1A -138, F6B -257, F7B -181 kA-turns), matching the undivided PTDATA within 1-3 %; started from the divided
values it needs ~50x more iterations to reach the same solution. E-coils have one turn per element and are
unaffected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@tomneiser
tomneiser force-pushed the fix/d3d-fcoil-turns branch from 3985867 to 1839457 Compare October 26, 2026 23:12
tomneiser added a commit to ProjectTorreyPines/FUSE.jl that referenced this pull request Oct 26, 2026
…s D3D mapping

Reverses the direction of the previous commits after measuring the convention: a FRESCO free-boundary refit of
the EFIT02 boundary (200000, 3.04 s) needs F6A = -227.7 kA-turns = -4.14 kA x 55, i.e. the IMAS per-turn current
of a DIII-D F-coil is the PTDATA signal itself, and the September 'as stored' rule was right. The omas D3D
mapping divides the PTDATA by turns_with_sign (gafusion/omas#449; dead code since v0.92.0, live since v0.96.0),
so FDP exports and any fetch on a released omas carry F-coils 55-58x too small.
- build_fuse29_actuators: coil channels read as stored; _coil_turns removed.
- case_parameters(:D3D): repair_pf_active_omas_turns! multiplies the multi-turn group by its element turns when
  its |I|max over the traces is below 500 A (divided set peaks ~154 A, correct set 6-9 kA), group-wise.
- fuse29_coil_convention_check: F-coils below 500 A = divided (not repaired), above 3x p99 = multiplied.
- torque and gas fallbacks unchanged; tests updated (25 pass).
Validation (FDP exports): loader repair fires on both shots (|I|max 93 A / 57 A); gate 96.7 % (200000) and
99.2 % (199055) with the repaired currents vs 48.8 % / 13.5 % as exported; FRESCO from the repaired dd converges
in 1.5 s instead of 82.5 s to the same equilibrium (boundary rms 1.06 cm, solved amp-turns within 0.1 %).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@smithsp smithsp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me

@smithsp

smithsp commented Oct 27, 2026

Copy link
Copy Markdown
Member

Waiting for @torrinba to also approve

@torrinba torrinba left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this was a mistake. Thanks for catching and fixing it @tomneiser

I think the fix is actually needed for equilibrium.time_slice[:].constraints.pf_current, but since that isn't part of d3d.py it's not quite so simple to add. I agree that it shouldn't be present for pf_active.coil[:].current.data and probably mixed these up in the previous commits

I expect this fix is needed in imas_composer as well

@AreWeDreaming

Copy link
Copy Markdown
Collaborator

@torrinba the toksearch version introduces efit.py and gets rid of the _efit.json direct MDSplus mappings (if I recall correctly). So in principle any efit mapping should basically act like d3d.py from there onwards

@torrinba
torrinba merged commit e5ac83f into gafusion:master Oct 29, 2026
16 checks passed
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.

4 participants