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
particulator
enable
rtol_x
rtol_thd
rh_max
success
adaptive
counters
dt_cond_range
schedule
max_iters
cell_order
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)
def instantiate(self, *, builder):
 8def _instantiate(self, *, builder):
 9    copy = deepcopy(self)
10    copy.register(builder=builder)
11    return copy