Source code for struphy.models.perturbative_mhd_with_q

from struphy.io.options import BaseUnits
from struphy.models.linear_mhd_with_q import LinearMHDWithQ
from struphy.models.utils import (
    PerturbativeEnergies,
    add_perturbative_scalars,
)
from struphy.models.variables import FEECVariable
from struphy.propagators.background_current import BackgroundCurrent
from struphy.propagators.momentum_advection import MomentumAdvection
from struphy.propagators.shear_alfven_nonlinear import ShearAlfvenNonlinear
from struphy.propagators.shear_alfven_propagator import ShearAlfvenPropagator
from struphy.propagators.sound_wave_linear_with_q import SoundWaveLinearWithQ
from struphy.propagators.sound_wave_nonlinear_with_q import SoundWaveNonlinearWithQ


[docs] class PerturbativeMHDWithQ(LinearMHDWithQ): """Ideal MHD equations for the perturbations of a static (zero-flow) equilibrium, with all nonlinear terms, and the square root of the pressure, :math:`q = \\sqrt p`, as thermodynamic variable. Extends :class:`~struphy.models.LinearMHDWithQ` by the terms that are nonlinear in the perturbations: the inertia of the density perturbation, the advection of mass and momentum, the nonlinear sound wave terms and the nonlinear terms of the Lorentz force and of the induction equation. The magnetic field is in :math:`H(\\textnormal{div})`. Parameters ---------- base_units: BaseUnits Base units for normalization (default: BaseUnits()) mass_number: float Mass number (in units of Proton mass) of the ion species (default: 1.0) """ ## additional propagators class Propagators(LinearMHDWithQ.Propagators): """Propagators of :class:`~struphy.models.LinearMHDWithQ`, with ``shear_alf``, ``bg_current`` and ``sound`` created again with the inertia of the total density, plus ``nonlin_alf`` (nonlinear shear-Alfvén terms), ``nonlin_sound`` (nonlinear sound wave terms) and ``mom_adv`` (advection of mass and momentum). Parameters ---------- rho_field : FEECVariable, optional Density perturbation, for the inertia of the total density (default: None). b_field : FEECVariable, optional Magnetic field perturbation, used by ``bg_current`` (default: None). **kwargs Passed to :class:`LinearMHDWithQ.Propagators <struphy.models.LinearMHDWithQ.Propagators>`. """ splitting_order = ("shear_alf", "nonlin_alf", "bg_current", "bg_q", "sound", "nonlin_sound", "mom_adv") def __init__(self, rho_field: FEECVariable = None, b_field: FEECVariable = None, **kwargs): super().__init__(b_field=b_field, **kwargs) # the linear steps with the inertia of the total density (replace those of the parent; b in H(div)) self.shear_alf = ShearAlfvenPropagator(rho_field=rho_field) self.bg_current = BackgroundCurrent(b_field=b_field, rho_field=rho_field) self.sound = SoundWaveLinearWithQ(rho_field=rho_field) self.nonlin_alf = ShearAlfvenNonlinear(rho_field=rho_field) self.nonlin_sound = SoundWaveNonlinearWithQ(rho_field=rho_field) self.mom_adv = MomentumAdvection() # attribute types for IDEs and type checkers: the classes of this model, not those of the parent propagators: Propagators ## abstract methods def __init__( self, base_units: BaseUnits = BaseUnits(), mass_number: float = 1.0, ): # 0. store input parameters self._store_params(locals()) # 1.-5. see LinearMHDWithQ (magnetic field in H(div), as needed by the nonlinear steps) super().__init__(base_units, mass_number, b_space="Hdiv", projected=False) def _propagator_args(self): """:meta private:""" return {**super()._propagator_args(), "rho_field": self.mhd.density} def _link_propagator_variables(self): """:meta private:""" super()._link_propagator_variables() self.propagators.nonlin_sound.variables.u = self.mhd.velocity self.propagators.nonlin_sound.variables.q = self.mhd.sqrt_p self.propagators.mom_adv.variables.n = self.mhd.density self.propagators.mom_adv.variables.u = self.mhd.velocity self.propagators.nonlin_alf.variables.u = self.mhd.velocity self.propagators.nonlin_alf.variables.b = self.em_fields.b_field def _create_scalars(self): """:meta private:""" scalars = super()._create_scalars() self._perturbative_energies = PerturbativeEnergies(self.mhd, self._magnetic_energies) add_perturbative_scalars(scalars, self.mhd, self._perturbative_energies) return scalars def post_allocate(self): """:meta private:""" super().post_allocate() self._perturbative_energies.allocate() ## abstract methods for documentation
[docs] @classmethod def doc_pde(cls): r"""**PDEs solved by model:** Continuity (mass conservation): .. math:: \frac{\partial \tilde{\rho}}{\partial t} + \nabla \cdot \left( (\rho_0 + \tilde \rho) \tilde{\mathbf{U}} \right) = 0 Momentum (Lorentz force): .. math:: (\rho_0 + \tilde \rho) \left( \frac{\partial \tilde{\mathbf{U}}}{\partial t} + \tilde{\mathbf U} \cdot \nabla \tilde{\mathbf U} \right) + \nabla (2 q_0 \tilde q + \tilde q^2) = (\nabla \times \tilde{\mathbf{B}}) \times (\mathbf{B}_0 + \tilde{\mathbf B}) + (\nabla \times \mathbf{B}_0) \times \tilde{\mathbf{B}} Square root of the pressure (adiabatic process): .. math:: \frac{\partial \tilde{q}}{\partial t} + \tilde{\mathbf{U}} \cdot \nabla (q_0 + \tilde q) + \frac{\gamma}{2} (q_0 + \tilde q) \nabla \cdot \tilde{\mathbf{U}} = 0 Induction (Faraday's law): .. math:: \frac{\partial \tilde{\mathbf{B}}}{\partial t} - \nabla \times \left(\tilde{\mathbf{U}} \times (\mathbf{B}_0 + \tilde{\mathbf B})\right) = 0. Here, :math:`\tilde \rho`, :math:`\tilde{\mathbf U}`, :math:`\tilde q`, :math:`\tilde{\mathbf B}` are the perturbed density, velocity, square root of the pressure and magnetic field (not necessarily small), and :math:`\rho_0`, :math:`q_0 = \sqrt{p_0}`, :math:`\mathbf B_0` those of a static MHD equilibrium, :math:`\nabla q_0^2 = (\nabla \times \mathbf B_0) \times \mathbf B_0`; :math:`\gamma = 5/3`. These are the ideal MHD equations for :math:`\rho = \rho_0 + \tilde \rho`, :math:`\mathbf U = \tilde{\mathbf U}`, :math:`q = q_0 + \tilde q`, :math:`\mathbf B = \mathbf B_0 + \tilde{\mathbf B}`, with the equilibrium force balance subtracted. Dropping the terms quadratic in the perturbations gives :class:`~struphy.models.LinearMHDWithQ`. **Conserved quantities** (for :math:`\tilde{\mathbf U} \cdot \mathbf n = 0` and :math:`\mathbf B \cdot \mathbf n = 0` on the boundary, or periodic boundaries): Mass perturbation and divergence of the magnetic field: .. math:: \frac{\textnormal d}{\textnormal d t} \int \tilde \rho \, \textnormal d \mathbf x = 0\,, \qquad \frac{\partial}{\partial t} \nabla \cdot \tilde{\mathbf B} = 0 Energy of the perturbation, for an equilibrium with :math:`\nabla q_0 = 0` and :math:`\nabla \times \mathbf B_0 = 0`: .. math:: \frac{\textnormal d}{\textnormal d t} \int \left( \frac{\rho_0 + \tilde \rho}{2} |\tilde{\mathbf U}|^2 + \frac{2 q_0 \tilde q + \tilde q^2}{\gamma - 1} + \mathbf B_0 \cdot \tilde{\mathbf B} + \frac 12 |\tilde{\mathbf B}|^2 \right) \textnormal d \mathbf x = 0 """
[docs] @classmethod def doc_discretization(cls): """Time integration is performed by the following propagators (in sequence; the magnetic field is in :math:`H(\textnormal{div})`):""" return cls.propagators_doc()
[docs] @classmethod def doc_normalization(cls): r"""All velocities are normalized by the Alfvén velocity: .. math:: \hat{U} = \hat{v}_\mathrm{A} = \frac{\hat{B}_0}{\sqrt{\mu_0 \rho_0}}\,, \qquad \hat{q} = \sqrt{\hat p} = \frac{\hat B_0}{\sqrt{\mu_0}} The model therefore uses the Alfvén velocity as its characteristic speed scale."""
[docs] @classmethod def doc_scalar_quantities(cls): r"""**The following scalars are tracked during simulation:** - Kinetic energy: :math:`E_U = \frac{1}{2} \int (\rho_0 + \tilde \rho) |\tilde{\mathbf{U}}|^2 \, \mathrm{d}V` (``en_U``), with the total density (projected :math:`\rho_0`) - Quadratic thermal energy of the linear sound wave step: :math:`\frac{2}{\gamma}\int \tilde{q}^2 \, \mathrm{d}V` (``en_thermal``); of the nonlinear sound wave step: :math:`\frac{1}{\gamma - 1}\int \tilde{q}^2 \, \mathrm{d}V` (``en_q2``) - Internal energy of the perturbation: :math:`E_\mathrm{int} = \int \frac{2 q_0 \tilde q + \tilde q^2}{\gamma - 1} \, \mathrm{d}V` (``en_internal``) - Magnetic energy (perturbation): :math:`E_B = \frac{1}{2} \int |\tilde{\mathbf{B}}|^2 \, \mathrm{d}V` (``en_B``), equilibrium magnetic energy (``en_B_eq``), total magnetic energy :math:`\frac 12 \int |\mathbf B_0 + \tilde{\mathbf B}|^2 \, \mathrm{d}V` (``en_B_tot``) - Equilibrium internal energy: :math:`E_{p0} = \int \frac{p_0}{\gamma - 1} \, \mathrm{d}V` (``en_p_eq``) - Energy of the perturbation: :math:`E_{\mathrm{tot}} = E_U + E_\mathrm{int} + \frac 12 \int (|\mathbf B_0 + \tilde{\mathbf B}|^2 - |\mathbf B_0|^2) \, \mathrm{d}V` (``en_tot``), conserved by the equations for :math:`\nabla q_0 = 0`, :math:`\nabla \times \mathbf B_0 = 0`. Each step of the splitting conserves its own energy exactly, so :math:`E_{\mathrm{tot}}` changes by the splitting error only."""
[docs] @classmethod def doc_long_description(cls): r"""This model simulates finite-amplitude perturbations in a magnetized plasma around a static MHD equilibrium. It solves the ideal MHD equations for the perturbations of the density, the velocity, the square root of the pressure, :math:`q = \sqrt p`, and the magnetic field, written as the linear system of :class:`~struphy.models.LinearMHDWithQ` plus the nonlinear terms. The time step reuses the steps of the linear model (with the inertia of the total density :math:`\rho_0 + \tilde \rho^n` and without the continuity equation) and adds the steps of the nonlinear terms, each energy conserving for its own energy: - ``nonlin_alf``: the shear-Alfvén step with the weight :math:`\mathbf B_0 \to \tilde{\mathbf B}`, i.e. the force :math:`(\nabla \times \tilde{\mathbf B}) \times \tilde{\mathbf B}` and the induction term :math:`\nabla \times (\tilde{\mathbf U} \times \tilde{\mathbf B})`; - ``nonlin_sound``: the terms of the sound waves quadratic in :math:`\tilde q`; - ``mom_adv``: the continuity equation and the advection of the momentum (discrete gradient, Picard iteration). The weights of the nonlinear terms are at :math:`t^n` by default (``Options(weights="frozen")``) or at :math:`t^{n+1/2}` (``weights="midpoint"``, Picard iteration, second order in time). The magnetic field is in :math:`H(\textnormal{div})` (the shear-Alfvén step of :class:`~struphy.models.LinearMHD` with ``b_space="Hdiv"``). No numerical dissipation is added, so the model is meant for smooth solutions."""
[docs] @classmethod def doc_examples(cls): r"""Create and initialize a perturbative MHD model in :math:`q`: .. code-block:: python from struphy.models import PerturbativeMHDWithQ model = PerturbativeMHDWithQ() # Access fields model.em_fields.b_field model.mhd.density model.mhd.velocity model.mhd.sqrt_p # Access tracked scalar quantities model.scalars["en_U"] model.scalars["en_B"] model.scalars["en_tot"] """
[docs] @classmethod def doc_use_cases(cls): r"""This model is appropriate for finite-amplitude perturbations of a static magnetized plasma. Typical use cases include: - nonlinear Alfvén and magnetosonic waves - nonlinear saturation of instabilities of a static equilibrium (in the absence of shocks) - comparison with :class:`~struphy.models.LinearMHDWithQ` in the small-amplitude limit"""
[docs] @classmethod def doc_cannot_be_used_for(cls): r"""This model is not suitable for: - shocks and other discontinuities (no numerical dissipation) - equilibria with non-zero background flow - dissipative effects such as resistivity or viscosity (reconnection) - the magnetic field in :math:`H(\textnormal{curl})` - kinetic or particle-field effects beyond the fluid MHD description"""