from struphy.io.options import BaseUnits
from struphy.models.linear_euler_with_q import LinearEulerWithQ
from struphy.models.utils import (
PerturbativeEnergies,
add_perturbative_scalars,
)
from struphy.models.variables import FEECVariable
from struphy.propagators.momentum_advection import MomentumAdvection
from struphy.propagators.sound_wave_linear_with_q import SoundWaveLinearWithQ
from struphy.propagators.sound_wave_nonlinear_with_q import SoundWaveNonlinearWithQ
[docs]
class PerturbativeEulerWithQ(LinearEulerWithQ):
"""Compressible Euler 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.LinearEulerWithQ` by the terms that are nonlinear in the perturbations: the
inertia of the density perturbation, the advection of mass and momentum and the nonlinear sound wave terms.
Parameters
----------
base_units: BaseUnits
Base units for normalization (default: ``BaseUnits(kBT=1.0)``).
mass_number: float
Mass number (in units of Proton mass) of the fluid species (default: 1.0)
"""
## additional propagators
class Propagators(LinearEulerWithQ.Propagators):
"""Propagators of :class:`~struphy.models.LinearEulerWithQ`, with ``sound`` created again with the inertia of
the total density, plus ``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).
**kwargs
Passed to :class:`LinearEulerWithQ.Propagators <struphy.models.LinearEulerWithQ.Propagators>`.
"""
def __init__(self, rho_field: FEECVariable = None, **kwargs):
super().__init__(**kwargs)
# the linear sound wave step with the inertia of the total density (replaces the one of the parent)
self.sound = SoundWaveLinearWithQ(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(kBT=1.0),
mass_number: float = 1.0,
):
# 0. store input parameters
self._store_params(locals())
# 1.-5. see LinearEulerWithQ
super().__init__(base_units, mass_number)
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
def _create_scalars(self):
""":meta private:"""
scalars = super()._create_scalars()
self._perturbative_energies = PerturbativeEnergies(self.mhd)
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:
.. 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) = 0
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\,.
Here, :math:`\tilde \rho`, :math:`\tilde{\mathbf U}`, :math:`\tilde q` are the perturbed density, velocity and
square root of the pressure (not necessarily small), :math:`\rho_0`, :math:`q_0 = \sqrt{p_0}` those of a static
equilibrium (zero flow) with :math:`\nabla p_0 = 0`, and :math:`\gamma = 5/3`. These are the compressible Euler
equations :math:`\partial_t \rho + \nabla \cdot (\rho \mathbf U) = 0`,
:math:`\rho (\partial_t \mathbf U + \mathbf U \cdot \nabla \mathbf U) + \nabla q^2 = 0`,
:math:`\partial_t q + \mathbf U \cdot \nabla q + \frac{\gamma}{2} q \nabla \cdot \mathbf U = 0` for
:math:`\rho = \rho_0 + \tilde \rho`, :math:`\mathbf U = \tilde{\mathbf U}`, :math:`q = q_0 + \tilde q`, with the
equilibrium subtracted. Dropping the terms quadratic in the perturbations gives
:class:`~struphy.models.LinearEulerWithQ`.
**Conserved quantities** (for :math:`\tilde{\mathbf U} \cdot \mathbf n = 0` on the boundary, or periodic
boundaries):
Mass perturbation:
.. math::
\frac{\textnormal d}{\textnormal d t} \int \tilde \rho \, \textnormal d \mathbf x = 0
Energy of the perturbation (kinetic plus internal energy, the internal energy of the equilibrium subtracted),
for an equilibrium with :math:`\nabla q_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} \right) \textnormal d \mathbf x = 0
"""
[docs]
@classmethod
def doc_normalization(cls):
r"""All velocities are normalized by the thermal velocity of the bulk species, defined by the unit of the
temperature :math:`\hat{k_B T}` (``BaseUnits.kBT``):
.. math::
\hat{U} = \hat{v}_\mathrm{th} = \sqrt{\frac{\hat{k_B T}}{A m_\mathrm{H}}}\,, \qquad
\hat{\rho} = A m_\mathrm{H} \hat{n}\,, \qquad \hat{q} = \sqrt{\hat{p}} = \sqrt{\hat{\rho}}\, \hat{v}_\mathrm{th}\,,
\qquad \hat{t} = \frac{\hat{x}}{\hat{v}_\mathrm{th}}\,.
The equations then have no dimensionless parameters."""
[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``)
- Quadratic thermal energy 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 = \int \frac{q^2 - q_0^2}{\gamma - 1} \, \mathrm{d}V` (``en_internal``)
- 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}` (``en_tot``), conserved by the
equations for :math:`\nabla q_0 = 0`. Each step of the splitting conserves its own energy exactly (the linear
sound wave step :math:`E_U` + ``en_thermal``, the nonlinear one :math:`E_U` + ``en_q2``, the advection step
:math:`E_U`), 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 of a compressible, inviscid fluid around a static
equilibrium (e.g. nonlinear sound waves up to the formation of shocks). It solves the full compressible Euler
equations for the perturbations of the density, the velocity and the square root of the pressure,
:math:`q = \sqrt p`, written as the linear system of :class:`~struphy.models.LinearEulerWithQ` plus the
nonlinear terms.
The time step reuses the steps of the linear model and adds the steps of the nonlinear terms:
- the steps of :class:`~struphy.models.LinearEulerWithQ` (background :math:`q` step and linear sound wave step,
one symmetric block), with the inertia of the total density :math:`\rho_0 + \tilde \rho^n` instead of
:math:`\rho_0` and without the continuity equation;
- ``nonlin_sound``: the terms of the sound waves quadratic in :math:`\tilde q`, with the transport operator
:math:`\mathcal S_{\tilde q}` and its transpose, so that it conserves
:math:`\frac 12 \int \rho |\tilde{\mathbf U}|^2 + \frac{1}{\gamma - 1} \int \tilde q^2` exactly;
- ``mom_adv``: the continuity equation and the advection of the momentum, which conserves the mass and
:math:`\frac 12 \int \rho |\tilde{\mathbf U}|^2` exactly (discrete gradient, Picard iteration).
All steps are energy conserving, each for its own energy; the nonlinear steps have the weights of the nonlinear
terms 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 inertia of every step is that of the total
density at :math:`t^n`, which is not changed except in ``mom_adv``. No numerical dissipation is added, so
shocks are not captured; the model is meant for smooth solutions."""
[docs]
@classmethod
def doc_examples(cls):
r"""Create and initialize a perturbative Euler model in :math:`q`:
.. code-block:: python
from struphy import BaseUnits
from struphy.models import PerturbativeEulerWithQ
model = PerturbativeEulerWithQ(base_units=BaseUnits(kBT=1.0))
# Access fields
model.mhd.density
model.mhd.velocity
model.mhd.sqrt_p
# second order in time: midpoint weights of the nonlinear steps
props = model.propagators
props.nonlin_sound.options = props.nonlin_sound.Options(weights="midpoint")
props.mom_adv.options = props.mom_adv.Options(weights="midpoint")
# Access tracked scalar quantities
model.scalars["en_U"]
model.scalars["en_internal"]
model.scalars["en_tot"]
"""
[docs]
@classmethod
def doc_use_cases(cls):
r"""This model is appropriate for finite-amplitude perturbations of a static compressible fluid.
Typical use cases include:
- nonlinear sound waves (steepening, before the formation of shocks)
- pressureless and compressible advection of density and momentum
- the nonlinear fluid part of :class:`~struphy.models.PerturbativeMHDWithQ`"""
[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, or with a pressure gradient (no force balance without a magnetic
field)
- dissipative effects such as viscosity or heat conduction
- magnetized plasmas (see :class:`~struphy.models.PerturbativeMHDWithQ`)"""