Source code for psi_io._units

  1r"""Physical constants and normalization factors for PSI MAS model output.
  2
  3The MAS (Magnetohydrodynamic Algorithm outside a Sphere) code solves the resistive
  4MHD equations in *dimensionless* form.  Every quantity stored in an HDF output file is
  5a dimensionless ratio
  6
  7.. math::
  8
  9   q_\text{code} = \frac{q_\text{phys}}{q_0}
 10
 11where :math:`q_0` is the characteristic scale for that quantity.  This module
 12collects all reference scales so that code-unit values can be converted to physical
 13(CGS or SI) units via :mod:`astropy.units`.
 14
 15Normalization Scheme
 16--------------------
 17MAS adopts a normalization anchored to two observable solar parameters and one
 18reference number density:
 19
 20.. list-table::
 21   :header-rows: 1
 22
 23   * - Symbol
 24     - Quantity
 25     - Value
 26   * - :data:`FNORML` = :data:`RSUN`
 27     - Characteristic length
 28     - :math:`6.96 \times 10^{10}` cm
 29   * - :data:`G0PHYS`
 30     - Solar surface gravity
 31     - :math:`2.74 \times 10^4` cm s⁻²
 32   * - :data:`FN0PHYS`
 33     - Reference number density
 34     - :math:`10^8` cm⁻³
 35
 36From these three anchors, the characteristic **time** is fixed by requiring the
 37dimensionless surface gravity :math:`G_0^\text{norm} = 0.823`:
 38
 39.. math::
 40
 41   T_0 = \sqrt{\frac{G_0^\text{norm}\, R_\odot}{g_0}} \approx 1446 \;\text{s}
 42
 43All other normalization factors follow from dimensional analysis:
 44
 45.. list-table::
 46   :header-rows: 1
 47
 48   * - Symbol
 49     - Quantity
 50     - Formula
 51   * - :data:`FN_V`
 52     - Velocity
 53     - :math:`V_0 = L_0 / T_0 \approx 481` km s⁻¹
 54   * - :data:`FN_RHO`
 55     - Mass density
 56     - :math:`\rho_0 = m_p n_0`
 57   * - :data:`FN_T`
 58     - Temperature
 59     - :math:`T_0^\text{temp} = m_p V_0^2 / k_B \approx 28` MK
 60   * - :data:`FN_P`
 61     - Pressure
 62     - :math:`P_0 = \rho_0 V_0^2`
 63   * - :data:`FN_B`
 64     - Magnetic field
 65     - :math:`B_0 = V_0 \sqrt{\mu_0 \rho_0} \approx 2.2` G
 66   * - :data:`FN_J`
 67     - Current density (SI)
 68     - :math:`J_0 = B_0 / (\mu_0 L_0)` A m⁻²
 69   * - :data:`FN_HEAT`
 70     - Volumetric heating rate
 71     - :math:`Q_0 = P_0 / T_0` erg cm⁻³ s⁻¹
 72
 73Electromagnetic Units
 74---------------------
 75MAS is formulated in Gaussian CGS, where Ampère's law reads
 76:math:`\mathbf{J} = (c/4\pi)\,\nabla \times \mathbf{B}`.  Because current density
 77in Gaussian CGS (statampere cm⁻²) is unfamiliar to most users, this module provides
 78both SI (:data:`FN_J`, :data:`FN_E`) and Gaussian CGS (:data:`FN_J_CGS`,
 79:data:`FN_E_CGS`) normalizations for these quantities.
 80
 81Magnetic field is expressed in Gauss throughout, since that system is unambiguous and
 82Gauss is the conventional unit for coronal magnetic field strengths.
 83
 84Custom Astropy Units
 85--------------------
 86Each PSI quantity has a dedicated :class:`~astropy.units.UnitBase` registered at
 87module import time.  These units carry the conversion factor from code units to
 88physical units so that astropy can chain conversions automatically — for example,
 89a quantity returned by :mod:`psi_io.mhd_io` in ``MAS_b`` units can be converted
 90with a single ``.to(u.Gauss)`` call.
 91
 92Units are registered both under a *family name* (e.g. ``MAS_b``) and under every
 93individual quantity name in that family (e.g. ``MAS_br``, ``MAS_bt``, ``MAS_bp``),
 94so that the unit embedded in an :class:`~astropy.units.Quantity` always reflects
 95the specific field component.
 96
 97.. list-table:: Registered PSI Custom Units
 98   :header-rows: 1
 99   :widths: 12 24 16 48
100
101   * - Unit object
102     - Physical equivalent
103     - Module constant
104     - Registered string names
105   * - :data:`MAS_b`
106     - :math:`\approx 2.2` G
107     - :data:`FN_B`
108     - ``MAS_b``, ``MAS_br``, ``MAS_bt``, ``MAS_bp``
109   * - :data:`MAS_v`
110     - :math:`\approx 481` km s⁻¹
111     - :data:`FN_V`
112     - ``MAS_v``, ``MAS_vr``, ``MAS_vt``, ``MAS_vp``, ``MAS_zp``, ``MAS_zm``
113   * - :data:`MAS_j`
114     - :math:`J_0` A m⁻²
115     - :data:`FN_J`
116     - ``MAS_j``, ``MAS_jr``, ``MAS_jt``, ``MAS_jp``
117   * - :data:`MAS_t`
118     - :math:`\approx 28` MK
119     - :data:`FN_T`
120     - ``MAS_t``, ``MAS_te``, ``MAS_tp``
121   * - :data:`MAS_n`
122     - :math:`10^8` cm⁻³
123     - :data:`FN_N`
124     - ``MAS_n``, ``MAS_rho``
125   * - :data:`MAS_p`
126     - :math:`P_0` erg cm⁻³
127     - :data:`FN_P`
128     - ``MAS_p``, ``MAS_ep``, ``MAS_em``
129   * - :data:`MAS_heat`
130     - :math:`Q_0` erg cm⁻³ s⁻¹
131     - :data:`FN_HEAT`
132     - ``MAS_heat``
133   * - :data:`POT3D_b`
134     - 1 (dimensionless)
135     - —
136     - ``POT3D_b``, ``POT3D_br``, ``POT3D_bt``, ``POT3D_bp``
137   * - :data:`PSI_rsun`
138     - :math:`6.96 \times 10^{10}` cm
139     - :data:`RSUN`
140     - ``PSI_rsun``, ``PSI_radius``, ``PSI_r``
141   * - :data:`PSI_angle`
142     - 1 rad
143     - —
144     - ``PSI_angle``, ``PSI_t``, ``PSI_p``, ``PSI_theta``, ``PSI_phi``
145
146See Also
147--------
148:mod:`astropy.units` :
149    The underlying unit system used for normalization and conversion.
150:mod:`psi_io._models` :
151    Maps each MAS quantity name to its :data:`FN_*` normalization.
152:mod:`psi_io.mhd_io` :
153    Lazy readers that apply these normalizations on property access.
154"""
155
156from __future__ import annotations
157
158__all__ = [
159    "FAVORED_UNITS",
160    "compose_mas_units",
161    "decompose_mas_units",
162    "get_helium_fractions",
163    "MAS_b",
164    "MAS_v",
165    "MAS_j",
166    "MAS_t",
167    "MAS_n",
168    "MAS_p",
169    "MAS_heat",
170    "POT3D_b",
171    "PSI_rsun",
172    "PSI_angle"
173]
174
175import astropy.units as u
176import numpy as np
177
178
179FAVORED_UNITS = (u.erg, u.cm, u.s, u.K, u.Gauss, u.g, u.rad)
180"""Tuple of CGS units preferred when composing or decomposing MAS quantities.
181
182Used by :func:`compose_mas_units` and :func:`decompose_mas_units` to express
183results in the most physically intuitive CGS basis: erg, cm, s, K, Gauss, g, rad.
184Gauss is included explicitly because :func:`astropy.units.compose` would otherwise
185resolve magnetic field to mixed electromagnetic CGS units.
186
187A tuple (not a set) is used so that Sphinx's autodoc renderer does not call
188``sorted()`` on the elements — astropy unit raise :exc:`~astropy.units.UnitConversionError`
189when compared across incompatible physical dimensions (e.g. ``g`` vs ``K``), and
190Sphinx 9+ does not catch that exception type.
191"""
192
193
[docs] 194def compose_mas_units(quantity: u.Quantity) -> u.Quantity: 195 """Express a quantity in the preferred MAS CGS units basis. 196 197 Calls :meth:`astropy.units.UnitBase.compose` with :data:`FAVORED_UNITS` as the 198 allowed unit set. The first (simplest) composed unit that astropy returns is used. 199 200 Parameters 201 ---------- 202 quantity : u.Quantity 203 Input quantity in any unit system. 204 205 Returns 206 ------- 207 out : u.Quantity 208 Equivalent quantity expressed in the MAS-preferred CGS units basis 209 (erg, cm, s, K, Gauss, g, rad). 210 211 See Also 212 -------- 213 decompose_mas_units : Decomposes to CGS *base* unit rather than composing. 214 215 Examples 216 -------- 217 >>> import astropy.units as u 218 >>> from psi_io._units import compose_mas_units, FN_P 219 >>> compose_mas_units(1.0 * u.Pa) # doctest: +ELLIPSIS 220 <Quantity ... erg / cm3> 221 """ 222 return quantity.to(quantity.unit.compose(units=FAVORED_UNITS)[0])
223 224
[docs] 225def decompose_mas_units(quantity: u.Quantity) -> u.Quantity: 226 """Decompose a quantity into the preferred MAS CGS base unit. 227 228 Calls :meth:`astropy.units.Quantity.decompose` with :data:`FAVORED_UNITS` as the 229 allowed bases. Unlike :func:`compose_mas_units`, this always expands compound 230 unit (e.g. Joule → erg) without attempting to find a compact composed form. 231 232 Parameters 233 ---------- 234 quantity : u.Quantity 235 Input quantity in any unit system. 236 237 Returns 238 ------- 239 out : u.Quantity 240 Equivalent quantity with all units decomposed into the MAS-preferred CGS 241 bases. 242 243 See Also 244 -------- 245 compose_mas_units : Composes to a simpler combined CGS units where possible. 246 247 Examples 248 -------- 249 >>> import astropy.units as u 250 >>> from psi_io._units import decompose_mas_units 251 >>> decompose_mas_units(1.0 * u.J) # doctest: +ELLIPSIS 252 <Quantity 1.e+07 erg> 253 """ 254 return quantity.decompose(bases=list(FAVORED_UNITS))
255 256 257# ============================================================================= 258# Physical constants 259# ============================================================================= 260 261PI = 3.1415926535897932 262"""High-precision value of π. 263 264Stored as a module-level float to avoid repeated calls to :func:`math.pi` or 265:data:`numpy.pi` in constant expressions evaluated at import time. 266""" 267 268RSUN = 6.96e10 * u.cm 269"""Solar radius in centimeters. 270 271The canonical value :math:`R_\\odot = 6.96 \\times 10^{10}` cm used throughout PSI 272models as the characteristic length scale. Also aliased as :data:`FN_LENGTH` and 273:data:`FNORML`. 274""" 275 276G0PHYS = 0.274e5 * u.cm / u.s**2 277"""Solar surface gravitational acceleration in CGS. 278 279:math:`g_0 = 2.74 \\times 10^4` cm s⁻², the standard solar surface gravity. 280Together with :data:`RSUN` and :data:`G0NORM`, this fixes the MAS time normalization 281:data:`FNORMT`. 282""" 283 284FN0PHYS = 1e8 / u.cm**3 285"""Reference number density of the coronal base in CGS. 286 287:math:`n_0 = 10^8` cm⁻³, a representative electron (or proton) number density at 288:math:`r = 1\\,R_\\odot` for a quiescent solar corona. Used together with 289:data:`FMP` to set the mass-density normalization :data:`FN_RHO`. 290""" 291 292FMP = 1.6726e-24 * u.g 293"""Proton mass in CGS. 294 295:math:`m_p = 1.6726 \\times 10^{-24}` g. The MAS density normalization assumes a 296fully ionized hydrogen plasma, so mass density is proportional to number density 297through :math:`\\rho_0 = m_p n_0`. 298""" 299 300BOLTZ = 1.3807e-16 * u.erg / u.K 301"""Boltzmann constant in CGS. 302 303:math:`k_B = 1.3807 \\times 10^{-16}` erg K⁻¹. Used to derive the temperature 304normalization :data:`FN_T` from the kinetic pressure relation. 305""" 306 307FKSPITZ = 9e-7 # erg cm^-1 s^-1 K^(-7/2) 308"""Spitzer parallel thermal conductivity prefactor in CGS. 309 310The Spitzer heat flux along the magnetic field is 311 312.. math:: 313 314 \\mathbf{q} = -\\kappa_0 T^{5/2}\\, \\hat{\\mathbf{b}}\\hat{\\mathbf{b}} \\cdot \\nabla T 315 316where :math:`\\kappa_0 = 9 \\times 10^{-7}` erg cm⁻¹ s⁻¹ K⁻⁷/² is the Spitzer 317conductivity coefficient for a fully ionized hydrogen plasma. This constant is stored 318as a plain :class:`float` (not an astropy :class:`~astropy.units.Quantity`) because it 319is used directly in normalized code expressions; the associated physical unit are 320erg cm⁻¹ s⁻¹ K⁻⁷/². 321 322See :data:`FN_KAPPA` for the conductivity normalization that converts the dimensionless 323code conductivity back to physical unit. 324""" 325 326 327# ============================================================================= 328# Convenience unit containers 329# ============================================================================= 330 331FLUX_UNIT = u.erg / (u.cm**2 * u.s) 332"""Astropy unit for surface energy flux: erg cm⁻² s⁻¹.""" 333 334VOLUMETRIC_RATE_UNIT = u.erg / (u.cm**3 * u.s) 335"""Astropy unit for volumetric energy deposition rate: erg cm⁻³ s⁻¹.""" 336 337 338# ============================================================================= 339# Normalization anchors 340# ============================================================================= 341 342G0NORM = 0.823 343"""Dimensionless surface gravity in MAS code unit. 344 345MAS defines a dimensionless gravitational acceleration profile 346 347.. math:: 348 349 g_\\text{code}(r) = \\frac{G_0^\\text{norm}}{r^2} 350 351so that the surface value :math:`g_\\text{code}(1) = G_0^\\text{norm} = 0.823`. 352The physical surface gravity is then :math:`g_0 = G_0^\\text{norm}\\,L_0/T_0^2`, 353which gives 354 355.. math:: 356 357 T_0 = \\sqrt{\\frac{G_0^\\text{norm}\\,L_0}{g_0}} \\approx 1446\\;\\text{s}. 358 359The value 0.823 is a calibration choice made when deriving :data:`FNORMT`; it is not 360a universal solar constant. 361""" 362 363FNORML = RSUN 364"""Characteristic length scale for MAS: the solar radius. 365 366:math:`L_0 = R_\\odot = 6.96 \\times 10^{10}` cm. All spatial coordinates in MAS 367output are in unit of :math:`L_0`. Aliased as :data:`FN_LENGTH`. 368""" 369 370 371# ============================================================================= 372# Derived normalization factors 373# ============================================================================= 374 375FNORMT = np.sqrt(G0NORM * FNORML / G0PHYS) 376"""Characteristic time scale for MAS. 377 378Derived from the dimensionless surface gravity :data:`G0NORM`, the characteristic 379length :data:`FNORML`, and the physical surface gravity :data:`G0PHYS`: 380 381.. math:: 382 383 T_0 = \\sqrt{\\frac{G_0^\\text{norm}\\,R_\\odot}{g_0}} 384 \\approx 1446\\;\\text{s} \\approx 24\\;\\text{min}. 385 386Aliased as :data:`FN_TIME`. 387""" 388 389FNORMM = FN0PHYS * FMP * np.power(FNORML, 3) 390"""Characteristic mass scale for MAS. 391 392:math:`M_0 = n_0\\,m_p\\,L_0^3 = \\rho_0\\,L_0^3`. Represents the total mass 393contained in a cube of side :math:`L_0 = R_\\odot` at the reference density. 394""" 395 396FN_N = FN0PHYS 397"""Number density normalization: :math:`n_0 = 10^8` cm⁻³. 398 399Code-unit number density 1 corresponds to :math:`10^8` particles cm⁻³. 400Note that MAS stores *mass* density (dimensionless :math:`\\rho`), related to the 401number density by :math:`\\rho_\\text{code} = (m_p / n_0) \\cdot n`. See 402:data:`FN_RHO` for the mass-density normalization. 403""" 404 405FN_RHO = FMP * FN0PHYS 406"""Mass density normalization for MAS. 407 408:math:`\\rho_0 = m_p\\,n_0 \\approx 1.67 \\times 10^{-16}` g cm⁻³, assuming a 409fully ionized hydrogen plasma where the mass per particle is the proton mass. 410""" 411 412FN_T = ((FMP * FNORML**2 / FNORMT**2).to(u.erg) / BOLTZ).to(u.MK) 413"""Temperature normalization for MAS. 414 415Derived from the condition that the ideal-gas pressure :math:`P = n\\,k_B\\,T` equals 416the dynamic pressure :math:`\\rho_0\\,V_0^2` at code-unit temperature 1: 417 418.. math:: 419 420 T_0 = \\frac{m_p\\,V_0^2}{k_B} = \\frac{m_p\\,L_0^2}{k_B\\,T_0^2} 421 \\approx 28\\;\\text{MK}. 422 423Code-unit temperatures of order unity therefore correspond to tens of megakelvin, 424consistent with hot coronal plasma. 425""" 426 427FN_V = (FNORML / FNORMT).to(u.km / u.s) 428"""Velocity normalization for MAS. 429 430:math:`V_0 = L_0 / T_0 \\approx 481` km s⁻¹. This is comparable to the fast solar 431wind speed, making code-unit velocities of order unity physically natural for 432heliospheric modelling. 433""" 434 435# Magnetic field normalization requires a CGS–SI bridge. 436# MAS is formulated in Gaussian CGS but astropy's CGS electromagnetic unit are 437# incomplete. The SI μ₀ is used here as a conversion bridge: in Gaussian CGS the 438# Alfvénic normalization B₀ = V₀ √(4πρ₀) is algebraically equivalent to the SI 439# expression B₀ = V₀ √(μ₀ ρ₀) once the result is converted to Gauss. 440MU0 = 4 * PI * 1e-7 * u.N / u.A**2 441"""Permeability of free space (SI). 442 443:math:`\\mu_0 = 4\\pi \\times 10^{-7}` N A⁻², used here as an algebraic bridge to 444compute the magnetic field normalization :data:`FN_B` and current density 445normalization :data:`FN_J` in SI, which are then converted to the required CGS or 446SI output unit. 447 448In Gaussian CGS, the Alfvénic magnetic normalization is 449:math:`B_0 = V_0\\sqrt{4\\pi\\rho_0}`, which is numerically equivalent to the SI 450expression :math:`B_0 = V_0\\sqrt{\\mu_0\\rho_0}` when both results are expressed in 451Gauss. 452""" 453 454FN_B = (np.sqrt(MU0 * FN_RHO) * FNORML / FNORMT).to(u.Gauss) 455"""Magnetic field normalization for MAS, in Gauss. 456 457The code B field is normalized so that the Alfvén speed equals the reference velocity 458:data:`FN_V` when :math:`B_\\text{code} = 1`: 459 460.. math:: 461 462 B_0 = V_0\\sqrt{\\mu_0\\,\\rho_0} \\approx 2.2\\;\\text{G}. 463 464This is on the order of typical large-scale coronal magnetic field strengths. 465""" 466 467FN_J = (FN_B.to(u.Tesla) / FNORML.to(u.m) / MU0).to(u.ampere / u.m**2) 468"""Current density normalization for MAS, in SI (A m⁻²). 469 470From Ampère's law :math:`\\mathbf{J} = \\mu_0^{-1}\\nabla\\times\\mathbf{B}` (SI): 471 472.. math:: 473 474 J_0 = \\frac{B_0}{\\mu_0\\,L_0}. 475 476SI unit are used here because current density in Gaussian CGS (statampere cm⁻²) is 477rarely used in practice. See :data:`FN_J_CGS` for the Gaussian equivalent. 478""" 479 480FN_E = (FN_B.to(u.Tesla) * FN_V.to(u.m / u.s)).to(u.Volt / u.m) 481"""Electric field normalization for MAS, in SI (V m⁻¹). 482 483In ideal MHD the electric field is :math:`\\mathbf{E} = -\\mathbf{v}\\times\\mathbf{B}`, 484so the natural normalization is 485 486.. math:: 487 488 E_0 = V_0\\,B_0. 489 490SI unit are used for the same reasons as :data:`FN_J`. See :data:`FN_E_CGS` for the 491Gaussian equivalent. 492""" 493 494# --------------------------------------------------------------------------- 495# Gaussian CGS conversions for J and E 496# --------------------------------------------------------------------------- 497C_CGS = 2.99792458e10 498"""Speed of light in CGS: :math:`c = 2.998 \\times 10^{10}` cm s⁻¹. 499 500Used to convert between SI and Gaussian electromagnetic unit. 501""" 502 503STATAMP_TO_AMPERE = 10 / C_CGS 504"""Conversion factor from statampere to ampere. 505 506In Gaussian CGS, charge is measured in statcoulombs (esu). One statampere 507(statcoulomb per second) equals :math:`10/c` amperes, where :math:`c` is the speed 508of light in cm s⁻¹: 509 510.. math:: 511 512 1\\;\\text{statA} = \\frac{10}{c}\\;\\text{A} \\approx 3.336 \\times 10^{-10}\\;\\text{A}. 513""" 514 515FN_J_CGS = FN_J.value / (1e4 * STATAMP_TO_AMPERE) * u.statA / u.cm**2 516"""Current density normalization for MAS, in Gaussian CGS (statA cm⁻²). 517 518Equivalent to :data:`FN_J` expressed in Gaussian electromagnetic unit. The 519conversion from SI to Gaussian CGS is: 520 521.. math:: 522 523 1\\;\\text{A m}^{-2} = \\frac{1}{10^4 \\times (10/c)}\\;\\text{statA cm}^{-2} 524 525where :math:`c` is :data:`C_CGS`. 526""" 527 528STATVOLT_TO_VOLT = C_CGS * 1e-8 529"""Conversion factor from statvolt to volt. 530 531One statvolt (erg per statcoulomb) equals :math:`c \\times 10^{-8}` volts, where 532:math:`c` is the speed of light in cm s⁻¹: 533 534.. math:: 535 536 1\\;\\text{statV} = c \\times 10^{-8}\\;\\text{V} \\approx 299.8\\;\\text{V}. 537""" 538 539FN_E_CGS = FN_E.value / (1e2 * STATVOLT_TO_VOLT) * u.erg / u.statC / u.cm 540"""Electric field normalization for MAS, in Gaussian CGS (statV cm⁻¹). 541 542Equivalent to :data:`FN_E` expressed in Gaussian electromagnetic unit (statvolt per 543centimeter, where 1 statvolt = 1 erg / statcoulomb). The conversion from SI to 544Gaussian CGS is: 545 546.. math:: 547 548 1\\;\\text{V m}^{-1} = \\frac{1}{10^2 \\times c \\times 10^{-8}}\\;\\text{statV cm}^{-1} 549 550where :math:`c` is :data:`C_CGS`. 551""" 552 553# --------------------------------------------------------------------------- 554# Thermodynamic and wave-energy normalizations 555# --------------------------------------------------------------------------- 556 557FN_P = compose_mas_units(FMP * FN0PHYS * FNORML**2 / FNORMT**2) 558"""Pressure normalization for MAS. 559 560The dynamic pressure :math:`P_0 = \\rho_0\\,V_0^2 = m_p\\,n_0\\,(L_0/T_0)^2`, 561expressed in the preferred MAS CGS basis (erg cm⁻³). Code-unit pressure 1 maps to 562this value. 563""" 564 565FN_QRAD = compose_mas_units(FMP * np.power(FNORML, 2) / (FN0PHYS * np.power(FNORMT, 3))) 566"""Radiative loss function normalization for MAS. 567 568The radiative loss per unit volume in the energy equation takes the form 569:math:`Q_\\text{rad} = n_e\\,n_p\\,\\Lambda(T)`, where :math:`\\Lambda(T)` is the 570optically thin loss function (erg cm³ s⁻¹). In MAS code units, the dimensionless 571loss function :math:`\\Lambda_\\text{code}` satisfies 572 573.. math:: 574 575 \\Lambda_\\text{phys} = \\Lambda_\\text{code} \\times 576 \\frac{m_p\\,L_0^2}{n_0\\,T_0^3}. 577 578This normalization ensures the radiative source term enters the dimensionless energy 579equation with a coefficient of order unity for typical coronal conditions. 580""" 581 582FN_KAPPA = compose_mas_units(BOLTZ * FN0PHYS * np.power(FNORML, 2) / FNORMT) 583"""Thermal conductivity normalization for MAS. 584 585The Spitzer parallel conductivity :math:`\\kappa = \\kappa_0 T^{5/2}` (with 586:data:`FKSPITZ` = :math:`\\kappa_0`) has unit of erg cm⁻¹ s⁻¹ K⁻¹. In MAS code 587unit the dimensionless conductivity :math:`\\kappa_\\text{code}` satisfies 588 589.. math:: 590 591 \\kappa_\\text{phys} = \\kappa_\\text{code} \\times 592 \\frac{k_B\\,n_0\\,L_0^2}{T_0}. 593 594The dimensionless Spitzer coefficient stored in the code is therefore 595 596.. math:: 597 598 F_\\kappa^\\text{code} = \\kappa_0 599 \\frac{T_0^{5/2}}{k_B\\,n_0\\,L_0^2/T_0\\cdot T_0^{5/2}} 600 = \\frac{\\kappa_0\\,T_0^{5/2}}{F_\\kappa}. 601""" 602 603FN_FLUX = compose_mas_units(FN_P * FN_V).to(FLUX_UNIT) 604"""Surface energy flux normalization for MAS. 605 606:math:`F_0 = P_0\\,V_0` in erg cm⁻² s⁻¹. This scale governs the Poynting flux, 607enthalpy flux, and wave-energy flux that appear in the energy equation boundary 608conditions. 609""" 610 611FN_HEAT = compose_mas_units(FN_P / FNORMT).to(VOLUMETRIC_RATE_UNIT) 612"""Volumetric heating rate normalization for MAS. 613 614:math:`Q_0 = P_0 / T_0` in erg cm⁻³ s⁻¹. Code-unit heating rate 1 corresponds to 615the reference dynamic pressure dissipated over one characteristic time. This sets 616the scale for empirical coronal heating prescriptions and wave-turbulence heating in 617MAS. 618""" 619 620FN_JB = compose_mas_units(FN_P / FNORML) 621"""Lorentz force density (and pressure gradient) normalization for MAS. 622 623:math:`(\\mathbf{J}\\times\\mathbf{B})_0 = P_0 / L_0` in erg cm⁻⁴ (equivalent to 624dyne cm⁻³). Both the magnetic Lorentz force and the plasma pressure gradient appear 625in the MAS momentum equation with this normalization, ensuring they enter on equal 626footing. 627""" 628 629# --------------------------------------------------------------------------- 630# Total energy scales 631# --------------------------------------------------------------------------- 632 633W = FN_P * FNORML**3 634"""Total (integrated) energy normalization for MAS. 635 636:math:`W_0 = P_0\\,L_0^3` in erg. The total magnetic, kinetic, or thermal energy 637integrated over a domain of volume :math:`L_0^3` at the reference pressure. Used 638when computing globally integrated energy budgets. 639""" 640 641K = W 642"""Kinetic energy normalization for MAS. 643 644Alias for :data:`W`. The kinetic and pressure-volume energy scales are identical 645since :math:`K_0 = \\tfrac{1}{2}\\rho_0 V_0^2 L_0^3 = \\tfrac{1}{2} P_0 L_0^3`; 646the factor of one-half is absorbed into the code-unit kinetic energy. 647""" 648 649# --------------------------------------------------------------------------- 650# Aliases and corotating frame 651# --------------------------------------------------------------------------- 652 653FN_LENGTH = FNORML 654"""Alias for :data:`FNORML`: the characteristic length scale :math:`L_0 = R_\\odot`.""" 655 656FN_TIME = FNORMT 657"""Alias for :data:`FNORMT`: the characteristic time scale :math:`T_0 \\approx 1446` s.""" 658 659OMEGA_COROTATE = 0.004144 * u.rad / FN_TIME 660"""Default angular velocity of the MAS corotating frame, in physical unit. 661 662MAS can be run in a frame rotating with the Sun. The dimensionless code rotation 663rate 0.004144 rad per time unit converts to 664 665.. math:: 666 667 \\Omega_\\text{corotate} = \\frac{0.004144}{T_0} \\approx 2.87 \\times 10^{-6}\\;\\text{rad s}^{-1}, 668 669which matches the solar sidereal rotation rate (period ≈ 25.4 days). 670""" 671 672 673# ============================================================================= 674# Helium abundance corrections 675# ============================================================================= 676
[docs] 677def get_helium_fractions(he_frac: float) -> dict[str, float]: 678 """Compute fractional abundance multipliers for a hydrogen–helium plasma. 679 680 MAS models the solar corona as a gas of electrons (e), protons (p), and alpha 681 particles (a). Given the fractional helium abundance :math:`f = n_a / n_e`, this 682 function returns dimensionless multipliers that relate the MAS code-unit density 683 and pressure to the individual species number densities. 684 685 The multipliers are derived from two constraints: 686 687 1. **Charge neutrality**: :math:`n_e = n_p + 2 n_a`, giving 688 :math:`n_p/n_e = 1/(1+2f)` and :math:`n_a/n_e = f/(1+2f)`. 689 2. **Alpha particle properties**: mass :math:`4 m_p`, charge :math:`2e`. 690 691 Parameters 692 ---------- 693 he_frac : float 694 Helium fraction :math:`f = n_a / n_e`, the ratio of alpha-particle number 695 density to electron number density. Typical coronal value: ``0.05``. 696 697 Returns 698 ------- 699 fractions : dict[str, float] 700 Dictionary with the following keys: 701 702 ``'he_rho'`` 703 Mass-density multiplier. The code mass density :math:`\\rho_\\text{code}` 704 is related to the electron number density by 705 :math:`\\rho_\\text{code} = n_e \\cdot` ``he_rho``, where 706 707 .. math:: 708 709 \\texttt{he\\_rho} = \\frac{n_p + 4 n_a}{n_e} = \\frac{1 + 4f}{1 + 2f}. 710 711 ``'he_p'`` 712 Total pressure multiplier for a single-temperature plasma. For an ideal 713 gas the total particle pressure is :math:`p = (n_e + n_p + n_a)\\,k_B T`, 714 so 715 716 .. math:: 717 718 \\texttt{he\\_p} = \\frac{n_e + n_p + n_a}{n_e} = \\frac{2 + 3f}{1 + 2f}. 719 720 ``'he_np'`` 721 Proton fraction :math:`n_p / n_e = 1/(1 + 2f)`. Needed for radiative 722 loss rates that scale as :math:`n_e n_p`. 723 724 ``'he_p_e'`` 725 Electron pressure multiplier (equal to 1.0). In the two-temperature 726 model the electron partial pressure is :math:`p_e = n_e k_B T_e`, so 727 the multiplier relative to the electron density is always unity. 728 729 ``'he_p_p'`` 730 Ion pressure multiplier. The combined proton + alpha pressure is 731 :math:`p_i = (n_p + n_a)\\,k_B T_i`, giving 732 733 .. math:: 734 735 \\texttt{he\\_p\\_p} = \\frac{n_p + n_a}{n_e} = \\frac{1 + f}{1 + 2f}. 736 737 Notes 738 ----- 739 These multipliers are *not* stored as module-level constants because they depend on 740 ``he_frac``, which varies between simulations. The caller is responsible for 741 passing the correct helium fraction from the model metadata. 742 743 Examples 744 -------- 745 >>> from psi_io._units import get_helium_fractions 746 >>> fracs = get_helium_fractions(0.0) # pure hydrogen plasma 747 >>> fracs['he_rho'] 748 1.0 749 >>> fracs['he_p'] 750 2.0 751 >>> fracs = get_helium_fractions(0.05) # 5 % helium by electron fraction 752 >>> round(fracs['he_rho'], 4) 753 1.1818 754 """ 755 # Mass Density Multiplier: A = (np + 4*na)/ne 756 # --> rho_mas = ne_mas*he_rho 757 he_rho = (1 + 4 * he_frac) / (1 + 2 * he_frac) 758 759 # Total number of particles: n = ne + np + na 760 # --> for 1T: p_mas = ne_mas*T_mas*he_p = rho_mas*T_mas*he_p/he_rho 761 he_p = (2 + 3 * he_frac) / (1 + 2 * he_frac) 762 763 # Number of protons (used by radloss, which needs ne*np) 764 he_np = 1 / (1 + 2 * he_frac) 765 766 # Electron pressure multiplier (used in 2T: Pe = ne*kB*Te → multiplier = 1) 767 he_p_e = 1.0 768 769 # Ion pressure multiplier (protons + alphas, used in 2T: Pi = (np+na)*kB*Ti) 770 he_p_p = (1 + he_frac) / (1 + 2 * he_frac) 771 772 return { 773 'he_rho': he_rho, 774 'he_p': he_p, 775 'he_np': he_np, 776 'he_p_e': he_p_e, 777 'he_p_p': he_p_p, 778 }
779 780 781# ============================================================================= 782# Custom astropy unit definitions for MAS and POT3D code quantities 783# ============================================================================= 784 785MAS_b = u.def_unit( 786 [f"MAS_{q}" for q in ("b", "br", "bt", "bp")], 787 FN_B, 788 doc="PSI's MAS magnetic field normalization unit.", 789 format={"latex": r"B_\mathrm{MAS}"}, 790) 791"""Custom astropy unit for MAS magnetic field quantities. 792 793One ``MAS_b`` equals :data:`FN_B` ≈ 2.2 Gauss. Registered for the quantity names 794``MAS_b``, ``MAS_br``, ``MAS_bt``, and ``MAS_bp`` so that astropy can automatically 795convert code-unit field values to Gauss or Tesla. 796""" 797 798MAS_v = u.def_unit( 799 [f"MAS_{q}" for q in ("v", "vr", "vt", "vp", "zp", "zm")], 800 FN_V, 801 doc="PSI's MAS velocity normalization unit.", 802 format={"latex": r"v_\mathrm{MAS}"}, 803) 804"""Custom astropy unit for MAS velocity quantities. 805 806One ``MAS_v`` equals :data:`FN_V` ≈ 481 km s⁻¹. Registered for velocity components 807(``vr``, ``vt``, ``vp``) and Elsässer wave variables (``zp``, ``zm``). 808""" 809 810MAS_j = u.def_unit( 811 [f"MAS_{q}" for q in ("j", "jr", "jt", "jp")], 812 FN_J, 813 doc="PSI's MAS current density normalization unit.", 814 format={"latex": r"J_\mathrm{MAS}"}, 815) 816"""Custom astropy unit for MAS current density quantities. 817 818One ``MAS_j`` equals :data:`FN_J` in A m⁻² (SI). Registered for the vector 819components ``jr``, ``jt``, ``jp``. 820""" 821 822MAS_t = u.def_unit( 823 [f"MAS_{q}" for q in ("t", "te", "tp")], 824 FN_T, 825 doc="PSI's MAS temperature normalization unit.", 826 format={"latex": r"T_\mathrm{MAS}"}, 827) 828"""Custom astropy unit for MAS temperature quantities. 829 830One ``MAS_t`` equals :data:`FN_T` ≈ 28 MK. Registered for the single-temperature 831(``t``), electron-temperature (``te``), and proton-temperature (``tp``) variables. 832""" 833 834MAS_n = u.def_unit( 835 [f"MAS_{q}" for q in ("n", "rho")], 836 FN_N, 837 doc="PSI's MAS (number) density normalization unit.", 838 format={"latex": r"n_\mathrm{MAS}"}, 839) 840"""Custom astropy unit for MAS number density and mass density. 841 842One ``MAS_n`` equals :data:`FN_N` = 10⁸ cm⁻³. Registered for both the number 843density (``n``) and the code mass-density (``rho``) variables; callers should divide 844``rho`` by :data:`FMP` to obtain a physical number density in cm⁻³. 845""" 846 847MAS_p = u.def_unit( 848 [f"MAS_{q}" for q in ("p", "ep", "em")], 849 FN_P, 850 doc="PSI's MAS pressure normalization unit.", 851 format={"latex": r"p_\mathrm{MAS}"}, 852) 853"""Custom astropy unit for MAS pressure and wave-energy quantities. 854 855One ``MAS_p`` equals :data:`FN_P` in erg cm⁻³. Registered for the thermal pressure 856(``p``), Elsässer energy densities (``ep``, ``em``), and Alfvén wave pressure 857variables. 858""" 859 860MAS_heat = u.def_unit( 861 [f"MAS_{q}" for q in ("heat",)], 862 FN_HEAT, 863 doc="PSI's MAS volumetric heating rate normalization unit.", 864 format={"latex": r"heat_\mathrm{MAS}"}, 865) 866"""Custom astropy unit for MAS volumetric heating rate. 867 868One ``MAS_heat`` equals :data:`FN_HEAT` in erg cm⁻³ s⁻¹. 869""" 870 871POT3D_b = u.def_unit( 872 [f"POT3D_{q}" for q in ("b", "br", "bt", "bp")], 873 1 * u.dimensionless_unscaled, 874 doc="PSI's POT3D magnetic field normalization unit.", 875 format={"latex": r"B_\mathrm{POT3D}"}, 876) 877"""Custom astropy unit for POT3D magnetic field quantities. 878 879One ``POT3D_b`` equals 1 (dimensionless). POT3D is a potential-field solver driven 880by photospheric magnetogram boundary conditions; its output magnetic field is already 881expressed in the same physical unit as the input map (typically Gauss), so no 882additional conversion factor is applied. 883""" 884 885PSI_rsun = u.def_unit( 886 [f"PSI_{q}" for q in ("rsun", "radius", "r")], 887 RSUN, 888 doc="PSI's solar radius normalization unit.", 889 format={"latex": r"R_\odot"}, 890) 891"""Custom astropy unit for PSI radial coordinate grids. 892 893One ``PSI_rsun`` equals :data:`RSUN` = 6.96 × 10¹⁰ cm. Radial coordinate arrays 894in MAS and POT3D HDF files are stored in unit of solar radii; this unit allows 895astropy to convert them to cm, km, or AU automatically. 896""" 897 898PSI_angle = u.def_unit( 899 [f"PSI_{q}" for q in ("angle", "t", "p", "theta", "phi")], 900 1 * u.rad, 901 doc="PSI's long-lat angle unit.", 902 format={"latex": r"R_\odot"}, 903) 904"""Custom astropy unit for PSI angular coordinate grids. 905 906One ``PSI_angle`` equals 1 radian. Colatitude (θ) and longitude (φ) coordinate 907arrays in MAS and POT3D HDF files are stored in radians; this unit allows astropy to 908convert them to degrees where needed. 909""" 910 911 912u.add_enabled_units([MAS_b, MAS_v, MAS_j, MAS_t, MAS_n, MAS_p, MAS_heat, POT3D_b, PSI_rsun, PSI_angle])