|
12 | 12 | from .density_peaking import calc_density_peaking, calc_effective_collisionality |
13 | 13 | from .numerical_profile_fits import evaluate_density_and_temperature_profile_fits |
14 | 14 |
|
15 | | -RHO_GRID_EDGE_NUDGE = 1.0e-6 |
16 | | - |
17 | 15 |
|
18 | 16 | @Algorithm.register_algorithm( |
19 | 17 | return_keys=[ |
@@ -215,8 +213,9 @@ def calc_1D_plasma_profiles( |
215 | 213 | dilution: dilution of main ions [~] |
216 | 214 | normalized_inverse_temp_scale_length: [~] :term:`glossary link<normalized_inverse_temp_scale_length>` |
217 | 215 | 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. |
220 | 219 | pedestal_width: Pedestal width in normalized rho for JCH profiles. |
221 | 220 | t_sep: Separatrix temperature used to anchor the JCH edge temperature profile. |
222 | 221 | 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: |
520 | 519 | return 1 + _find_nearest_grid_index(values[1:-1], target) |
521 | 520 |
|
522 | 521 |
|
| 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 | + |
523 | 532 | def _build_profile_grid(npoints: int, rho_ped: float | None = None) -> np.ndarray: |
524 | 533 | """Build the radial grid and optionally reserve four points across the pedestal. |
525 | 534 |
|
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``. |
530 | 540 | """ |
| 541 | + edge_nudge = _calc_profile_grid_edge_nudge(npoints) |
| 542 | + |
531 | 543 | 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) |
535 | 545 |
|
536 | 546 | pedestal_points = 4 |
537 | 547 | if npoints < pedestal_points + 1: |
538 | 548 | raise ValueError("JCH profile grids require at least five radial points to preserve the axis and four pedestal samples.") |
539 | 549 |
|
540 | 550 | core_points = npoints - pedestal_points + 1 |
541 | 551 | 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) |
543 | 553 | return np.concatenate((rho_core, rho_pedestal[1:])) |
544 | 554 |
|
545 | 555 |
|
|
0 commit comments