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 Hashable, Iterable, Mapping, 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 AnteStepNull: # pylint: disable=too-few-public-methods 18 """do-nothing version of the ante-step hook""" 19 20 def __init__(self): 21 pass 22 23 def call( 24 self, 25 traversals_data, 26 advectee, 27 advector, 28 step, 29 index, 30 dynamic_advector_stash_outer, 31 dynamic_advector_stash_mid3d, 32 dynamic_advector_stash_inner, 33 ): # pylint: disable-next=unused-argument,disable=too-many-arguments 34 """think of it as a `__call__` method (which Numba does not allow)""" 35 36 37@numba.experimental.jitclass([]) 38class PostStepNull: # pylint: disable=too-few-public-methods 39 """do-nothing version of the post-step hook""" 40 41 def __init__(self): 42 pass 43 44 def call( 45 self, traversals_data, psi, step, index 46 ): # pylint: disable-next=unused-argument 47 """think of it as a `__call__` method (which Numba does not allow)""" 48 49 50@numba.experimental.jitclass([]) 51class PostIterNull: # pylint: disable=too-few-public-methods 52 """do-nothing version of the post-iter hook""" 53 54 def __init__(self): 55 pass 56 57 def call( 58 self, traversals_data, flux, g_factor, step, iteration 59 ): # pylint: disable=unused-argument,disable=too-many-arguments 60 """think of it as a `__call__` method (which Numba does not allow)""" 61 62 63class Solver: 64 """Solution orchestrator requiring prior instantiation of: a `Stepper`, 65 a scalar advectee field (that which is advected), 66 a vector advector field (that which advects), 67 and optionally a scalar g_factor field (used in some cases of the advection equation). 68 Note: in some cases of advection, i.e. momentum advection, 69 the advectee can act upon the advector. 70 See `PyMPDATA_examples.Jarecka_et_al_2015` for an example of this. 71 """ 72 73 def __init__( 74 self, 75 stepper: Stepper, 76 advectee: [ScalarField, Iterable[ScalarField], Mapping[Hashable, ScalarField]], 77 advector: VectorField, 78 g_factor: [ScalarField, None] = None, 79 ): 80 self.advectee = advectee 81 n_dims = advector.n_dims 82 if isinstance(advectee, ScalarField): 83 self.__fields = {"advectee": (advectee,)} 84 85 elif isinstance(advectee, Mapping): 86 self.__fields = {"advectee": tuple(advectee.values())} 87 88 elif isinstance(advectee, Iterable): 89 self.__fields = {"advectee": tuple(advectee)} 90 91 def scalar_field(dtype=None): 92 return ScalarField.clone(self.__fields["advectee"][0], dtype=dtype) 93 94 def null_scalar_field(): 95 return ScalarField.make_null(n_dims, stepper.traversals) 96 97 def vector_field(): 98 return VectorField.clone(advector) 99 100 def null_vector_field(): 101 return VectorField.make_null(n_dims, stepper.traversals) 102 103 for field in [advector, *self.__fields["advectee"]] + ( 104 [g_factor] if g_factor is not None else [] 105 ): 106 assert field.halo == stepper.options.n_halo 107 assert field.dtype == stepper.options.dtype 108 assert field.grid == advector.grid 109 110 self.__fields.update( 111 { 112 "advector": advector, 113 "g_factor": g_factor or null_scalar_field(), 114 "vectmp_a": vector_field(), 115 "vectmp_b": vector_field(), 116 "vectmp_c": ( 117 vector_field() 118 if stepper.options.non_zero_mu_coeff 119 else null_vector_field() 120 ), 121 "dynamic_advector_stash_outer": ( 122 scalar_field() 123 if stepper.options.dynamic_advector and n_dims > 1 124 else null_scalar_field() 125 ), 126 "dynamic_advector_stash_mid3d": ( 127 scalar_field() 128 if stepper.options.dynamic_advector and n_dims > 2 129 else null_scalar_field() 130 ), 131 "dynamic_advector_stash_inner": ( 132 scalar_field() 133 if stepper.options.dynamic_advector 134 else null_scalar_field() 135 ), 136 "nonosc_xtrm": ( 137 scalar_field(dtype=complex) 138 if stepper.options.nonoscillatory 139 else null_scalar_field() 140 ), 141 "nonosc_beta": ( 142 scalar_field(dtype=complex) 143 if stepper.options.nonoscillatory 144 else null_scalar_field() 145 ), 146 } 147 ) 148 for key, value in self.__fields.items(): 149 for field in (value,) if key != "advectee" else value: 150 field.assemble(stepper.traversals) 151 152 self.__stepper = stepper 153 154 @property 155 def advector(self) -> VectorField: 156 """advector vector field , dynamic_advector_stash_outer, 157 dynamic_advector_stash_mid3d, dynamic_advector_stash_inner(with halo), 158 unmodified by advance(), may be modified from user code""" 159 return self.__fields["advector"] 160 161 @property 162 def g_factor(self) -> ScalarField: 163 """G_factor field (with halo), unmodified by advance(), assumed to be constant-in-time. 164 Can be used as a Jacobian for coordinate transformations, 165 e.g. into spherical coordinates. 166 For this type of usage, see 167 `PyMPDATA_examples.Williamson_and_Rasch_1989_as_in_Jaruga_et_al_2015_Fig_14`. 168 Can also be used to account for spatial variability of fluid density, see 169 `PyMPDATA_examples.Shipway_and_Hill_2012`. 170 e.g. the changing density of a fluid.""" 171 return self.__fields["g_factor"] 172 173 def advance( 174 self, 175 n_steps: int, 176 *, 177 mu_coeff: Union[tuple, None] = None, 178 ante_step=None, 179 post_step=None, 180 post_iter=None, 181 ): 182 """advances solution by `n_steps` steps, optionally accepts: a tuple of diffusion 183 coefficients (one value per dimension) as well as `post_iter` and `post_step` 184 callbacks expected to be `numba.jitclass`es with a `call` method, for 185 signature see `PostStepNull` and `PostIterNull`; 186 returns CPU time per timestep (units as returned by `clock.clock()`)""" 187 if mu_coeff is not None: 188 assert self.__stepper.options.non_zero_mu_coeff 189 else: 190 mu_coeff = (0.0, 0.0, 0.0) 191 if ( 192 self.__stepper.options.non_zero_mu_coeff 193 and not self.__fields["g_factor"].meta[META_IS_NULL] 194 ): 195 raise NotImplementedError() 196 197 ante_step = ante_step or AnteStepNull() 198 post_step = post_step or PostStepNull() 199 post_iter = post_iter or PostIterNull() 200 201 return self.__stepper( 202 n_steps=n_steps, 203 mu_coeff=mu_coeff, 204 ante_step=ante_step, 205 post_step=post_step, 206 post_iter=post_iter, 207 fields=self.__fields, 208 )
64class Solver: 65 """Solution orchestrator requiring prior instantiation of: a `Stepper`, 66 a scalar advectee field (that which is advected), 67 a vector advector field (that which advects), 68 and optionally a scalar g_factor field (used in some cases of the advection equation). 69 Note: in some cases of advection, i.e. momentum advection, 70 the advectee can act upon the advector. 71 See `PyMPDATA_examples.Jarecka_et_al_2015` for an example of this. 72 """ 73 74 def __init__( 75 self, 76 stepper: Stepper, 77 advectee: [ScalarField, Iterable[ScalarField], Mapping[Hashable, ScalarField]], 78 advector: VectorField, 79 g_factor: [ScalarField, None] = None, 80 ): 81 self.advectee = advectee 82 n_dims = advector.n_dims 83 if isinstance(advectee, ScalarField): 84 self.__fields = {"advectee": (advectee,)} 85 86 elif isinstance(advectee, Mapping): 87 self.__fields = {"advectee": tuple(advectee.values())} 88 89 elif isinstance(advectee, Iterable): 90 self.__fields = {"advectee": tuple(advectee)} 91 92 def scalar_field(dtype=None): 93 return ScalarField.clone(self.__fields["advectee"][0], dtype=dtype) 94 95 def null_scalar_field(): 96 return ScalarField.make_null(n_dims, stepper.traversals) 97 98 def vector_field(): 99 return VectorField.clone(advector) 100 101 def null_vector_field(): 102 return VectorField.make_null(n_dims, stepper.traversals) 103 104 for field in [advector, *self.__fields["advectee"]] + ( 105 [g_factor] if g_factor is not None else [] 106 ): 107 assert field.halo == stepper.options.n_halo 108 assert field.dtype == stepper.options.dtype 109 assert field.grid == advector.grid 110 111 self.__fields.update( 112 { 113 "advector": advector, 114 "g_factor": g_factor or null_scalar_field(), 115 "vectmp_a": vector_field(), 116 "vectmp_b": vector_field(), 117 "vectmp_c": ( 118 vector_field() 119 if stepper.options.non_zero_mu_coeff 120 else null_vector_field() 121 ), 122 "dynamic_advector_stash_outer": ( 123 scalar_field() 124 if stepper.options.dynamic_advector and n_dims > 1 125 else null_scalar_field() 126 ), 127 "dynamic_advector_stash_mid3d": ( 128 scalar_field() 129 if stepper.options.dynamic_advector and n_dims > 2 130 else null_scalar_field() 131 ), 132 "dynamic_advector_stash_inner": ( 133 scalar_field() 134 if stepper.options.dynamic_advector 135 else null_scalar_field() 136 ), 137 "nonosc_xtrm": ( 138 scalar_field(dtype=complex) 139 if stepper.options.nonoscillatory 140 else null_scalar_field() 141 ), 142 "nonosc_beta": ( 143 scalar_field(dtype=complex) 144 if stepper.options.nonoscillatory 145 else null_scalar_field() 146 ), 147 } 148 ) 149 for key, value in self.__fields.items(): 150 for field in (value,) if key != "advectee" else value: 151 field.assemble(stepper.traversals) 152 153 self.__stepper = stepper 154 155 @property 156 def advector(self) -> VectorField: 157 """advector vector field , dynamic_advector_stash_outer, 158 dynamic_advector_stash_mid3d, dynamic_advector_stash_inner(with halo), 159 unmodified by advance(), may be modified from user code""" 160 return self.__fields["advector"] 161 162 @property 163 def g_factor(self) -> ScalarField: 164 """G_factor field (with halo), unmodified by advance(), assumed to be constant-in-time. 165 Can be used as a Jacobian for coordinate transformations, 166 e.g. into spherical coordinates. 167 For this type of usage, see 168 `PyMPDATA_examples.Williamson_and_Rasch_1989_as_in_Jaruga_et_al_2015_Fig_14`. 169 Can also be used to account for spatial variability of fluid density, see 170 `PyMPDATA_examples.Shipway_and_Hill_2012`. 171 e.g. the changing density of a fluid.""" 172 return self.__fields["g_factor"] 173 174 def advance( 175 self, 176 n_steps: int, 177 *, 178 mu_coeff: Union[tuple, None] = None, 179 ante_step=None, 180 post_step=None, 181 post_iter=None, 182 ): 183 """advances solution by `n_steps` steps, optionally accepts: a tuple of diffusion 184 coefficients (one value per dimension) as well as `post_iter` and `post_step` 185 callbacks expected to be `numba.jitclass`es with a `call` method, for 186 signature see `PostStepNull` and `PostIterNull`; 187 returns CPU time per timestep (units as returned by `clock.clock()`)""" 188 if mu_coeff is not None: 189 assert self.__stepper.options.non_zero_mu_coeff 190 else: 191 mu_coeff = (0.0, 0.0, 0.0) 192 if ( 193 self.__stepper.options.non_zero_mu_coeff 194 and not self.__fields["g_factor"].meta[META_IS_NULL] 195 ): 196 raise NotImplementedError() 197 198 ante_step = ante_step or AnteStepNull() 199 post_step = post_step or PostStepNull() 200 post_iter = post_iter or PostIterNull() 201 202 return self.__stepper( 203 n_steps=n_steps, 204 mu_coeff=mu_coeff, 205 ante_step=ante_step, 206 post_step=post_step, 207 post_iter=post_iter, 208 fields=self.__fields, 209 )
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.
74 def __init__( 75 self, 76 stepper: Stepper, 77 advectee: [ScalarField, Iterable[ScalarField], Mapping[Hashable, ScalarField]], 78 advector: VectorField, 79 g_factor: [ScalarField, None] = None, 80 ): 81 self.advectee = advectee 82 n_dims = advector.n_dims 83 if isinstance(advectee, ScalarField): 84 self.__fields = {"advectee": (advectee,)} 85 86 elif isinstance(advectee, Mapping): 87 self.__fields = {"advectee": tuple(advectee.values())} 88 89 elif isinstance(advectee, Iterable): 90 self.__fields = {"advectee": tuple(advectee)} 91 92 def scalar_field(dtype=None): 93 return ScalarField.clone(self.__fields["advectee"][0], dtype=dtype) 94 95 def null_scalar_field(): 96 return ScalarField.make_null(n_dims, stepper.traversals) 97 98 def vector_field(): 99 return VectorField.clone(advector) 100 101 def null_vector_field(): 102 return VectorField.make_null(n_dims, stepper.traversals) 103 104 for field in [advector, *self.__fields["advectee"]] + ( 105 [g_factor] if g_factor is not None else [] 106 ): 107 assert field.halo == stepper.options.n_halo 108 assert field.dtype == stepper.options.dtype 109 assert field.grid == advector.grid 110 111 self.__fields.update( 112 { 113 "advector": advector, 114 "g_factor": g_factor or null_scalar_field(), 115 "vectmp_a": vector_field(), 116 "vectmp_b": vector_field(), 117 "vectmp_c": ( 118 vector_field() 119 if stepper.options.non_zero_mu_coeff 120 else null_vector_field() 121 ), 122 "dynamic_advector_stash_outer": ( 123 scalar_field() 124 if stepper.options.dynamic_advector and n_dims > 1 125 else null_scalar_field() 126 ), 127 "dynamic_advector_stash_mid3d": ( 128 scalar_field() 129 if stepper.options.dynamic_advector and n_dims > 2 130 else null_scalar_field() 131 ), 132 "dynamic_advector_stash_inner": ( 133 scalar_field() 134 if stepper.options.dynamic_advector 135 else null_scalar_field() 136 ), 137 "nonosc_xtrm": ( 138 scalar_field(dtype=complex) 139 if stepper.options.nonoscillatory 140 else null_scalar_field() 141 ), 142 "nonosc_beta": ( 143 scalar_field(dtype=complex) 144 if stepper.options.nonoscillatory 145 else null_scalar_field() 146 ), 147 } 148 ) 149 for key, value in self.__fields.items(): 150 for field in (value,) if key != "advectee" else value: 151 field.assemble(stepper.traversals) 152 153 self.__stepper = stepper
155 @property 156 def advector(self) -> VectorField: 157 """advector vector field , dynamic_advector_stash_outer, 158 dynamic_advector_stash_mid3d, dynamic_advector_stash_inner(with halo), 159 unmodified by advance(), may be modified from user code""" 160 return self.__fields["advector"]
advector vector field , dynamic_advector_stash_outer, dynamic_advector_stash_mid3d, dynamic_advector_stash_inner(with halo), unmodified by advance(), may be modified from user code
162 @property 163 def g_factor(self) -> ScalarField: 164 """G_factor field (with halo), unmodified by advance(), assumed to be constant-in-time. 165 Can be used as a Jacobian for coordinate transformations, 166 e.g. into spherical coordinates. 167 For this type of usage, see 168 `PyMPDATA_examples.Williamson_and_Rasch_1989_as_in_Jaruga_et_al_2015_Fig_14`. 169 Can also be used to account for spatial variability of fluid density, see 170 `PyMPDATA_examples.Shipway_and_Hill_2012`. 171 e.g. the changing density of a fluid.""" 172 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.
174 def advance( 175 self, 176 n_steps: int, 177 *, 178 mu_coeff: Union[tuple, None] = None, 179 ante_step=None, 180 post_step=None, 181 post_iter=None, 182 ): 183 """advances solution by `n_steps` steps, optionally accepts: a tuple of diffusion 184 coefficients (one value per dimension) as well as `post_iter` and `post_step` 185 callbacks expected to be `numba.jitclass`es with a `call` method, for 186 signature see `PostStepNull` and `PostIterNull`; 187 returns CPU time per timestep (units as returned by `clock.clock()`)""" 188 if mu_coeff is not None: 189 assert self.__stepper.options.non_zero_mu_coeff 190 else: 191 mu_coeff = (0.0, 0.0, 0.0) 192 if ( 193 self.__stepper.options.non_zero_mu_coeff 194 and not self.__fields["g_factor"].meta[META_IS_NULL] 195 ): 196 raise NotImplementedError() 197 198 ante_step = ante_step or AnteStepNull() 199 post_step = post_step or PostStepNull() 200 post_iter = post_iter or PostIterNull() 201 202 return self.__stepper( 203 n_steps=n_steps, 204 mu_coeff=mu_coeff, 205 ante_step=ante_step, 206 post_step=post_step, 207 post_iter=post_iter, 208 fields=self.__fields, 209 )
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())