diff --git a/notebooks/mhd_equilibrium_analysis.ipynb b/notebooks/mhd_equilibrium_analysis.ipynb index c1e9c260d..2b685ee52 100644 --- a/notebooks/mhd_equilibrium_analysis.ipynb +++ b/notebooks/mhd_equilibrium_analysis.ipynb @@ -207,7 +207,7 @@ "q95 8.671 - [linear interpolation]\n", "q_edge 11.73 - [profile endpoint]\n", "beta_t 0.001274 - [volume and pressure integration]\n", - "beta_n 0.0702 % m T / MA [derived]\n", + "beta_n 0.0702 - [derived]\n", "li_virial 0.6876 - [Lao virial closure]\n", "shafranov_shift 0.0284 m [difference]\n", "magnetic_shear 129-point profile - [finite differences]\n" @@ -221,7 +221,7 @@ " (\"elongation\", 1.0, \"-\"), (\"triangularity_upper\", 1.0, \"-\"),\n", " (\"triangularity_lower\", 1.0, \"-\"), (\"volume\", 1.0, \"m^3\"),\n", " (\"cross_section_area\", 1.0, \"m^2\"), (\"q0\", 1.0, \"-\"), (\"q95\", 1.0, \"-\"),\n", - " (\"q_edge\", 1.0, \"-\"), (\"beta_t\", 1.0, \"-\"), (\"beta_n\", 1.0, \"% m T / MA\"),\n", + " (\"q_edge\", 1.0, \"-\"), (\"beta_t\", 1.0, \"-\"), (\"beta_n\", 1.0, \"-\"),\n", " (\"li_virial\", 1.0, \"-\"), (\"shafranov_shift\", 1.0, \"m\"),\n", " (\"magnetic_shear\", 1.0, \"-\"),\n", ")\n", diff --git a/test/test_parametric_equilibrium.py b/test/test_parametric_equilibrium.py index 75faebf6f..1a25f7fea 100644 --- a/test/test_parametric_equilibrium.py +++ b/test/test_parametric_equilibrium.py @@ -129,6 +129,9 @@ def test_global_descriptors_and_radial_coordinates_have_definitions(): assert descriptors["volume"].value == pytest.approx(2 * np.pi**2 * 1.0 * 0.35 * 0.5, rel=2e-3) assert descriptors["thermal_energy"].available assert descriptors["beta_t"].available + assert descriptors["beta_n"].available + assert descriptors["beta_n"].unit == "1" + assert descriptors["beta_n"].quality.get("scaling_convention") == "% m T / MA" assert len(descriptors.rational_surfaces[1.5]) == 1 radial = derive_radial_coordinates(eq) np.testing.assert_allclose(radial["psi_n"].value[[0, -1]], [0, 1]) @@ -576,3 +579,11 @@ def test_explicit_flux_tolerance_overrides_the_derived_window(): strict = derive_boundary_representation(eq, flux_tolerance=0.0) assert strict.provenance.tolerances["xpoint_flux_psi_n"] == 0.0 assert not strict.topology.is_diverted + + +def test_unavailable_beta_n_has_dimensionless_unit(): + eq = _analytic_equilibrium() + eq_no_p = EquilibriumData(**{**eq.__dict__, "pressure": None}) + descriptors = derive_global_descriptors(eq_no_p) + assert not descriptors["beta_n"].available + assert descriptors["beta_n"].unit == "1" diff --git a/vaft/process/_equilibrium_parametric.py b/vaft/process/_equilibrium_parametric.py index b704149cb..16337a873 100644 --- a/vaft/process/_equilibrium_parametric.py +++ b/vaft/process/_equilibrium_parametric.py @@ -779,7 +779,12 @@ def derive_global_descriptors( centimetres apart. The poloidal field is computed through the equilibrium's own declared COCOS and per-radian flag, so a wrongly declared convention propagates into every field-derived quantity here. The magnetic shear is - taken against the poloidal radius. + taken against the poloidal radius. The normalized beta follows the Troyon + convention ``beta_N = 100 * beta_t * a * |B_t0| / |I_p|`` (with ``a`` in + metres, ``B_t0`` in tesla, and ``I_p`` in megamperes); its published unit + is ``"1"`` (dimensionless) conforming to IMAS ``global_quantities.beta_normal``, + with the conventional ``% * m * T / MA`` scaling factor folded into the value + and recorded in the derivation quality metadata. Applicability ------------- @@ -790,9 +795,8 @@ def derive_global_descriptors( Every quantity degrades independently: a missing pressure profile costs the energies and the betas but not the geometry. A failure inside the pressure integration is captured as a warning on the returned report rather than - raised. The normalized beta carries a non-SI unit string where the rest of - the module is SI, tracked in #607. The virial internal inductance is - ill-conditioned as its coefficient approaches one. + raised. The virial internal inductance is ill-conditioned as its coefficient + approaches one. Provenance ---------- @@ -916,11 +920,15 @@ def derive_global_descriptors( values["beta_t"] = _derived(eq, beta_t, "1", "2*mu0*
/Bt0^2", "volume and pressure integration", ("pressure", "bt0")) if geometry and eq.ip not in (None, 0): beta_n = 100 * beta_t * values["minor_radius"].value * abs(eq.bt0) / (abs(eq.ip) / 1e6) - values["beta_n"] = _derived(eq, beta_n, "% m T / MA", "100*beta_t*a*abs(Bt0)/abs(Ip_MA)", "derived", ("beta_t", "minor_radius", "bt0", "ip")) + values["beta_n"] = _derived( + eq, beta_n, "1", "100*beta_t*a*abs(Bt0)/abs(Ip_MA)", "derived", + ("beta_t", "minor_radius", "bt0", "ip"), + {"scaling_convention": "% m T / MA"}, + ) if average_pressure is not None and bpa not in (None, 0): values["beta_p_boundary_average"] = _derived(eq, 2 * MU0 * average_pressure / bpa**2, "1", "2*mu0*
/ /Bt0^2"), ("beta_n", "100*beta_t*a*abs(Bt0)/abs(Ip_MA)"), ("beta_p_boundary_average", "2*mu0* /