Source code for jhuki.dissipation

#!/usr/bin/env python3

# Copyright (C) 2020-2022 Gabriele Bozzola
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, see <https://www.gnu.org/licenses/>.


"""The :py:mod:`~.dissipation` module takes care of setting parameters for the
dissipation thorn. The main class is :py:class:`~.Dissipation`, but it is more
convenient to use helper functions to setup this object. For example, to set
up dissipation for a simulation with Lean, the simplest way is:

.. code-block:: python

   dis = add_gauge(add_Lean(create_dissipation_from_grid(grid, 0.3)))

where 0.3 is the value of ``eps_dis`` at the finest level.

"""

import enum
from functools import lru_cache

from jhuki.base import BaseThorn


[docs]class Dissipation(BaseThorn): """Class that describes the dissipation. Best used in combination with helper functions. This class is immutable. :ivar epsdis_per_level: Dictionary that maps the value of the dissipation on each refinement level. :type epsdis_per_level: dict :ivar order: Order of the dissipation. :type order: int :ivar variables: List of variables to which the dissipation has to be applied. :type variables: list of str """ def __init__(self, epsdis_per_level, order, variables): """It is your responsibility to provide meaningful eps_dis_per_level. :param epsdis_per_level: Dictionary that maps the value of the dissipation on each refinement level. :type epsdis_per_level: dict :param order: Order of the dissipation. :type order: int :param variables: List of variables to which the dissipation has to be applied. :type variables: list of str """ self.epsdis_per_level = epsdis_per_level self.order = order self.variables = variables @property @lru_cache(1) def parfile_code(self): """Return the code you would put in your parfile.""" eps_dis_str = "\n".join( f"Dissipation::epsdis_for_level[{level}] = {epsdis}" for level, epsdis in self.epsdis_per_level.items() ) vars_str = "\n".join(self.variables) return f"""\ Dissipation::order = {self.order} Dissipation::vars = "{vars_str}" {eps_dis_str}"""
[docs]class DissPrescription(enum.Enum): """DissPrescription for how to set up the dissipation. Three different prescriptions are available: - ``const``: set the same ``eps_dis`` everywhere, - ``dtfact``: set ``eps_dis`` proportional to the local dtfac, - ``continuous``: set ``eps_dis`` proportional to the local grid spacing to the power of the order, - ``partially_continuous``: constant except where levels are synced, in which case, use the continuous integration. """ const = enum.auto() dtfact = enum.auto() continuous = enum.auto() partially_continuous = enum.auto()
[docs]def create_dissipation_from_grid( grid, eps_dis_finest, variables=None, prescription=DissPrescription.dtfact ): """Create a :py:class:`~.Dissipation` object from a given :py:class:`~.Grid`. The order of the dissipation is deduced from the number of ghost zones. :param grid: Simulation grid. :type grid: :py:class:`~.Grid` :param epsdis_per_level: Dictionary that maps the value of the dissipation on each refinement level. :type epsdis_per_level: dict :param order: Order of the dissipation. :type order: int :param variables: List of variables to which the dissipation has to be applied. :type variables: list of str :param prescription: How to set ``eps_dis``. :type prescription: :py:class:`~.DissPrescription` :returns: Dissipation object. :rtype: :py:class:`~.Dissipation` """ if not isinstance(prescription, DissPrescription): raise ValueError( f"Unknown prescription {prescription}. " "Accepted values: {list(DissPrescription.__members__.keys())}" ) order = 2 * grid.num_ghost - 1 variables = [] if variables is None else variables # If we have N levels, we need to set up dissipation on N + 1 zones num_levels = grid.max_num_refinement_levels + 1 eps_dis_levels = {} if prescription == DissPrescription.const: # Same everywhere eps_dis_levels = {level: eps_dis_finest for level in range(num_levels)} elif prescription == DissPrescription.dtfact: # Same everywhere, halved by two every time a level is synced # For example, if have 4 levels, 2 synced, then we should have # [1/4, 1/2, 1, 1] * eps_dis num_levels_synced = grid.num_levels_with_dt_coarse # First, the ones with the values halved eps_dis_levels = { level: eps_dis_finest / 2 ** (num_levels_synced - level) for level in range(num_levels_synced) } # Next, we add all the ones that are eps_dis_finest eps_dis_levels.update( { level: eps_dis_finest for level in range( num_levels_synced, num_levels, ) } ) elif prescription == DissPrescription.continuous: # Proportional to the local size, so there's a factor of 2**order for every # refinement level eps_dis_levels = { level: eps_dis_finest / (2 ** (num_levels - level - 1)) ** order for level in range(num_levels) } elif prescription == DissPrescription.partially_continuous: # Continuous where the levels are synced num_levels_synced = grid.num_levels_with_dt_coarse eps_dis_levels = { level: eps_dis_finest / (2 ** (num_levels_synced - level)) ** order for level in range(num_levels_synced) } # Next, we add all the ones that are eps_dis_finest eps_dis_levels.update( { level: eps_dis_finest for level in range( num_levels_synced, num_levels, ) } ) return Dissipation(eps_dis_levels, order, variables)
[docs]def add_to_dissipation(variables, doc): """Return a function that adds variables to the given Dissipation.""" def inner(dissipation): __doc__ = doc # noqa: F841 return Dissipation( dissipation.epsdis_per_level, dissipation.order, dissipation.variables + variables, ) return inner
_lean_vars = [ "LeanBSSNMoL::conf_fac", "LeanBSSNMoL::hmetric", "LeanBSSNMoL::hcurv", "LeanBSSNMoL::trk", "LeanBSSNMoL::gammat", ] _gauge_vars = ["ADMBase::lapse", "ADMBase::shift"] _proca_vars = [ "ProcaBase::Ei", "ProcaBase::Ai", "ProcaBase::Aphi", "ProcaBase::Zeta", ] add_Lean = add_to_dissipation( _lean_vars, """Return a new :py:class:`~.Dissipation` with Lean's variables added. :param dissipation: Dissipation that has to be used as base. :type dissipation: :py:class:`~.Dissipation` :returns: Dissipation with Lean's variables added. :rtype: :py:class:`~.Dissipation`""", ) add_gauge = add_to_dissipation( _gauge_vars, """Return a new :py:class:`~.Dissipation` with guage variables added. :param dissipation: Dissipation that has to be used as base. :type dissipation: :py:class:`~.Dissipation` :returns: Dissipation with guage variables added. :rtype: :py:class:`~.Dissipation`""", ) add_Proca = add_to_dissipation( _proca_vars, """Return a new :py:class:`~.Dissipation` with Proca variables added. :param dissipation: Dissipation that has to be used as base. :type dissipation: :py:class:`~.Dissipation` :returns: Dissipation with Proca variables added. :rtype: :py:class:`~.Dissipation`""", )