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

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, dynamic_advector: 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        dynamic_advector: bool = False,
37        dimensionally_split: bool = False,
38        dtype: [np.float32, np.float64] = np.float64
39    ):
40        self._values = HashableDict(
41            {
42                "n_iters": n_iters,
43                "infinite_gauge": infinite_gauge,
44                "epsilon": epsilon,
45                "divergent_flow": divergent_flow,
46                "nonoscillatory": nonoscillatory,
47                "third_order_terms": third_order_terms,
48                "non_zero_mu_coeff": non_zero_mu_coeff,
49                "dynamic_advector": dynamic_advector,
50                "dimensionally_split": dimensionally_split,
51                "dtype": dtype,
52                "DPDC": DPDC,
53            }
54        )
55
56        if (
57            any(
58                (
59                    infinite_gauge,
60                    divergent_flow,
61                    nonoscillatory,
62                    third_order_terms,
63                    DPDC,
64                )
65            )
66            and n_iters < 2
67        ):
68            raise ValueError()
69        if n_iters < 1:
70            raise ValueError()
dtype
72    @property
73    def dtype(self):
74        """data type (e.g., np.float64)"""
75        return self._values["dtype"]

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

n_iters: int
77    @property
78    def n_iters(self) -> int:
79        """Number of corrective iterations in the MPDATA algorithm + 1
80        e.g. (1: upwind, 2: upwind + one corrective iteration, ...).
81        Bigger values mean smaller error, but more computational cost.
82        It does not change the order of the method.
83        The order of the method depends on the variant of antidiffusive
84        velocity used, see for example `third_order_terms` option.
85        Note: not to confuse with n_steps in the Stepper."""
86        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
88    @property
89    def infinite_gauge(self) -> bool:
90        """flag enabling the infinite-gauge option, see e.g.:
91        [Margolin & Shashkov, 2006](https://doi.org/10.1002/fld.1070),
92        [Smolarkiewicz & Clark, 1986](https://doi.org/10.1016/0021-9991(86)90270-6)
93        """
94        return self._values["infinite_gauge"]

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

epsilon: float
 96    @property
 97    def epsilon(self) -> float:
 98        """value of constant used to prevent from divisions by zero
 99        in statements such as (a - b)/(a + b + eps)"""
100        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
102    @property
103    def divergent_flow(self) -> bool:
104        """flag enabling the divergent-flow option, see e.g.:
105        [Smolarkiewicz, 1984](https://doi.org/10.1016/0021-9991(84)90121-9),
106        [Margolin & Smolarkiewicz, 1998](https://doi.org/10.1137/S106482759324700X)
107        """
108        return self._values["divergent_flow"]

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

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

flag enabling the nonoscillatory option, see Smolarkiewicz & Grabowski 1990

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

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

DPDC: bool
123    @property
124    def DPDC(self) -> bool:  # pylint: disable=invalid-name
125        """flag enabling the double-pass donor-cell option, see:
126        [Beason & Margolin, 1988](https://osti.gov/biblio/7049237),
127        [Margolin & Shashkov, 2006](https://doi.org/10.1002/fld.1070),
128        [Margolin & Smolarkiewicz, 1998](https://doi.org/10.1137/S106482759324700X)
129        """
130        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
132    @property
133    def non_zero_mu_coeff(self) -> bool:
134        """flag enabling handling of Fickian diffusion term"""
135        return self._values["non_zero_mu_coeff"]

flag enabling handling of Fickian diffusion term

dynamic_advector: bool
137    @property
138    def dynamic_advector(self) -> bool:
139        """flag enabling (todo desc)"""
140        return self._values["dynamic_advector"]

flag enabling (todo desc)

dimensionally_split: bool
142    @property
143    def dimensionally_split(self) -> bool:
144        """flag disabling cross-dimensional terms in antidiffusive velocities"""
145        return self._values["dimensionally_split"]

flag disabling cross-dimensional terms in antidiffusive velocities

n_halo: int
157    @property
158    def n_halo(self) -> int:
159        """Halo extent for a given options set.
160        The halo extent is the number of 'ghost layers' that need to be added
161        to the outside of the domain to ensure that the MPDATA stencil operations can be
162        applied to the edges of the domain.
163        It is similar to
164        [array padding](https://numpy.org/doc/stable/reference/generated/numpy.pad.html).
165        The halo extent is determined by the options set."""
166        if self.divergent_flow or self.nonoscillatory or self.third_order_terms:
167            return 2
168        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
170    @property
171    def jit_flags(self) -> HashableDict:
172        """options passed [to numba.njit()](
173        https://numba.pydata.org/numba-doc/dev/user/jit.html#compilation-options)"""
174        return HashableDict(
175            {
176                "fastmath": True,
177                "error_model": "numpy",
178                "boundscheck": False,
179            }
180        )

options passed to numba.njit()