PySDM.dynamics.condensation
bespoke condensational growth solver with implicit-in-particle-size integration and adaptive timestepping
1""" 2bespoke condensational growth solver 3with implicit-in-particle-size integration and adaptive timestepping 4""" 5 6from collections import namedtuple 7 8import numpy as np 9 10from PySDM.physics import si 11from PySDM.dynamics.impl import register_dynamic 12 13DEFAULTS = namedtuple("_", ("rtol_x", "rtol_thd", "cond_range", "schedule"))( 14 rtol_x=1e-6, 15 rtol_thd=1e-6, 16 cond_range=(1e-4 * si.second, 1 * si.second), 17 schedule="dynamic", 18) 19 20 21@register_dynamic() 22class Condensation: # pylint: disable=too-many-instance-attributes 23 def __init__( 24 self, 25 *, 26 rtol_x=DEFAULTS.rtol_x, 27 rtol_thd=DEFAULTS.rtol_thd, 28 substeps: int = 1, 29 adaptive: bool = True, 30 dt_cond_range: tuple = DEFAULTS.cond_range, 31 schedule: str = DEFAULTS.schedule, 32 max_iters: int = 16, 33 update_thd: bool = True, 34 ): 35 if adaptive and substeps != 1: 36 raise ValueError( 37 "if specifying substeps count manually, adaptivity must be disabled" 38 ) 39 40 self.particulator = None 41 self.enable = True 42 43 self.rtol_x = rtol_x 44 self.rtol_thd = rtol_thd 45 46 self.rh_max = None 47 self.success = None 48 49 self.__substeps = substeps 50 self.adaptive = adaptive 51 self.counters = {} 52 self.dt_cond_range = dt_cond_range 53 self.schedule = schedule 54 self.max_iters = max_iters 55 56 self.cell_order = None 57 58 self.update_thd = update_thd 59 60 def register(self, builder): 61 self.particulator = builder.particulator 62 63 builder._set_condensation_parameters( 64 dt_range=self.dt_cond_range, 65 adaptive=self.adaptive, 66 fuse=32, 67 multiplier=2, 68 RH_rtol=1e-7, 69 max_iters=self.max_iters, 70 ) 71 builder.request_attribute("critical volume") 72 builder.request_attribute("kappa") 73 builder.request_attribute("dry volume organic fraction") 74 builder.request_attribute("Reynolds number") 75 76 for counter in ("n_substeps", "n_activating", "n_deactivating", "n_ripening"): 77 self.counters[counter] = self.particulator.Storage.empty( 78 self.particulator.mesh.n_cell, dtype=int 79 ) 80 if counter == "n_substeps": 81 self.counters[counter][:] = self.__substeps if not self.adaptive else -1 82 else: 83 self.counters[counter][:] = -1 84 85 self.rh_max = self.particulator.Storage.empty( 86 self.particulator.mesh.n_cell, dtype=float 87 ) 88 self.rh_max[:] = np.nan 89 self.success = self.particulator.Storage.empty( 90 self.particulator.mesh.n_cell, dtype=bool 91 ) 92 self.success[:] = False 93 self.cell_order = np.arange(self.particulator.mesh.n_cell) 94 95 def __call__(self): 96 if self.enable: 97 if self.schedule == "dynamic": 98 self.cell_order = np.argsort(self.counters["n_substeps"]) 99 elif self.schedule == "static": 100 pass 101 else: 102 raise NotImplementedError() 103 104 self.particulator.condensation( 105 rtol_x=self.rtol_x, 106 rtol_thd=self.rtol_thd, 107 counters=self.counters, 108 RH_max=self.rh_max, 109 success=self.success, 110 cell_order=self.cell_order, 111 ) 112 if not self.success.all(): 113 raise RuntimeError("Condensation failed") 114 if not self.update_thd: 115 self.particulator.environment.get_predicted("thd").ravel( 116 self.particulator.environment.get_thd() 117 ) 118 # note: this makes order of dynamics matter 119 # (e.g., condensation after chemistry or before) 120 self.particulator.update_TpRH() 121 122 if self.adaptive: 123 self.counters["n_substeps"][:] = np.maximum( 124 self.counters["n_substeps"][:], 125 int(self.particulator.dt / self.dt_cond_range[1]), 126 ) 127 if self.dt_cond_range[0] != 0: 128 self.counters["n_substeps"][:] = np.minimum( 129 self.counters["n_substeps"][:], 130 int(self.particulator.dt / self.dt_cond_range[0]), 131 )
DEFAULTS =
_(rtol_x=1e-06, rtol_thd=1e-06, cond_range=(0.0001, 1.0), schedule='dynamic')
@register_dynamic()
class
Condensation:
22@register_dynamic() 23class Condensation: # pylint: disable=too-many-instance-attributes 24 def __init__( 25 self, 26 *, 27 rtol_x=DEFAULTS.rtol_x, 28 rtol_thd=DEFAULTS.rtol_thd, 29 substeps: int = 1, 30 adaptive: bool = True, 31 dt_cond_range: tuple = DEFAULTS.cond_range, 32 schedule: str = DEFAULTS.schedule, 33 max_iters: int = 16, 34 update_thd: bool = True, 35 ): 36 if adaptive and substeps != 1: 37 raise ValueError( 38 "if specifying substeps count manually, adaptivity must be disabled" 39 ) 40 41 self.particulator = None 42 self.enable = True 43 44 self.rtol_x = rtol_x 45 self.rtol_thd = rtol_thd 46 47 self.rh_max = None 48 self.success = None 49 50 self.__substeps = substeps 51 self.adaptive = adaptive 52 self.counters = {} 53 self.dt_cond_range = dt_cond_range 54 self.schedule = schedule 55 self.max_iters = max_iters 56 57 self.cell_order = None 58 59 self.update_thd = update_thd 60 61 def register(self, builder): 62 self.particulator = builder.particulator 63 64 builder._set_condensation_parameters( 65 dt_range=self.dt_cond_range, 66 adaptive=self.adaptive, 67 fuse=32, 68 multiplier=2, 69 RH_rtol=1e-7, 70 max_iters=self.max_iters, 71 ) 72 builder.request_attribute("critical volume") 73 builder.request_attribute("kappa") 74 builder.request_attribute("dry volume organic fraction") 75 builder.request_attribute("Reynolds number") 76 77 for counter in ("n_substeps", "n_activating", "n_deactivating", "n_ripening"): 78 self.counters[counter] = self.particulator.Storage.empty( 79 self.particulator.mesh.n_cell, dtype=int 80 ) 81 if counter == "n_substeps": 82 self.counters[counter][:] = self.__substeps if not self.adaptive else -1 83 else: 84 self.counters[counter][:] = -1 85 86 self.rh_max = self.particulator.Storage.empty( 87 self.particulator.mesh.n_cell, dtype=float 88 ) 89 self.rh_max[:] = np.nan 90 self.success = self.particulator.Storage.empty( 91 self.particulator.mesh.n_cell, dtype=bool 92 ) 93 self.success[:] = False 94 self.cell_order = np.arange(self.particulator.mesh.n_cell) 95 96 def __call__(self): 97 if self.enable: 98 if self.schedule == "dynamic": 99 self.cell_order = np.argsort(self.counters["n_substeps"]) 100 elif self.schedule == "static": 101 pass 102 else: 103 raise NotImplementedError() 104 105 self.particulator.condensation( 106 rtol_x=self.rtol_x, 107 rtol_thd=self.rtol_thd, 108 counters=self.counters, 109 RH_max=self.rh_max, 110 success=self.success, 111 cell_order=self.cell_order, 112 ) 113 if not self.success.all(): 114 raise RuntimeError("Condensation failed") 115 if not self.update_thd: 116 self.particulator.environment.get_predicted("thd").ravel( 117 self.particulator.environment.get_thd() 118 ) 119 # note: this makes order of dynamics matter 120 # (e.g., condensation after chemistry or before) 121 self.particulator.update_TpRH() 122 123 if self.adaptive: 124 self.counters["n_substeps"][:] = np.maximum( 125 self.counters["n_substeps"][:], 126 int(self.particulator.dt / self.dt_cond_range[1]), 127 ) 128 if self.dt_cond_range[0] != 0: 129 self.counters["n_substeps"][:] = np.minimum( 130 self.counters["n_substeps"][:], 131 int(self.particulator.dt / self.dt_cond_range[0]), 132 )
Condensation( *, rtol_x=1e-06, rtol_thd=1e-06, substeps: int = 1, adaptive: bool = True, dt_cond_range: tuple = (0.0001, 1.0), schedule: str = 'dynamic', max_iters: int = 16, update_thd: bool = True)
24 def __init__( 25 self, 26 *, 27 rtol_x=DEFAULTS.rtol_x, 28 rtol_thd=DEFAULTS.rtol_thd, 29 substeps: int = 1, 30 adaptive: bool = True, 31 dt_cond_range: tuple = DEFAULTS.cond_range, 32 schedule: str = DEFAULTS.schedule, 33 max_iters: int = 16, 34 update_thd: bool = True, 35 ): 36 if adaptive and substeps != 1: 37 raise ValueError( 38 "if specifying substeps count manually, adaptivity must be disabled" 39 ) 40 41 self.particulator = None 42 self.enable = True 43 44 self.rtol_x = rtol_x 45 self.rtol_thd = rtol_thd 46 47 self.rh_max = None 48 self.success = None 49 50 self.__substeps = substeps 51 self.adaptive = adaptive 52 self.counters = {} 53 self.dt_cond_range = dt_cond_range 54 self.schedule = schedule 55 self.max_iters = max_iters 56 57 self.cell_order = None 58 59 self.update_thd = update_thd
def
register(self, builder):
61 def register(self, builder): 62 self.particulator = builder.particulator 63 64 builder._set_condensation_parameters( 65 dt_range=self.dt_cond_range, 66 adaptive=self.adaptive, 67 fuse=32, 68 multiplier=2, 69 RH_rtol=1e-7, 70 max_iters=self.max_iters, 71 ) 72 builder.request_attribute("critical volume") 73 builder.request_attribute("kappa") 74 builder.request_attribute("dry volume organic fraction") 75 builder.request_attribute("Reynolds number") 76 77 for counter in ("n_substeps", "n_activating", "n_deactivating", "n_ripening"): 78 self.counters[counter] = self.particulator.Storage.empty( 79 self.particulator.mesh.n_cell, dtype=int 80 ) 81 if counter == "n_substeps": 82 self.counters[counter][:] = self.__substeps if not self.adaptive else -1 83 else: 84 self.counters[counter][:] = -1 85 86 self.rh_max = self.particulator.Storage.empty( 87 self.particulator.mesh.n_cell, dtype=float 88 ) 89 self.rh_max[:] = np.nan 90 self.success = self.particulator.Storage.empty( 91 self.particulator.mesh.n_cell, dtype=bool 92 ) 93 self.success[:] = False 94 self.cell_order = np.arange(self.particulator.mesh.n_cell)