Skip to content

Commit ecd1eb1

Browse files
committed
update grid to avoid singularity when mixing profile types
1 parent 3adfb08 commit ecd1eb1

4 files changed

Lines changed: 344 additions & 287 deletions

File tree

cfspopcon/formulas/plasma_profiles/plasma_profiles.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
from .density_peaking import calc_density_peaking, calc_effective_collisionality
1313
from .numerical_profile_fits import evaluate_density_and_temperature_profile_fits
1414

15-
RHO_GRID_EDGE_NUDGE = 1.0e-6
16-
1715

1816
@Algorithm.register_algorithm(
1917
return_keys=[
@@ -215,8 +213,9 @@ def calc_1D_plasma_profiles(
215213
dilution: dilution of main ions [~]
216214
normalized_inverse_temp_scale_length: [~] :term:`glossary link<normalized_inverse_temp_scale_length>`
217215
n_points_for_confined_region_profiles: Number of points to return in the profile grid.
218-
Non-JCH grids stop at ``rho = 1 - 1e-6`` instead of exactly 1.0 so
219-
hollow analytic profiles remain finite at the separatrix.
216+
All profile grids stop about one tenth of a grid spacing inside the
217+
LCFS so hollow analytic profiles remain finite without letting the
218+
final trapezoid dominate the volume integral.
220219
pedestal_width: Pedestal width in normalized rho for JCH profiles.
221220
t_sep: Separatrix temperature used to anchor the JCH edge temperature profile.
222221
n_sep_ratio: Ratio of separatrix density to pedestal density for JCH profiles.
@@ -520,26 +519,37 @@ def _find_nearest_interior_grid_index(values: np.ndarray, target: float) -> int:
520519
return 1 + _find_nearest_grid_index(values[1:-1], target)
521520

522521

522+
def _calc_profile_grid_edge_nudge(npoints: int) -> float:
523+
"""Return the edge offset that keeps the last sample about one tenth of a grid spacing inside the LCFS."""
524+
if npoints <= 1:
525+
return 0.0
526+
527+
# Choose the endpoint offset so it is one tenth of the induced grid
528+
# spacing: nudge = 0.1 * drho, drho = (1 - nudge) / (npoints - 1).
529+
return 0.1 / (npoints - 1 + 0.1)
530+
531+
523532
def _build_profile_grid(npoints: int, rho_ped: float | None = None) -> np.ndarray:
524533
"""Build the radial grid and optionally reserve four points across the pedestal.
525534
526-
Non-JCH grids nudge the final sample to ``rho = 1 - 1e-6`` so the analytic
527-
hollow-profile form is never evaluated exactly at its separatrix singularity.
528-
JCH grids keep the explicit separatrix point because the pedestal model is
529-
anchored there.
535+
Non-JCH grids stop about one tenth of a grid spacing inside the LCFS so the
536+
analytic hollow-profile form is regularized without overweighting the final
537+
trapezoid. JCH grids use the same offset so mixed analytic/JCH calls can
538+
safely share a single grid without evaluating analytic hollow profiles at
539+
``rho = 1``.
530540
"""
541+
edge_nudge = _calc_profile_grid_edge_nudge(npoints)
542+
531543
if rho_ped is None:
532-
# Keep the final sample infinitesimally inside the LCFS so hollow
533-
# analytic profiles do not diverge at rho = 1.
534-
return np.linspace(0.0, 1.0 - RHO_GRID_EDGE_NUDGE, num=npoints)
544+
return np.linspace(0.0, 1.0 - edge_nudge, num=npoints)
535545

536546
pedestal_points = 4
537547
if npoints < pedestal_points + 1:
538548
raise ValueError("JCH profile grids require at least five radial points to preserve the axis and four pedestal samples.")
539549

540550
core_points = npoints - pedestal_points + 1
541551
rho_core = np.linspace(0.0, rho_ped, num=core_points)
542-
rho_pedestal = np.linspace(rho_ped, 1.0, num=pedestal_points)
552+
rho_pedestal = np.linspace(rho_ped, 1.0 - edge_nudge, num=pedestal_points)
543553
return np.concatenate((rho_core, rho_pedestal[1:]))
544554

545555

0 commit comments

Comments
 (0)