Source code for struphy.models.visco_resistive_deltaf_mhd

import copy

import cunumpy as xp
from feectools.ddm.mpi import mpi as MPI

from struphy.feec.mass import L2Projector
from struphy.io.options import BaseUnits, LiteralOptions
from struphy.models.base import StruphyModel
from struphy.models.scalars import BilinearEnergyFEEC, FunctionScalarFEEC, Scalars
from struphy.models.species import (
    DiagnosticSpecies,
    FieldSpecies,
    FluidSpecies,
)
from struphy.models.variables import FEECVariable
from struphy.polar.basic import PolarVector
from struphy.propagators.base import Propagator
from struphy.propagators.variational_density_evolve import VariationalDensityEvolve
from struphy.propagators.variational_momentum_advection import VariationalMomentumAdvection
from struphy.propagators.variational_pb_evolve import VariationalPBEvolve
from struphy.propagators.variational_resistivity import VariationalResistivity
from struphy.propagators.variational_viscosity import VariationalViscosity

rank = MPI.COMM_WORLD.Get_rank()


[docs] class ViscoResistiveDeltafMHD(StruphyModel): """Delta-f visco-resistive MHD equations discretized with a variational method. Parameters ---------- base_units: BaseUnits Base units for normalization (default: BaseUnits()) mass_number: float Mass number (in units of Proton mass) of the fluid species (default: 1.0) with_viscosity: bool Whether to include viscous dissipation (default: True) with_resistivity: bool Whether to include resistive dissipation (default: True) """ @classmethod def model_type(cls) -> LiteralOptions.ModelTypes: return "Fluid" ## species class EMFields(FieldSpecies): def __init__(self): self.b_field = FEECVariable(space="Hdiv") self.init_variables() class MHD(FluidSpecies): def __init__(self, mass_number: float = 1.0): self.density = FEECVariable(space="L2") self.velocity = FEECVariable(space="H1vec") self.pressure = FEECVariable(space="L2") self.init_variables(mass_number=mass_number) class Diagnostics(DiagnosticSpecies): def __init__(self): self.div_u = FEECVariable(space="L2") self.u2 = FEECVariable(space="Hdiv") self.pt3 = FEECVariable(space="L2") self.bt2 = FEECVariable(space="Hdiv") self.init_variables() ## propagators class Propagators: def __init__( self, pt3: FEECVariable = None, bt2: FEECVariable = None, rho: FEECVariable = None, with_viscosity: bool = True, with_resistivity: bool = True, ): self.variat_dens = VariationalDensityEvolve() self.variat_mom = VariationalMomentumAdvection() self.variat_pb = VariationalPBEvolve(pt3=pt3, bt2=bt2) if with_viscosity: self.variat_viscous = VariationalViscosity(rho=rho) if with_resistivity: self.variat_resist = VariationalResistivity(rho=rho) ## abstract methods def __init__( self, base_units: BaseUnits = BaseUnits(), mass_number: float = 1.0, with_viscosity: bool = True, with_resistivity: bool = True, ): # 0. store input parameters self.params = copy.deepcopy(locals()) # 1. instantiate all species self.em_fields = self.EMFields() self.mhd = self.MHD(mass_number=mass_number) self.diagnostics = self.Diagnostics() # 2. derive units (must be done after instantiating species to access charge and mass numbers) self.setup_equation_params(base_units=base_units) # 3. instantiate all propagators self.propagators = self.Propagators( pt3=self.diagnostics.pt3, bt2=self.diagnostics.bt2, rho=self.mhd.density, with_viscosity=with_viscosity, with_resistivity=with_resistivity, ) # 4. assign variables to propagators self.propagators.variat_dens.variables.rho = self.mhd.density self.propagators.variat_dens.variables.u = self.mhd.velocity self.propagators.variat_mom.variables.u = self.mhd.velocity self.propagators.variat_pb.variables.u = self.mhd.velocity self.propagators.variat_pb.variables.p = self.mhd.pressure self.propagators.variat_pb.variables.b = self.em_fields.b_field if with_viscosity: self.propagators.variat_viscous.variables.s = self.mhd.pressure self.propagators.variat_viscous.variables.u = self.mhd.velocity if with_resistivity: self.propagators.variat_resist.variables.s = self.mhd.pressure self.propagators.variat_resist.variables.b = self.em_fields.b_field # 5. define scalars to be tracked during simulation kinetic_energy = BilinearEnergyFEEC(self.mhd.velocity, bilinear_form_name="WMMnew") magnetic_energy_1 = BilinearEnergyFEEC(self.em_fields.b_field) magnetic_energy_2 = BilinearEnergyFEEC(self.diagnostics.bt2, right_variable="b2") thermo_energy = FunctionScalarFEEC(self._compute_en_thermo) thermo_energy_l1 = FunctionScalarFEEC(self._compute_en_thermo_l1) magnetic_energy_l1 = BilinearEnergyFEEC(self.em_fields.b_field, right_variable="b2") self.scalars = Scalars( en_U=kinetic_energy, en_thermo=thermo_energy, en_mag_1=magnetic_energy_1, en_mag_2=magnetic_energy_2, en_tot=kinetic_energy + thermo_energy + magnetic_energy_1 + magnetic_energy_2, en_tot_l1=thermo_energy_l1 + magnetic_energy_l1, en_thermo_l1=thermo_energy_l1, en_mag_l1=magnetic_energy_l1, ) @property def bulk_species(self): return self.mhd @property def velocity_scale(self): return "alfvén" def allocate_helpers(self): projV3 = L2Projector("L2", Propagator.mass_ops) def f(e1, e2, e3): return 1 f = xp.vectorize(f) self._integrator = projV3(f) self._ones = Propagator.derham.V3pol.zeros() if isinstance(self._ones, PolarVector): self._ones.tp[:] = 1.0 else: self._ones[:] = 1.0 self._tmp_div_B = Propagator.derham.V3pol.zeros() def _compute_en_thermo(self): pt3 = self.propagators.variat_pb.pt3.spline.vector gamma = self.propagators.variat_pb.options.gamma return Propagator.mass_ops.M3.dot_inner(pt3, self._integrator) / (gamma - 1.0) def _compute_en_thermo_l1(self): p = self.mhd.pressure.spline.vector gamma = self.propagators.variat_pb.options.gamma return Propagator.mass_ops.M3.dot_inner(p, self._integrator) / (gamma - 1.0) # default parameters def generate_default_parameter_file(self, path=None, prompt=True): params_path = super().generate_default_parameter_file(path=path, prompt=prompt) new_file = [] with open(params_path, "r") as f: for line in f: if "variat_dens.Options" in line: new_file += [ "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='deltaf')\n", ] elif "variat_pb.Options" in line: new_file += [ "model.propagators.variat_pb.options = model.propagators.variat_pb.Options(model='deltaf')\n", ] elif "variat_viscous.Options" in line: new_file += [ "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='full_p')\n", ] elif "variat_resist.Options" in line: new_file += [ "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='full_p')\n", ] elif "pressure.add_background" in line: # new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] new_file += [line] else: new_file += [line] with open(params_path, "w") as f: for line in new_file: f.write(line)
[docs] @classmethod def doc_pde(cls): r"""**PDEs solved by model:** Continuity: .. math:: \partial_t \tilde{\rho} + \nabla \cdot \left( (\tilde{\rho} + \rho_0) \tilde{\mathbf{u}} \right) = 0 Momentum: .. math:: \partial_t \left( (\tilde{\rho} + \rho_0) \tilde{\mathbf{u}} \right) + \nabla \cdot \left( (\tilde{\rho} + \rho_0) \tilde{\mathbf{u}} \otimes \tilde{\mathbf{u}} \right) + \frac{1}{\gamma - 1} \nabla \tilde{p} + \mathbf{B}_0 \times \nabla \times \tilde{\mathbf{B}} + \tilde{\mathbf{B}} \times \nabla \times \mathbf{B}_0 + \tilde{\mathbf{B}} \times \nabla \times \tilde{\mathbf{B}} - \nabla \cdot \left( (\mu + \mu_a(\mathbf{x})) \nabla \tilde{\mathbf{u}} \right) = 0 Pressure: .. math:: \partial_t \tilde{p} + \tilde{\mathbf{u}} \cdot \nabla (\tilde{p} + p_0) + \gamma (\tilde{p} + p_0) \nabla \cdot \tilde{\mathbf{u}} = \frac{1}{\gamma - 1} \left( (\mu + \mu_a(\mathbf{x})) |\nabla \tilde{\mathbf{u}}|^2 + (\eta + \eta_a(\mathbf{x})) |\nabla \times \tilde{\mathbf{B}}|^2 \right) Induction: .. math:: \partial_t \tilde{\mathbf{B}} + \nabla \times \left( (\tilde{\mathbf{B}} + \mathbf{B}_0) \times \tilde{\mathbf{u}} \right) + \nabla \times (\eta + \eta_a(\mathbf{x})) \nabla \times \tilde{\mathbf{B}} = 0 Here :math:`\mu_a(\mathbf{x})` and :math:`\eta_a(\mathbf{x})` are artificial viscosity and resistivity coefficients. """
[docs] @classmethod def doc_normalization(cls): r"""The velocity scale is Alfvénic: .. math:: \hat u = \hat v_A. """
[docs] @classmethod def doc_scalar_quantities(cls): r"""**The following scalars are tracked during simulation:** - Kinetic energy: ``en_U`` - Thermal energies: ``en_thermo``, ``en_thermo_l1`` - Magnetic energies: ``en_mag_1``, ``en_mag_2``, ``en_mag_l1`` - Total energies: ``en_tot``, ``en_tot_l1``"""
[docs] @classmethod def doc_discretization(cls): """Time integration is performed by the following propagators (in sequence): 1. :class:`~struphy.propagators.variational_density_evolve.VariationalDensityEvolve` 2. :class:`~struphy.propagators.variational_momentum_advection.VariationalMomentumAdvection` 3. :class:`~struphy.propagators.variational_pb_evolve.VariationalPBEvolve` 4. :class:`~struphy.propagators.variational_viscosity.VariationalViscosity` (if :attr:`with_viscosity` is True) 5. :class:`~struphy.propagators.variational_resistivity.VariationalResistivity` (if :attr:`with_resistivity` is True) """ doc = rf"""**1. VariationalDensityEvolve:** {VariationalDensityEvolve.__doc__} **2. VariationalMomentumAdvection:** {VariationalMomentumAdvection.__doc__} **3. VariationalPBEvolve:** {VariationalPBEvolve.__doc__} **4. VariationalViscosity:** {VariationalViscosity.__doc__} **5. VariationalResistivity:** {VariationalResistivity.__doc__} """ return doc
[docs] @classmethod def doc_long_description(cls): r"""ViscoResistiveDeltafMHD is the dissipative perturbative MHD model using pressure as thermodynamic variable. It is intended for departures from a background equilibrium that are not strictly linear but still naturally formulated as perturbations."""
[docs] @classmethod def doc_examples(cls): r"""Create and initialize the visco-resistive ``delta f`` MHD model: .. code-block:: python from struphy.models import ViscoResistiveDeltafMHD model = ViscoResistiveDeltafMHD() model.mhd.pressure model.em_fields.b_field """
[docs] @classmethod def doc_use_cases(cls): r"""This model is appropriate for: - perturbative nonlinear MHD around a prescribed equilibrium - dissipative MHD verification with background fields - comparing linear and ``delta f`` variational formulations"""
[docs] @classmethod def doc_cannot_be_used_for(cls): r"""This model is not suitable for: - fully full-f MHD evolution far from the chosen equilibrium - kinetic or particle-resolved plasma effects - entropy- or q-based thermodynamic formulations"""