PyMPDATA.options

MPDATA variants, iterations, data-type and jit-flags settings

  1"""
  2MPDATA variants, iterations, data-type and jit-flags settings
  3"""
  4
  5import numpy as np
  6from pystrict import strict
  7
  8
  9class HashableDict(dict):
 10    """serialization enabler"""
 11
 12    def __hash__(self):
 13        return hash(tuple(sorted(self.items())))
 14
 15
 16@strict
 17class Options:
 18    """representation of MPDATA algorithm variant choice, for an overview of
 19    MPDATA options implemented in PyMPDATA, see
 20    [Olesik et al. 2020](https://doi.org/10.5194/gmd-15-3879-2022);
 21    equipped with meaningful `__str__` `__hash__`, `__eq__`.
 22    """
 23
 24    def __init__(
 25        self,
 26        *,
 27        n_iters: int = 2,
 28        infinite_gauge: bool = False,
 29        divergent_flow: bool = False,
 30        nonoscillatory: bool = False,
 31        third_order_terms: bool = False,
 32        DPDC: bool = False,  # pylint: disable=invalid-name
 33        epsilon: float = 1e-15,
 34        non_zero_mu_coeff: bool = False,
 35        dimensionally_split: bool = False,
 36        dtype: [np.float32, np.float64] = np.float64
 37    ):
 38        self._values = HashableDict(
 39            {
 40                "n_iters": n_iters,
 41                "infinite_gauge": infinite_gauge,
 42                "epsilon": epsilon,
 43                "divergent_flow": divergent_flow,
 44                "nonoscillatory": nonoscillatory,
 45                "third_order_terms": third_order_terms,
 46                "non_zero_mu_coeff": non_zero_mu_coeff,
 47                "dimensionally_split": dimensionally_split,
 48                "dtype": dtype,
 49                "DPDC": DPDC,
 50            }
 51        )
 52
 53        if (
 54            any(
 55                (
 56                    infinite_gauge,
 57                    divergent_flow,
 58                    nonoscillatory,
 59                    third_order_terms,
 60                    DPDC,
 61                )
 62            )
 63            and n_iters < 2
 64        ):
 65            raise ValueError()
 66        if n_iters < 1:
 67            raise ValueError()
 68
 69    @property
 70    def dtype(self):
 71        """data type (e.g., np.float64)"""
 72        return self._values["dtype"]
 73
 74    @property
 75    def n_iters(self) -> int:
 76        """Number of corrective iterations in the MPDATA algorithm + 1
 77        e.g. (1: upwind, 2: upwind + one corrective iteration, ...).
 78        Bigger values mean smaller error, but more computational cost.
 79        It does not change the order of the method.
 80        The order of the method depends on the variant of antidiffusive
 81        velocity used, see for example `third_order_terms` option.
 82        Note: not to confuse with n_steps in the Stepper."""
 83        return self._values["n_iters"]
 84
 85    @property
 86    def infinite_gauge(self) -> bool:
 87        """flag enabling the infinite-gauge option, see e.g.:
 88        [Margolin & Shashkov, 2006](https://doi.org/10.1002/fld.1070),
 89        [Smolarkiewicz & Clark, 1986](https://doi.org/10.1016/0021-9991(86)90270-6)
 90        """
 91        return self._values["infinite_gauge"]
 92
 93    @property
 94    def epsilon(self) -> float:
 95        """value of constant used to prevent from divisions by zero
 96        in statements such as (a - b)/(a + b + eps)"""
 97        return self._values["epsilon"]
 98
 99    @property
100    def divergent_flow(self) -> bool:
101        """flag enabling the divergent-flow option, see e.g.:
102        [Smolarkiewicz, 1984](https://doi.org/10.1016/0021-9991(84)90121-9),
103        [Margolin & Smolarkiewicz, 1998](https://doi.org/10.1137/S106482759324700X)
104        """
105        return self._values["divergent_flow"]
106
107    @property
108    def nonoscillatory(self) -> bool:
109        """flag enabling the nonoscillatory option, see
110        [Smolarkiewicz & Grabowski 1990](https://doi.org/10.1016/0021-9991(90)90105-A)
111        """
112        return self._values["nonoscillatory"]
113
114    @property
115    def third_order_terms(self) -> bool:
116        """flag enabling the third-order-terms option, see
117        [Margolin & Smolarkiewicz 1998](https://doi.org/10.1137/S106482759324700X)"""
118        return self._values["third_order_terms"]
119
120    @property
121    def DPDC(self) -> bool:  # pylint: disable=invalid-name
122        """flag enabling the double-pass donor-cell option, see:
123        [Beason & Margolin, 1988](https://osti.gov/biblio/7049237),
124        [Margolin & Shashkov, 2006](https://doi.org/10.1002/fld.1070),
125        [Margolin & Smolarkiewicz, 1998](https://doi.org/10.1137/S106482759324700X)
126        """
127        return self._values["DPDC"]
128
129    @property
130    def non_zero_mu_coeff(self) -> bool:
131        """flag enabling handling of Fickian diffusion term"""
132        return self._values["non_zero_mu_coeff"]
133
134    @property
135    def dimensionally_split(self) -> bool:
136        """flag disabling cross-dimensional terms in antidiffusive velocities"""
137        return self._values["dimensionally_split"]
138
139    def __str__(self):
140        return str(self._values)
141
142    def __hash__(self):
143        value = hash(self._values) + hash(self.jit_flags)
144        return value
145
146    def __eq__(self, other):
147        return other.__hash__() == self.__hash__()
148
149    @property
150    def n_halo(self) -> int:
151        """Halo extent for a given options set.
152        The halo extent is the number of 'ghost layers' that need to be added
153        to the outside of the domain to ensure that the MPDATA stencil operations can be
154        applied to the edges of the domain.
155        It is similar to
156        [array padding](https://numpy.org/doc/stable/reference/generated/numpy.pad.html).
157        The halo extent is determined by the options set."""
158        if self.divergent_flow or self.nonoscillatory or self.third_order_terms:
159            return 2
160        return 1
161
162    @property
163    def jit_flags(self) -> HashableDict:
164        """options passed [to numba.njit()](
165        https://numba.pydata.org/numba-doc/dev/user/jit.html#compilation-options)"""
166        return HashableDict(
167            {
168                "fastmath": True,
169                "error_model": "numpy",
170                "boundscheck": False,
171            }
172        )
class HashableDict(builtins.dict):
10class HashableDict(dict):
11    """serialization enabler"""
12
13    def __hash__(self):
14        return hash(tuple(sorted(self.items())))

serialization enabler

@strict
class Options:
 17@strict
 18class Options:
 19    """representation of MPDATA algorithm variant choice, for an overview of
 20    MPDATA options implemented in PyMPDATA, see
 21    [Olesik et al. 2020](https://doi.org/10.5194/gmd-15-3879-2022);
 22    equipped with meaningful `__str__` `__hash__`, `__eq__`.
 23    """
 24
 25    def __init__(
 26        self,
 27        *,
 28        n_iters: int = 2,
 29        infinite_gauge: bool = False,
 30        divergent_flow: bool = False,
 31        nonoscillatory: bool = False,
 32        third_order_terms: bool = False,
 33        DPDC: bool = False,  # pylint: disable=invalid-name
 34        epsilon: float = 1e-15,
 35        non_zero_mu_coeff: bool = False,
 36        dimensionally_split: bool = False,
 37        dtype: [np.float32, np.float64] = np.float64
 38    ):
 39        self._values = HashableDict(
 40            {
 41                "n_iters": n_iters,
 42                "infinite_gauge": infinite_gauge,
 43                "epsilon": epsilon,
 44                "divergent_flow": divergent_flow,
 45                "nonoscillatory": nonoscillatory,
 46                "third_order_terms": third_order_terms,
 47                "non_zero_mu_coeff": non_zero_mu_coeff,
 48                "dimensionally_split": dimensionally_split,
 49                "dtype": dtype,
 50                "DPDC": DPDC,
 51            }
 52        )
 53
 54        if (
 55            any(
 56                (
 57                    infinite_gauge,
 58                    divergent_flow,
 59                    nonoscillatory,
 60                    third_order_terms,
 61                    DPDC,
 62                )
 63            )
 64            and n_iters < 2
 65        ):
 66            raise ValueError()
 67        if n_iters < 1:
 68            raise ValueError()
 69
 70    @property
 71    def dtype(self):
 72        """data type (e.g., np.float64)"""
 73        return self._values["dtype"]
 74
 75    @property
 76    def n_iters(self) -> int:
 77        """Number of corrective iterations in the MPDATA algorithm + 1
 78        e.g. (1: upwind, 2: upwind + one corrective iteration, ...).
 79        Bigger values mean smaller error, but more computational cost.
 80        It does not change the order of the method.
 81        The order of the method depends on the variant of antidiffusive
 82        velocity used, see for example `third_order_terms` option.
 83        Note: not to confuse with n_steps in the Stepper."""
 84        return self._values["n_iters"]
 85
 86    @property
 87    def infinite_gauge(self) -> bool:
 88        """flag enabling the infinite-gauge option, see e.g.:
 89        [Margolin & Shashkov, 2006](https://doi.org/10.1002/fld.1070),
 90        [Smolarkiewicz & Clark, 1986](https://doi.org/10.1016/0021-9991(86)90270-6)
 91        """
 92        return self._values["infinite_gauge"]
 93
 94    @property
 95    def epsilon(self) -> float:
 96        """value of constant used to prevent from divisions by zero
 97        in statements such as (a - b)/(a + b + eps)"""
 98        return self._values["epsilon"]
 99
100    @property
101    def divergent_flow(self) -> bool:
102        """flag enabling the divergent-flow option, see e.g.:
103        [Smolarkiewicz, 1984](https://doi.org/10.1016/0021-9991(84)90121-9),
104        [Margolin & Smolarkiewicz, 1998](https://doi.org/10.1137/S106482759324700X)
105        """
106        return self._values["divergent_flow"]
107
108    @property
109    def nonoscillatory(self) -> bool:
110        """flag enabling the nonoscillatory option, see
111        [Smolarkiewicz & Grabowski 1990](https://doi.org/10.1016/0021-9991(90)90105-A)
112        """
113        return self._values["nonoscillatory"]
114
115    @property
116    def third_order_terms(self) -> bool:
117        """flag enabling the third-order-terms option, see
118        [Margolin & Smolarkiewicz 1998](https://doi.org/10.1137/S106482759324700X)"""
119        return self._values["third_order_terms"]
120
121    @property
122    def DPDC(self) -> bool:  # pylint: disable=invalid-name
123        """flag enabling the double-pass donor-cell option, see:
124        [Beason & Margolin, 1988](https://osti.gov/biblio/7049237),
125        [Margolin & Shashkov, 2006](https://doi.org/10.1002/fld.1070),
126        [Margolin & Smolarkiewicz, 1998](https://doi.org/10.1137/S106482759324700X)
127        """
128        return self._values["DPDC"]
129
130    @property
131    def non_zero_mu_coeff(self) -> bool:
132        """flag enabling handling of Fickian diffusion term"""
133        return self._values["non_zero_mu_coeff"]
134
135    @property
136    def dimensionally_split(self) -> bool:
137        """flag disabling cross-dimensional terms in antidiffusive velocities"""
138        return self._values["dimensionally_split"]
139
140    def __str__(self):
141        return str(self._values)
142
143    def __hash__(self):
144        value = hash(self._values) + hash(self.jit_flags)
145        return value
146
147    def __eq__(self, other):
148        return other.__hash__() == self.__hash__()
149
150    @property
151    def n_halo(self) -> int:
152        """Halo extent for a given options set.
153        The halo extent is the number of 'ghost layers' that need to be added
154        to the outside of the domain to ensure that the MPDATA stencil operations can be
155        applied to the edges of the domain.
156        It is similar to
157        [array padding](https://numpy.org/doc/stable/reference/generated/numpy.pad.html).
158        The halo extent is determined by the options set."""
159        if self.divergent_flow or self.nonoscillatory or self.third_order_terms:
160            return 2
161        return 1
162
163    @property
164    def jit_flags(self) -> HashableDict:
165        """options passed [to numba.njit()](
166        https://numba.pydata.org/numba-doc/dev/user/jit.html#compilation-options)"""
167        return HashableDict(
168            {
169                "fastmath": True,
170                "error_model": "numpy",
171                "boundscheck": False,
172            }
173        )

representation of MPDATA algorithm variant choice, for an overview of MPDATA options implemented in PyMPDATA, see Olesik et al. 2020; equipped with meaningful __str__ __hash__, __eq__.

Options( *, n_iters: int = 2, infinite_gauge: bool = False, divergent_flow: bool = False, nonoscillatory: bool = False, third_order_terms: bool = False, DPDC: bool = False, epsilon: float = 1e-15, non_zero_mu_coeff: bool = False, dimensionally_split: bool = False, dtype: [<class 'numpy.float32'>, <class 'numpy.float64'>] = <class 'numpy.float64'>)
25    def __init__(
26        self,
27        *,
28        n_iters: int = 2,
29        infinite_gauge: bool = False,
30        divergent_flow: bool = False,
31        nonoscillatory: bool = False,
32        third_order_terms: bool = False,
33        DPDC: bool = False,  # pylint: disable=invalid-name
34        epsilon: float = 1e-15,
35        non_zero_mu_coeff: bool = False,
36        dimensionally_split: bool = False,
37        dtype: [np.float32, np.float64] = np.float64
38    ):
39        self._values = HashableDict(
40            {
41                "n_iters": n_iters,
42                "infinite_gauge": infinite_gauge,
43                "epsilon": epsilon,
44                "divergent_flow": divergent_flow,
45                "nonoscillatory": nonoscillatory,
46                "third_order_terms": third_order_terms,
47                "non_zero_mu_coeff": non_zero_mu_coeff,
48                "dimensionally_split": dimensionally_split,
49                "dtype": dtype,
50                "DPDC": DPDC,
51            }
52        )
53
54        if (
55            any(
56                (
57                    infinite_gauge,
58                    divergent_flow,
59                    nonoscillatory,
60                    third_order_terms,
61                    DPDC,
62                )
63            )
64            and n_iters < 2
65        ):
66            raise ValueError()
67        if n_iters < 1:
68            raise ValueError()
dtype
70    @property
71    def dtype(self):
72        """data type (e.g., np.float64)"""
73        return self._values["dtype"]

data type (e.g., np.float64)

n_iters: int
75    @property
76    def n_iters(self) -> int:
77        """Number of corrective iterations in the MPDATA algorithm + 1
78        e.g. (1: upwind, 2: upwind + one corrective iteration, ...).
79        Bigger values mean smaller error, but more computational cost.
80        It does not change the order of the method.
81        The order of the method depends on the variant of antidiffusive
82        velocity used, see for example `third_order_terms` option.
83        Note: not to confuse with n_steps in the Stepper."""
84        return self._values["n_iters"]

Number of corrective iterations in the MPDATA algorithm + 1 e.g. (1: upwind, 2: upwind + one corrective iteration, ...). Bigger values mean smaller error, but more computational cost. It does not change the order of the method. The order of the method depends on the variant of antidiffusive velocity used, see for example third_order_terms option. Note: not to confuse with n_steps in the Stepper.

infinite_gauge: bool
86    @property
87    def infinite_gauge(self) -> bool:
88        """flag enabling the infinite-gauge option, see e.g.:
89        [Margolin & Shashkov, 2006](https://doi.org/10.1002/fld.1070),
90        [Smolarkiewicz & Clark, 1986](https://doi.org/10.1016/0021-9991(86)90270-6)
91        """
92        return self._values["infinite_gauge"]

flag enabling the infinite-gauge option, see e.g.: Margolin & Shashkov, 2006, Smolarkiewicz & Clark, 1986

epsilon: float
94    @property
95    def epsilon(self) -> float:
96        """value of constant used to prevent from divisions by zero
97        in statements such as (a - b)/(a + b + eps)"""
98        return self._values["epsilon"]

value of constant used to prevent from divisions by zero in statements such as (a - b)/(a + b + eps)

divergent_flow: bool
100    @property
101    def divergent_flow(self) -> bool:
102        """flag enabling the divergent-flow option, see e.g.:
103        [Smolarkiewicz, 1984](https://doi.org/10.1016/0021-9991(84)90121-9),
104        [Margolin & Smolarkiewicz, 1998](https://doi.org/10.1137/S106482759324700X)
105        """
106        return self._values["divergent_flow"]

flag enabling the divergent-flow option, see e.g.: Smolarkiewicz, 1984, Margolin & Smolarkiewicz, 1998

nonoscillatory: bool
108    @property
109    def nonoscillatory(self) -> bool:
110        """flag enabling the nonoscillatory option, see
111        [Smolarkiewicz & Grabowski 1990](https://doi.org/10.1016/0021-9991(90)90105-A)
112        """
113        return self._values["nonoscillatory"]

flag enabling the nonoscillatory option, see Smolarkiewicz & Grabowski 1990

third_order_terms: bool
115    @property
116    def third_order_terms(self) -> bool:
117        """flag enabling the third-order-terms option, see
118        [Margolin & Smolarkiewicz 1998](https://doi.org/10.1137/S106482759324700X)"""
119        return self._values["third_order_terms"]

flag enabling the third-order-terms option, see Margolin & Smolarkiewicz 1998

DPDC: bool
121    @property
122    def DPDC(self) -> bool:  # pylint: disable=invalid-name
123        """flag enabling the double-pass donor-cell option, see:
124        [Beason & Margolin, 1988](https://osti.gov/biblio/7049237),
125        [Margolin & Shashkov, 2006](https://doi.org/10.1002/fld.1070),
126        [Margolin & Smolarkiewicz, 1998](https://doi.org/10.1137/S106482759324700X)
127        """
128        return self._values["DPDC"]

flag enabling the double-pass donor-cell option, see: Beason & Margolin, 1988, Margolin & Shashkov, 2006, Margolin & Smolarkiewicz, 1998

non_zero_mu_coeff: bool
130    @property
131    def non_zero_mu_coeff(self) -> bool:
132        """flag enabling handling of Fickian diffusion term"""
133        return self._values["non_zero_mu_coeff"]

flag enabling handling of Fickian diffusion term

dimensionally_split: bool
135    @property
136    def dimensionally_split(self) -> bool:
137        """flag disabling cross-dimensional terms in antidiffusive velocities"""
138        return self._values["dimensionally_split"]

flag disabling cross-dimensional terms in antidiffusive velocities

n_halo: int
150    @property
151    def n_halo(self) -> int:
152        """Halo extent for a given options set.
153        The halo extent is the number of 'ghost layers' that need to be added
154        to the outside of the domain to ensure that the MPDATA stencil operations can be
155        applied to the edges of the domain.
156        It is similar to
157        [array padding](https://numpy.org/doc/stable/reference/generated/numpy.pad.html).
158        The halo extent is determined by the options set."""
159        if self.divergent_flow or self.nonoscillatory or self.third_order_terms:
160            return 2
161        return 1

Halo extent for a given options set. The halo extent is the number of 'ghost layers' that need to be added to the outside of the domain to ensure that the MPDATA stencil operations can be applied to the edges of the domain. It is similar to array padding. The halo extent is determined by the options set.

jit_flags: HashableDict
163    @property
164    def jit_flags(self) -> HashableDict:
165        """options passed [to numba.njit()](
166        https://numba.pydata.org/numba-doc/dev/user/jit.html#compilation-options)"""
167        return HashableDict(
168            {
169                "fastmath": True,
170                "error_model": "numpy",
171                "boundscheck": False,
172            }
173        )

options passed to numba.njit()