Skip to content

Commit 561d492

Browse files
tluettmslayoo
authored andcommitted
Homogeneous freezing (as a new option in Freezing dynamic, disabled by default + new physics formulae for hom. nucl. rate); new example: Spichtinger_et_al_2023 (open-atmos#1488)
Co-authored-by: Sylwester Arabas <[email protected]>
1 parent 6813de9 commit 561d492

File tree

26 files changed

+2885
-61
lines changed

26 files changed

+2885
-61
lines changed

PySDM/backends/impl_common/freezing_attributes.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,14 @@ class TimeDependentAttributes(
2525
"""groups attributes required in time-dependent regime"""
2626

2727
__slots__ = ()
28+
29+
30+
class TimeDependentHomogeneousAttributes(
31+
namedtuple(
32+
typename="TimeDependentHomogeneousAttributes",
33+
field_names=("volume", "signed_water_mass"),
34+
)
35+
):
36+
"""groups attributes required in time-dependent regime for homogeneous freezing"""
37+
38+
__slots__ = ()

PySDM/backends/impl_numba/methods/freezing_methods.py

Lines changed: 123 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""
2-
CPU implementation of backend methods for freezing (singular and time-dependent immersion freezing)
2+
CPU implementation of backend methods for homogeneous freezing and
3+
heterogeneous freezing (singular and time-dependent immersion freezing)
34
"""
45

56
from functools import cached_property
@@ -12,31 +13,40 @@
1213
from ...impl_common.freezing_attributes import (
1314
SingularAttributes,
1415
TimeDependentAttributes,
16+
TimeDependentHomogeneousAttributes,
1517
)
1618

1719

1820
class FreezingMethods(BackendMethods):
19-
def __init__(self):
20-
BackendMethods.__init__(self)
21-
unfrozen_and_saturated = self.formulae.trivia.unfrozen_and_saturated
22-
frozen_and_above_freezing_point = (
23-
self.formulae.trivia.frozen_and_above_freezing_point
24-
)
25-
26-
@numba.njit(**{**self.default_jit_flags, "parallel": False})
27-
def _freeze(water_mass, i):
28-
water_mass[i] = -1 * water_mass[i]
21+
@cached_property
22+
def _freeze(self):
23+
@numba.njit(**{**self.default_jit_flags, **{"parallel": False}})
24+
def body(signed_water_mass, i):
25+
signed_water_mass[i] = -1 * signed_water_mass[i]
2926
# TODO #599: change thd (latent heat)!
3027

31-
@numba.njit(**{**self.default_jit_flags, "parallel": False})
32-
def _thaw(water_mass, i):
33-
water_mass[i] = -1 * water_mass[i]
28+
return body
29+
30+
@cached_property
31+
def _thaw(self):
32+
@numba.njit(**{**self.default_jit_flags, **{"parallel": False}})
33+
def body(signed_water_mass, i):
34+
signed_water_mass[i] = -1 * signed_water_mass[i]
3435
# TODO #599: change thd (latent heat)!
3536

37+
return body
38+
39+
@cached_property
40+
def _freeze_singular_body(self):
41+
_thaw = self._thaw
42+
_freeze = self._freeze
43+
frozen_and_above_freezing_point = (
44+
self.formulae.trivia.frozen_and_above_freezing_point
45+
)
46+
unfrozen_and_saturated = self.formulae.trivia.unfrozen_and_saturated
47+
3648
@numba.njit(**self.default_jit_flags)
37-
def freeze_singular_body(
38-
attributes, temperature, relative_humidity, cell, thaw
39-
):
49+
def body(attributes, temperature, relative_humidity, cell, thaw):
4050
n_sd = len(attributes.freezing_temperature)
4151
for i in numba.prange(n_sd): # pylint: disable=not-an-iterable
4252
if attributes.freezing_temperature[i] == 0:
@@ -53,13 +63,21 @@ def freeze_singular_body(
5363
):
5464
_freeze(attributes.signed_water_mass, i)
5565

56-
self.freeze_singular_body = freeze_singular_body
66+
return body
5767

68+
@cached_property
69+
def _freeze_time_dependent_body(self):
70+
_thaw = self._thaw
71+
_freeze = self._freeze
72+
frozen_and_above_freezing_point = (
73+
self.formulae.trivia.frozen_and_above_freezing_point
74+
)
75+
unfrozen_and_saturated = self.formulae.trivia.unfrozen_and_saturated
5876
j_het = self.formulae.heterogeneous_ice_nucleation_rate.j_het
5977
prob_zero_events = self.formulae.trivia.poissonian_avoidance_function
6078

6179
@numba.njit(**self.default_jit_flags)
62-
def freeze_time_dependent_body( # pylint: disable=too-many-arguments
80+
def body( # pylint: disable=too-many-arguments
6381
rand,
6482
attributes,
6583
timestep,
@@ -90,12 +108,69 @@ def freeze_time_dependent_body( # pylint: disable=too-many-arguments
90108
if rand[i] < prob:
91109
_freeze(attributes.signed_water_mass, i)
92110

93-
self.freeze_time_dependent_body = freeze_time_dependent_body
111+
return body
112+
113+
@cached_property
114+
def _freeze_time_dependent_homogeneous_body(self):
115+
_thaw = self._thaw
116+
_freeze = self._freeze
117+
frozen_and_above_freezing_point = (
118+
self.formulae.trivia.frozen_and_above_freezing_point
119+
)
120+
unfrozen_and_ice_saturated = self.formulae.trivia.unfrozen_and_ice_saturated
121+
j_hom = self.formulae.homogeneous_ice_nucleation_rate.j_hom
122+
prob_zero_events = self.formulae.trivia.poissonian_avoidance_function
123+
d_a_w_ice_within_range = (
124+
self.formulae.homogeneous_ice_nucleation_rate.d_a_w_ice_within_range
125+
)
126+
d_a_w_ice_maximum = (
127+
self.formulae.homogeneous_ice_nucleation_rate.d_a_w_ice_maximum
128+
)
129+
130+
@numba.njit(**self.default_jit_flags)
131+
def body( # pylint: disable=unused-argument,too-many-arguments
132+
rand,
133+
attributes,
134+
timestep,
135+
cell,
136+
a_w_ice,
137+
temperature,
138+
relative_humidity_ice,
139+
thaw,
140+
):
141+
142+
n_sd = len(attributes.signed_water_mass)
143+
for i in numba.prange(n_sd): # pylint: disable=not-an-iterable
144+
cell_id = cell[i]
145+
if thaw and frozen_and_above_freezing_point(
146+
attributes.signed_water_mass[i], temperature[cell_id]
147+
):
148+
_thaw(attributes.signed_water_mass, i)
149+
elif unfrozen_and_ice_saturated(
150+
attributes.signed_water_mass[i], relative_humidity_ice[cell_id]
151+
):
152+
d_a_w_ice = (relative_humidity_ice[cell_id] - 1.0) * a_w_ice[
153+
cell_id
154+
]
155+
156+
if d_a_w_ice_within_range(d_a_w_ice):
157+
d_a_w_ice = d_a_w_ice_maximum(d_a_w_ice)
158+
rate_assuming_constant_temperature_within_dt = (
159+
j_hom(temperature[cell_id], d_a_w_ice)
160+
* attributes.volume[i]
161+
)
162+
prob = 1 - prob_zero_events(
163+
r=rate_assuming_constant_temperature_within_dt, dt=timestep
164+
)
165+
if rand[i] < prob:
166+
_freeze(attributes.signed_water_mass, i)
167+
168+
return body
94169

95170
def freeze_singular(
96171
self, *, attributes, temperature, relative_humidity, cell, thaw: bool
97172
):
98-
self.freeze_singular_body(
173+
self._freeze_singular_body(
99174
SingularAttributes(
100175
freezing_temperature=attributes.freezing_temperature.data,
101176
signed_water_mass=attributes.signed_water_mass.data,
@@ -118,7 +193,7 @@ def freeze_time_dependent(
118193
relative_humidity,
119194
thaw: bool,
120195
):
121-
self.freeze_time_dependent_body(
196+
self._freeze_time_dependent_body(
122197
rand.data,
123198
TimeDependentAttributes(
124199
immersed_surface_area=attributes.immersed_surface_area.data,
@@ -132,6 +207,32 @@ def freeze_time_dependent(
132207
thaw=thaw,
133208
)
134209

210+
def freeze_time_dependent_homogeneous(
211+
self,
212+
*,
213+
rand,
214+
attributes,
215+
timestep,
216+
cell,
217+
a_w_ice,
218+
temperature,
219+
relative_humidity_ice,
220+
thaw: bool,
221+
):
222+
self._freeze_time_dependent_homogeneous_body(
223+
rand.data,
224+
TimeDependentHomogeneousAttributes(
225+
volume=attributes.volume.data,
226+
signed_water_mass=attributes.signed_water_mass.data,
227+
),
228+
timestep,
229+
cell.data,
230+
a_w_ice.data,
231+
temperature.data,
232+
relative_humidity_ice.data,
233+
thaw=thaw,
234+
)
235+
135236
@cached_property
136237
def _record_freezing_temperatures_body(self):
137238
ff = self.formulae_flattened

PySDM/dynamics/freezing.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
"""
2-
immersion freezing using either singular or time-dependent formulation
2+
droplet freezing using either singular or
3+
time-dependent formulation for immersion freezing
4+
and homogeneous freezing and thaw
35
"""
46

57
from PySDM.dynamics.impl import register_dynamic
68

79

810
@register_dynamic()
9-
class Freezing:
10-
def __init__(self, *, singular=True, thaw=False):
11+
class Freezing: # pylint: disable=too-many-instance-attributes
12+
def __init__(
13+
self,
14+
*,
15+
singular=True,
16+
homogeneous_freezing=False,
17+
immersion_freezing=True,
18+
thaw=False,
19+
):
1120
self.singular = singular
21+
self.homogeneous_freezing = homogeneous_freezing
22+
self.immersion_freezing = immersion_freezing
1223
self.thaw = thaw
1324
self.enable = True
1425
self.rand = None
@@ -26,12 +37,21 @@ def register(self, builder):
2637
if self.singular:
2738
builder.request_attribute("freezing temperature")
2839

29-
if not self.singular:
40+
if not self.singular and self.immersion_freezing:
3041
assert (
3142
self.particulator.formulae.heterogeneous_ice_nucleation_rate.__name__
3243
!= "Null"
3344
)
3445
builder.request_attribute("immersed surface area")
46+
47+
if self.homogeneous_freezing:
48+
assert (
49+
self.particulator.formulae.homogeneous_ice_nucleation_rate.__name__
50+
!= "Null"
51+
)
52+
builder.request_attribute("volume")
53+
54+
if self.homogeneous_freezing or not self.singular:
3555
self.rand = self.particulator.Storage.empty(
3656
self.particulator.n_sd, dtype=float
3757
)
@@ -49,11 +69,19 @@ def __call__(self):
4969
if not self.enable:
5070
return
5171

52-
if self.singular:
53-
self.particulator.immersion_freezing_singular(thaw=self.thaw)
54-
else:
72+
if self.immersion_freezing:
73+
if self.singular:
74+
self.particulator.immersion_freezing_singular(thaw=self.thaw)
75+
else:
76+
self.rand.urand(self.rng)
77+
self.particulator.immersion_freezing_time_dependent(
78+
rand=self.rand,
79+
thaw=self.thaw,
80+
)
81+
82+
if self.homogeneous_freezing:
5583
self.rand.urand(self.rng)
56-
self.particulator.immersion_freezing_time_dependent(
84+
self.particulator.homogeneous_freezing_time_dependent(
5785
rand=self.rand,
5886
thaw=self.thaw,
5987
)

PySDM/formulae.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __init__( # pylint: disable=too-many-locals
4747
hydrostatics: str = "ConstantGVapourMixingRatioAndThetaStd",
4848
freezing_temperature_spectrum: str = "Null",
4949
heterogeneous_ice_nucleation_rate: str = "Null",
50+
homogeneous_ice_nucleation_rate: str = "Null",
5051
fragmentation_function: str = "AlwaysN",
5152
isotope_equilibrium_fractionation_factors: str = "Null",
5253
isotope_kinetic_fractionation_factors: str = "Null",
@@ -86,6 +87,7 @@ def __init__( # pylint: disable=too-many-locals
8687
self.hydrostatics = hydrostatics
8788
self.freezing_temperature_spectrum = freezing_temperature_spectrum
8889
self.heterogeneous_ice_nucleation_rate = heterogeneous_ice_nucleation_rate
90+
self.homogeneous_ice_nucleation_rate = homogeneous_ice_nucleation_rate
8991
self.fragmentation_function = fragmentation_function
9092
self.isotope_equilibrium_fractionation_factors = (
9193
isotope_equilibrium_fractionation_factors

PySDM/particulator.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from PySDM.backends.impl_common.freezing_attributes import (
99
SingularAttributes,
1010
TimeDependentAttributes,
11+
TimeDependentHomogeneousAttributes,
1112
)
1213
from PySDM.backends.impl_common.index import make_Index
1314
from PySDM.backends.impl_common.indexed_storage import make_IndexedStorage
@@ -537,3 +538,18 @@ def immersion_freezing_singular(self, *, thaw: bool):
537538
thaw=thaw,
538539
)
539540
self.attributes.mark_updated("signed water mass")
541+
542+
def homogeneous_freezing_time_dependent(self, *, thaw: bool, rand: Storage):
543+
self.backend.freeze_time_dependent_homogeneous(
544+
rand=rand,
545+
attributes=TimeDependentHomogeneousAttributes(
546+
volume=self.attributes["volume"],
547+
signed_water_mass=self.attributes["signed water mass"],
548+
),
549+
timestep=self.dt,
550+
cell=self.attributes["cell id"],
551+
a_w_ice=self.environment["a_w_ice"],
552+
temperature=self.environment["T"],
553+
relative_humidity_ice=self.environment["RH_ice"],
554+
thaw=thaw,
555+
)

PySDM/physics/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
fragmentation_function,
2828
freezing_temperature_spectrum,
2929
heterogeneous_ice_nucleation_rate,
30+
homogeneous_ice_nucleation_rate,
3031
hydrostatics,
3132
hygroscopicity,
3233
impl,

0 commit comments

Comments
 (0)