PySDM.dynamics.condensation

bespoke condensational growth solver with implicit-in-particle-size integration and adaptive timestepping as in Bartman 2020 (MSc thesis, Section 3.3)

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