PyMPDATA.solver

class grouping user-supplied stepper, fields and post-step/post-iter hooks, as well as self-initialised temporary storage

  1"""
  2class grouping user-supplied stepper, fields and post-step/post-iter hooks,
  3as well as self-initialised temporary storage
  4"""
  5
  6from typing import Union
  7
  8import numba
  9
 10from .impl.meta import META_IS_NULL
 11from .scalar_field import ScalarField
 12from .stepper import Stepper
 13from .vector_field import VectorField
 14
 15
 16@numba.experimental.jitclass([])
 17class PostStepNull:  # pylint: disable=too-few-public-methods
 18    """do-nothing version of the post-step hook"""
 19
 20    def __init__(self):
 21        pass
 22
 23    def call(self, psi, step):  # pylint: disable-next=unused-argument
 24        """think of it as a `__call__` method (which Numba does not allow)"""
 25
 26
 27@numba.experimental.jitclass([])
 28class PostIterNull:  # pylint: disable=too-few-public-methods
 29    """do-nothing version of the post-iter hook"""
 30
 31    def __init__(self):
 32        pass
 33
 34    def call(self, flux, g_factor, step, iteration):  # pylint: disable=unused-argument
 35        """think of it as a `__call__` method (which Numba does not allow)"""
 36
 37
 38class Solver:
 39    """Solution orchestrator requiring prior instantiation of: a `Stepper`,
 40    a scalar advectee field (that which is advected),
 41    a vector advector field (that which advects),
 42    and optionally a scalar g_factor field (used in some cases of the advection equation).
 43    Note: in some cases of advection, i.e. momentum advection,
 44    the advectee can act upon the advector.
 45    See `PyMPDATA_examples.Jarecka_et_al_2015` for an example of this.
 46    """
 47
 48    def __init__(
 49        self,
 50        stepper: Stepper,
 51        advectee: ScalarField,
 52        advector: VectorField,
 53        g_factor: [ScalarField, None] = None,
 54    ):
 55        def scalar_field(dtype=None):
 56            return ScalarField.clone(advectee, dtype=dtype)
 57
 58        def null_scalar_field():
 59            return ScalarField.make_null(advectee.n_dims, stepper.traversals)
 60
 61        def vector_field():
 62            return VectorField.clone(advector)
 63
 64        def null_vector_field():
 65            return VectorField.make_null(advector.n_dims, stepper.traversals)
 66
 67        for field in [advector, advectee] + (
 68            [g_factor] if g_factor is not None else []
 69        ):
 70            assert field.halo == stepper.options.n_halo
 71            assert field.dtype == stepper.options.dtype
 72            assert field.grid == advector.grid
 73
 74        self.__fields = {
 75            "advectee": advectee,
 76            "advector": advector,
 77            "g_factor": g_factor or null_scalar_field(),
 78            "vectmp_a": vector_field(),
 79            "vectmp_b": vector_field(),
 80            "vectmp_c": (
 81                vector_field()
 82                if stepper.options.non_zero_mu_coeff
 83                else null_vector_field()
 84            ),
 85            "nonosc_xtrm": (
 86                scalar_field(dtype=complex)
 87                if stepper.options.nonoscillatory
 88                else null_scalar_field()
 89            ),
 90            "nonosc_beta": (
 91                scalar_field(dtype=complex)
 92                if stepper.options.nonoscillatory
 93                else null_scalar_field()
 94            ),
 95        }
 96        for field in self.__fields.values():
 97            field.assemble(stepper.traversals)
 98
 99        self.__stepper = stepper
100
101    @property
102    def advectee(self) -> ScalarField:
103        """advectee scalar field (with halo), modified by advance(),
104        may be modified from user code (e.g., source-term handling)"""
105        return self.__fields["advectee"]
106
107    @property
108    def advector(self) -> VectorField:
109        """advector vector field (with halo), unmodified by advance(),
110        may be modified from user code"""
111        return self.__fields["advector"]
112
113    @property
114    def g_factor(self) -> ScalarField:
115        """G_factor field (with halo), unmodified by advance(), assumed to be constant-in-time.
116        Can be used as a Jacobian for coordinate transformations,
117        e.g. into spherical coordinates.
118        For this type of usage, see
119        `PyMPDATA_examples.Williamson_and_Rasch_1989_as_in_Jaruga_et_al_2015_Fig_14`.
120        Can also be used to account for spatial variability of fluid density, see
121        `PyMPDATA_examples.Shipway_and_Hill_2012`.
122        e.g. the changing density of a fluid."""
123        return self.__fields["g_factor"]
124
125    def advance(
126        self,
127        n_steps: int,
128        mu_coeff: Union[tuple, None] = None,
129        post_step=None,
130        post_iter=None,
131    ):
132        """advances solution by `n_steps` steps, optionally accepts: a tuple of diffusion
133        coefficients (one value per dimension) as well as `post_iter` and `post_step`
134        callbacks expected to be `numba.jitclass`es with a `call` method, for
135        signature see `PostStepNull` and `PostIterNull`;
136        returns CPU time per timestep (units as returned by `clock.clock()`)"""
137        if mu_coeff is not None:
138            assert self.__stepper.options.non_zero_mu_coeff
139        else:
140            mu_coeff = (0.0, 0.0, 0.0)
141        if (
142            self.__stepper.options.non_zero_mu_coeff
143            and not self.__fields["g_factor"].meta[META_IS_NULL]
144        ):
145            raise NotImplementedError()
146
147        post_step = post_step or PostStepNull()
148        post_iter = post_iter or PostIterNull()
149
150        return self.__stepper(
151            n_steps=n_steps,
152            mu_coeff=mu_coeff,
153            post_step=post_step,
154            post_iter=post_iter,
155            fields=self.__fields,
156        )
class Solver:
 39class Solver:
 40    """Solution orchestrator requiring prior instantiation of: a `Stepper`,
 41    a scalar advectee field (that which is advected),
 42    a vector advector field (that which advects),
 43    and optionally a scalar g_factor field (used in some cases of the advection equation).
 44    Note: in some cases of advection, i.e. momentum advection,
 45    the advectee can act upon the advector.
 46    See `PyMPDATA_examples.Jarecka_et_al_2015` for an example of this.
 47    """
 48
 49    def __init__(
 50        self,
 51        stepper: Stepper,
 52        advectee: ScalarField,
 53        advector: VectorField,
 54        g_factor: [ScalarField, None] = None,
 55    ):
 56        def scalar_field(dtype=None):
 57            return ScalarField.clone(advectee, dtype=dtype)
 58
 59        def null_scalar_field():
 60            return ScalarField.make_null(advectee.n_dims, stepper.traversals)
 61
 62        def vector_field():
 63            return VectorField.clone(advector)
 64
 65        def null_vector_field():
 66            return VectorField.make_null(advector.n_dims, stepper.traversals)
 67
 68        for field in [advector, advectee] + (
 69            [g_factor] if g_factor is not None else []
 70        ):
 71            assert field.halo == stepper.options.n_halo
 72            assert field.dtype == stepper.options.dtype
 73            assert field.grid == advector.grid
 74
 75        self.__fields = {
 76            "advectee": advectee,
 77            "advector": advector,
 78            "g_factor": g_factor or null_scalar_field(),
 79            "vectmp_a": vector_field(),
 80            "vectmp_b": vector_field(),
 81            "vectmp_c": (
 82                vector_field()
 83                if stepper.options.non_zero_mu_coeff
 84                else null_vector_field()
 85            ),
 86            "nonosc_xtrm": (
 87                scalar_field(dtype=complex)
 88                if stepper.options.nonoscillatory
 89                else null_scalar_field()
 90            ),
 91            "nonosc_beta": (
 92                scalar_field(dtype=complex)
 93                if stepper.options.nonoscillatory
 94                else null_scalar_field()
 95            ),
 96        }
 97        for field in self.__fields.values():
 98            field.assemble(stepper.traversals)
 99
100        self.__stepper = stepper
101
102    @property
103    def advectee(self) -> ScalarField:
104        """advectee scalar field (with halo), modified by advance(),
105        may be modified from user code (e.g., source-term handling)"""
106        return self.__fields["advectee"]
107
108    @property
109    def advector(self) -> VectorField:
110        """advector vector field (with halo), unmodified by advance(),
111        may be modified from user code"""
112        return self.__fields["advector"]
113
114    @property
115    def g_factor(self) -> ScalarField:
116        """G_factor field (with halo), unmodified by advance(), assumed to be constant-in-time.
117        Can be used as a Jacobian for coordinate transformations,
118        e.g. into spherical coordinates.
119        For this type of usage, see
120        `PyMPDATA_examples.Williamson_and_Rasch_1989_as_in_Jaruga_et_al_2015_Fig_14`.
121        Can also be used to account for spatial variability of fluid density, see
122        `PyMPDATA_examples.Shipway_and_Hill_2012`.
123        e.g. the changing density of a fluid."""
124        return self.__fields["g_factor"]
125
126    def advance(
127        self,
128        n_steps: int,
129        mu_coeff: Union[tuple, None] = None,
130        post_step=None,
131        post_iter=None,
132    ):
133        """advances solution by `n_steps` steps, optionally accepts: a tuple of diffusion
134        coefficients (one value per dimension) as well as `post_iter` and `post_step`
135        callbacks expected to be `numba.jitclass`es with a `call` method, for
136        signature see `PostStepNull` and `PostIterNull`;
137        returns CPU time per timestep (units as returned by `clock.clock()`)"""
138        if mu_coeff is not None:
139            assert self.__stepper.options.non_zero_mu_coeff
140        else:
141            mu_coeff = (0.0, 0.0, 0.0)
142        if (
143            self.__stepper.options.non_zero_mu_coeff
144            and not self.__fields["g_factor"].meta[META_IS_NULL]
145        ):
146            raise NotImplementedError()
147
148        post_step = post_step or PostStepNull()
149        post_iter = post_iter or PostIterNull()
150
151        return self.__stepper(
152            n_steps=n_steps,
153            mu_coeff=mu_coeff,
154            post_step=post_step,
155            post_iter=post_iter,
156            fields=self.__fields,
157        )

Solution orchestrator requiring prior instantiation of: a Stepper, a scalar advectee field (that which is advected), a vector advector field (that which advects), and optionally a scalar g_factor field (used in some cases of the advection equation). Note: in some cases of advection, i.e. momentum advection, the advectee can act upon the advector. See PyMPDATA_examples.Jarecka_et_al_2015 for an example of this.

Solver( stepper: PyMPDATA.stepper.Stepper, advectee: PyMPDATA.scalar_field.ScalarField, advector: PyMPDATA.vector_field.VectorField, g_factor: [<class 'PyMPDATA.scalar_field.ScalarField'>, None] = None)
 49    def __init__(
 50        self,
 51        stepper: Stepper,
 52        advectee: ScalarField,
 53        advector: VectorField,
 54        g_factor: [ScalarField, None] = None,
 55    ):
 56        def scalar_field(dtype=None):
 57            return ScalarField.clone(advectee, dtype=dtype)
 58
 59        def null_scalar_field():
 60            return ScalarField.make_null(advectee.n_dims, stepper.traversals)
 61
 62        def vector_field():
 63            return VectorField.clone(advector)
 64
 65        def null_vector_field():
 66            return VectorField.make_null(advector.n_dims, stepper.traversals)
 67
 68        for field in [advector, advectee] + (
 69            [g_factor] if g_factor is not None else []
 70        ):
 71            assert field.halo == stepper.options.n_halo
 72            assert field.dtype == stepper.options.dtype
 73            assert field.grid == advector.grid
 74
 75        self.__fields = {
 76            "advectee": advectee,
 77            "advector": advector,
 78            "g_factor": g_factor or null_scalar_field(),
 79            "vectmp_a": vector_field(),
 80            "vectmp_b": vector_field(),
 81            "vectmp_c": (
 82                vector_field()
 83                if stepper.options.non_zero_mu_coeff
 84                else null_vector_field()
 85            ),
 86            "nonosc_xtrm": (
 87                scalar_field(dtype=complex)
 88                if stepper.options.nonoscillatory
 89                else null_scalar_field()
 90            ),
 91            "nonosc_beta": (
 92                scalar_field(dtype=complex)
 93                if stepper.options.nonoscillatory
 94                else null_scalar_field()
 95            ),
 96        }
 97        for field in self.__fields.values():
 98            field.assemble(stepper.traversals)
 99
100        self.__stepper = stepper
advectee: PyMPDATA.scalar_field.ScalarField
102    @property
103    def advectee(self) -> ScalarField:
104        """advectee scalar field (with halo), modified by advance(),
105        may be modified from user code (e.g., source-term handling)"""
106        return self.__fields["advectee"]

advectee scalar field (with halo), modified by advance(), may be modified from user code (e.g., source-term handling)

advector: PyMPDATA.vector_field.VectorField
108    @property
109    def advector(self) -> VectorField:
110        """advector vector field (with halo), unmodified by advance(),
111        may be modified from user code"""
112        return self.__fields["advector"]

advector vector field (with halo), unmodified by advance(), may be modified from user code

g_factor: PyMPDATA.scalar_field.ScalarField
114    @property
115    def g_factor(self) -> ScalarField:
116        """G_factor field (with halo), unmodified by advance(), assumed to be constant-in-time.
117        Can be used as a Jacobian for coordinate transformations,
118        e.g. into spherical coordinates.
119        For this type of usage, see
120        `PyMPDATA_examples.Williamson_and_Rasch_1989_as_in_Jaruga_et_al_2015_Fig_14`.
121        Can also be used to account for spatial variability of fluid density, see
122        `PyMPDATA_examples.Shipway_and_Hill_2012`.
123        e.g. the changing density of a fluid."""
124        return self.__fields["g_factor"]

G_factor field (with halo), unmodified by advance(), assumed to be constant-in-time. Can be used as a Jacobian for coordinate transformations, e.g. into spherical coordinates. For this type of usage, see PyMPDATA_examples.Williamson_and_Rasch_1989_as_in_Jaruga_et_al_2015_Fig_14. Can also be used to account for spatial variability of fluid density, see PyMPDATA_examples.Shipway_and_Hill_2012. e.g. the changing density of a fluid.

def advance( self, n_steps: int, mu_coeff: Optional[tuple] = None, post_step=None, post_iter=None):
126    def advance(
127        self,
128        n_steps: int,
129        mu_coeff: Union[tuple, None] = None,
130        post_step=None,
131        post_iter=None,
132    ):
133        """advances solution by `n_steps` steps, optionally accepts: a tuple of diffusion
134        coefficients (one value per dimension) as well as `post_iter` and `post_step`
135        callbacks expected to be `numba.jitclass`es with a `call` method, for
136        signature see `PostStepNull` and `PostIterNull`;
137        returns CPU time per timestep (units as returned by `clock.clock()`)"""
138        if mu_coeff is not None:
139            assert self.__stepper.options.non_zero_mu_coeff
140        else:
141            mu_coeff = (0.0, 0.0, 0.0)
142        if (
143            self.__stepper.options.non_zero_mu_coeff
144            and not self.__fields["g_factor"].meta[META_IS_NULL]
145        ):
146            raise NotImplementedError()
147
148        post_step = post_step or PostStepNull()
149        post_iter = post_iter or PostIterNull()
150
151        return self.__stepper(
152            n_steps=n_steps,
153            mu_coeff=mu_coeff,
154            post_step=post_step,
155            post_iter=post_iter,
156            fields=self.__fields,
157        )

advances solution by n_steps steps, optionally accepts: a tuple of diffusion coefficients (one value per dimension) as well as post_iter and post_step callbacks expected to be numba.jitclasses with a call method, for signature see PostStepNull and PostIterNull; returns CPU time per timestep (units as returned by clock.clock())