Repository navigation
Fix swapped inner-quadrant labels in luce_squareness - #318
Conversation
The quadrant loop computes (ou, ol, iu, il) but the unpack assigned the third value to zetail and the fourth to zetaiu, so squareness_upper_inner and squareness_lower_inner were exchanged on output. Outer quadrants were unaffected. Add a per-quadrant test on a Luce superellipse boundary. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
I would appreciate a diagram here. |
The lower quadrants were built by negating the upper-half Z after zoffset had already been added, so for any nonzero zoffset the lower half was reflected about Z = 0 instead of Z = zoffset and ended up displaced by twice the offset. Mirror about zoffset and add a test that a nonzero zoffset is a pure vertical translation, with and without X-points. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Two figures to illustrate the bugs and the fixes. Figure 1: Figure 2: regenerating a boundary from the extracted parameters. Shape parameters are extracted with
With both fixes the test boundary is reproduced exactly. The residual mismatch on the DIII-D case, at the inboard midplane and at the X-point corner on top, is a limitation of the Luce form with a single (Figures are hosted on the |
…d decreasing-psi r_mid Structural tests for the defect class behind #318 (right computation, wrong label / axis / order). Every shape and flux-surface routine is run on an asymmetric input and on the same input translated in Z, reflected up/down, with reversed point order, or with the sign of psi flipped; the outputs must transform the way their labels say. Smoke tests cover the physics files that had no test at all (equilibrium, currents, neoclassical, radiation, transport, pedestal, rf, particles, tf, pf_active, thermal_loads), including Jtor<->Jpar and JtoR<->JparB round trips. Found by the new tests: - geometry.jl intersection: several crossings on one segment of the first path were returned in second-path order instead of along the first path. find_psi_last_diverted relies on along-contour order to decide whether a surface is diverted; on the D3D sample the result differed between the plasma and its mirror image (psi_last_lfs -1.3146 vs -1.3128, converged to 1e-6) because a wall-corner clip was filtered out in one orientation. Sort within-segment crossings along the segment. - fluxsurfaces.jl interp_rmid_at_psi: a decreasing psi(R) was handed to the interpolant as decreasing knots, which FastInterpolations does not support (3 mm error at the far SOL on the sample). Reverse to increasing knots. sol.jl worked around this with its own untruncated interpolant in the psi<0 branch; both signs now go through interp_rmid_at_psi (consistency; no numerical change on the sample for either sign). - fluxsurface_extrema and luce_squareness now return NamedTuples in the same order (positional unpacking unchanged), so consumers can use names. Shared test helpers moved to test/helpers.jl. Noted for the author: flux_surfaces is not idempotent at the 2e-4 level (a second call starts from the refined boundary psi). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…#319) * Fix label, axis and argument-order defects found in a targeted review Review of IMAS.jl for the defect class behind #318 (right computation, wrong label / mirror axis / argument order). Each fix is covered by a test that fails on the previous code and uses an asymmetric case. - thermal_loads.jl mesher_heat_flux: unpacked 3 of the 4 fields returned by find_psi_last_diverted (a 4th field was inserted in Sept 2024), so psi_first_lfs_far received psi_first_open and null_within_wall received a Float. This disabled the wall-mask term that adds points in the private region around the second null (the psi window collapsed to a point), and would have raised a TypeError had that term ever selected a point. For the D3D sample and the FUSE D3D H-mode case the term selects no points, so their meshes are unchanged. Use the named fields. - fluxsurfaces.jl find_psi_last_diverted: limited-plasma branch referenced an undefined variable (UndefVarError). Use find_psi_separatrix. - fluxsurfaces.jl find_psi_separatrix: psi_sep_closed / psi_sep_open were returned swapped (closed label gave an open surface). Swap the return and make the sol.jl limited-case check use psi_sep_closed, which is what the old mislabelled value was. - equilibrium.jl symmetrize_equilibrium!: evaluation on the grid dispatched to an in-place interpolant method and threw; and the clipped Z grid was centred on (grid centre + ZA)/2, so the up-down mirror was not about the magnetic axis. Evaluate pointwise and clip symmetrically about ZA. - fluxsurfaces.jl find_x_point!: private regions classified with sum(pz) < Z0 instead of a mean, and the opposite-side and strike-point ordering tests were relative to Z = 0 instead of Z0, so a vertically shifted plasma lost its second X-point. Reference Z0 throughout. - sol.jl find_levels_from_P: the wall and dd wrappers forwarded (q, r) to the (r, q) core method; the double swap cancelled on the dd path but the wall method failed its r[1] == 0 assertion when called as documented. - geometry.jl curvature: on closed polygons the first point used a reversed entering segment (sign flipped) and the last point a zero-length leaving segment (curvature 0). Use the segments across the closing point. - profiles.jl: retrhow -> rethrow. Downstream: find_psi_separatrix consumers, symmetrize_equilibrium! (FUSE ActorEquilibrium with symmetrize=true) and curvature endpoints (FUSE limit_curvature, init_pf_active) change numerically. FUSE ActorParticleHeatFlux on the D3D H-mode case gives identical output before and after. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Fix remaining Z=0 references, interp_rmid_at_psi truncation and boundary_shape end points Second pass of the review (same defect classes), with asymmetric tests. - fluxsurfaces.jl find_psi_tangent_omp: separatrix half-height, outboard wall window and the two reference lines were taken about Z = 0 instead of the magnetic axis; the function threw on a vertically shifted plasma. - fluxsurfaces.jl find_psi_max: "opposite sides of the midplane" tests used products of raw Z instead of Z - ZA. - thermal_loads.jl mesher_heat_flux: the "beyond the second null" wall test was about Z = 0 instead of Z0. - profiles.jl L_H_threshold: primary x-point above/below is now judged relative to the magnetic axis, as the grad-B drift argument requires. - fluxsurfaces.jl interp_rmid_at_psi: findlast on the sign of dpsi kept every sample up to the last one with the initial sign, so a non-monotonic stretch in the middle was included and the last monotonic sample was always dropped. Keep the first monotonic stretch, including its end. - boundary.jl boundary_shape: the quadrant angle grids excluded theta = pi/2 and pi, so the top, bottom and inner-midplane points were never generated and the [2:end] trims dropped distinct points instead of duplicates (minor radius short by ~1e-4 m, uneven spacing). The closing point also differed from the first by rounding (upper vs lower triangularity), so the polygon was not exactly closed. Generate the end points, drop only shared points, snap the closing point. - sol.jl: rename levels_is_not_monotonic_in_Ip_direction to what it means. - Docstrings: cumlul_volume_integrate / cumlul_surface_integrate named the wrong function, boundary_shape documented `lkap` for `kapl`, getdata omitted the `units` field. Reviewed and left unchanged: bootstrap sign convention, orientation conventions in sol / fluxsurfaces, line_wall_2_wall index heuristics, particle window centring, x-point dedupe, relative convergence tolerances (psi-offset invariance verified), boundary_shape h-search loops, the tollZ band in mesher_heat_flux, dynamic.jl geometric_axis.z / elongation limitations. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Add invariance and smoke tests; fix crossing order in intersection and decreasing-psi r_mid Structural tests for the defect class behind #318 (right computation, wrong label / axis / order). Every shape and flux-surface routine is run on an asymmetric input and on the same input translated in Z, reflected up/down, with reversed point order, or with the sign of psi flipped; the outputs must transform the way their labels say. Smoke tests cover the physics files that had no test at all (equilibrium, currents, neoclassical, radiation, transport, pedestal, rf, particles, tf, pf_active, thermal_loads), including Jtor<->Jpar and JtoR<->JparB round trips. Found by the new tests: - geometry.jl intersection: several crossings on one segment of the first path were returned in second-path order instead of along the first path. find_psi_last_diverted relies on along-contour order to decide whether a surface is diverted; on the D3D sample the result differed between the plasma and its mirror image (psi_last_lfs -1.3146 vs -1.3128, converged to 1e-6) because a wall-corner clip was filtered out in one orientation. Sort within-segment crossings along the segment. - fluxsurfaces.jl interp_rmid_at_psi: a decreasing psi(R) was handed to the interpolant as decreasing knots, which FastInterpolations does not support (3 mm error at the far SOL on the sample). Reverse to increasing knots. sol.jl worked around this with its own untruncated interpolant in the psi<0 branch; both signs now go through interp_rmid_at_psi (consistency; no numerical change on the sample for either sign). - fluxsurface_extrema and luce_squareness now return NamedTuples in the same order (positional unpacking unchanged), so consumers can use names. Shared test helpers moved to test/helpers.jl. Noted for the author: flux_surfaces is not idempotent at the 2e-4 level (a second call starts from the refined boundary psi). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Remove silent error swallowing, stray debug output and misnamed doc registrations - luce_squareness caught every exception and pushed 0.0, so any failure (including a genuine bug) looked like zero squareness. The only expected failure is the quadrant diagonal missing the boundary, which never happens on the sample equilibria (0 of 588 diagonals). That case is now explicit: NaN for that quadrant plus a one-time warning. Any other error propagates. - find_psi_last_diverted plotted and @show'd to the display before asserting on the intersection count; replaced with an error message carrying the intersections. find_flux formats the particle into its error message instead of @show. - Removed commented-out debug plotting/printing in intersection, inscribed_polygon, flux_surfaces, luce_squareness, find_flux, and the dead final_area / total_removed bookkeeping that only fed those prints. - Doc registrations copied from the previous block: Zcoord was registered as Particle, zohm_divertor_figure_of_merit as q_par_omp_eich, total_sources as retain_source, exponential_profile under the stale name edge_profile (undefined); beta_tor was registered twice. Every registered symbol now resolves and no section lists a name twice. Not changed: the empty catch blocks in plot.jl overlay optional diagnostic data and are intentional; the goto catch in total_fluxes skips models that lack an ion. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>


Summary
Two fixes to the Luce shape functions.
1.
luce_squarenessinner-quadrant labels swappedluce_squarenesscomputes the four quadrant squareness values in the order(ou, ol, iu, il)(see thenames/POs/PEstuples), but the unpack after the loop readzetaou, zetaol, zetail, zetaiu = z, assigning the upper-inner value tozetailand the lower-inner value tozetaiu. Since the function returns(zetaou, zetaol, zetail, zetaiu)andmiller_R_a_κ_δ_ζ/flux_surfacesconsume it in that documented order,squareness_upper_innerandsquareness_lower_innerwere exchanged on output. Outer quadrants were unaffected.Verified numerically against an independent implementation on five DIII-D boundaries: outer quadrants agree to 1e-16, inner quadrants agree only after exchanging the labels. A lower-single-null shape (δ_lower = 0.67) had its strongly negative X-point squareness reported as
squareness_upper_inner.2.
boundary_shapelower half reflected about the wrong axisThe lower quadrants of
boundary_shapewere built by negating the upper-half Z afterzoffsethad already been added, so for any nonzerozoffsetthe lower half was mirrored about Z = 0 instead of Z =zoffsetand displaced vertically by twice the offset. Found while regenerating the sample DIII-D boundary from its extracted parameters (outboard extremum at Z = -0.05 m, lower half 0.10 m too high). No callers in IMAS or FUSE pass a nonzerozoffsettoday.Changes
luce_squareness(src/physics/fluxsurfaces.jl). Return order, docstring, and quadrant geometry are unchanged.boundary_shapeaboutzoffset(src/physics/boundary.jl).luce_squareness quadrant labelsintest/runtests_fluxsurfaces.jl: builds a boundary from Luce's superellipse parameterisation (PPCF 55 (2013) 095009) with four distinct per-quadrant squareness values and checks each is recovered in the right quadrant. A mirror-symmetric test cannot catch this bug; this one fails before the fix with the two inner values exchanged and passes after.boundary_shape zoffset: a nonzerozoffsetmust be a pure vertical translation of thezoffset = 0shape, with and without X-points. Fails 9 of 18 before the fix.Downstream impact
Consumers of
squareness_lower_inner/squareness_upper_inner(e.g. FUSE pulse_schedule initialisation, shape-control targets, any stored references derived from earlier IMAS output) will see the two inner values exchange after this fix.Test plan
luce_squareness quadrant labelsfails before the fix (ζilgets -0.20,ζiugets 0.25) and passes afterboundary_shape zoffsetfails before the fix (9/18) and passes afterPkg.test("IMAS")passes🤖 Generated with Claude Code