Skip to content

Commit 4722d86

Browse files
authored
Merge pull request #616 from slayoo/freezing
adding smoke test for Alpert & Knopf 2016 Fig 1
2 parents 454a352 + a2194d3 commit 4722d86

File tree

7 files changed

+236
-1
lines changed

7 files changed

+236
-1
lines changed

PySDM/physics/constants.py

+10
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ def convert_to(value, unit):
6666
FWC_C7 = 0.379534310e-13 * si.hPa / si.K**7
6767
FWC_C8 = -.321582393e-15 * si.hPa / si.K**8
6868

69+
FWC_I0 = 6.098689930e000 * si.hPa
70+
FWC_I1 = 0.499320233e000 * si.hPa / si.K
71+
FWC_I2 = 0.184672631e-01 * si.hPa / si.K**2
72+
FWC_I3 = 0.402737184e-03 * si.hPa / si.K**3
73+
FWC_I4 = 0.565392987e-05 * si.hPa / si.K**4
74+
FWC_I5 = 0.521693933e-07 * si.hPa / si.K**5
75+
FWC_I6 = 0.307839583e-09 * si.hPa / si.K**6
76+
FWC_I7 = 0.105785160e-11 * si.hPa / si.K**7
77+
FWC_I8 = 0.161444444e-14 * si.hPa / si.K**8
78+
6979
rho_w = 1 * si.kilograms / si.litres
7080
rho_i = 916.8 * si.kg / si.metres**3
7181
pH_w = 7

PySDM/physics/saturation_vapour_pressure/flatau_walko_cotton.py

+14
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,17 @@ def pvs_Celsius(T):
1515
const.FWC_C7 + T *
1616
const.FWC_C8
1717
))))))))
18+
19+
@staticmethod
20+
def ice_Celsius(T):
21+
return (
22+
const.FWC_I0 + T * (
23+
const.FWC_I1 + T * (
24+
const.FWC_I2 + T * (
25+
const.FWC_I3 + T * (
26+
const.FWC_I4 + T * (
27+
const.FWC_I5 + T * (
28+
const.FWC_I6 + T * (
29+
const.FWC_I7 + T *
30+
const.FWC_I8
31+
))))))))

PySDM/physics/spectra.py

+17
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,23 @@ def __init__(self, norm_factor: float, m_mode: float, s_geom: float):
5353
def s_geom(self):
5454
return math.exp(self.distribution_params[0])
5555

56+
@property
57+
def m_mode(self):
58+
return self.distribution_params[2]
59+
60+
class TopHat:
61+
def __init__(self, norm_factor, endpoints):
62+
self.norm_factor = norm_factor
63+
self.endpoints = endpoints
64+
self._mn = endpoints[0]
65+
self._mx = endpoints[1]
66+
67+
def cumulative(self, x):
68+
return self.norm_factor * np.minimum(1, np.maximum(0, (x - self._mn) / (self._mx - self._mn)))
69+
70+
def percentiles(self, cdf_values):
71+
return (self._mx - self._mn) * (np.asarray(cdf_values) + self._mn / (self._mx - self._mn))
72+
5673
class Sum:
5774

5875
def __init__(self, spectra: tuple, interpolation_grid=default_interpolation_grid):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from PySDM_examples.Alpert_and_Knopf_2016 import simulation, Table1
2+
from PySDM.physics import si, constants as const, Formulae
3+
from PySDM.physics.spectra import Lognormal
4+
import numpy as np
5+
from matplotlib import pylab
6+
import pytest
7+
8+
n_runs_per_case = 3
9+
10+
@pytest.mark.parametrize("multiplicity", (1, 2, 10))
11+
def test_AK16_fig_1(multiplicity, plot=False):
12+
# Arrange
13+
J_het = 1e3 / si.cm ** 2 / si.s
14+
A_g = 1e-5 * si.cm ** 2
15+
16+
dt = 1 * si.s
17+
total_time = 6 * si.min
18+
19+
# dummy multipliers (multiplied and then divided by)
20+
dv = 1 * si.cm ** 3 # will become used if coalescence or other processes are turned on
21+
droplet_volume = 1 * si.um ** 3 # ditto
22+
23+
cases = Table1(dv=dv)
24+
25+
# Act
26+
output = {}
27+
28+
for key in ('Iso3', 'Iso4', 'Iso1', 'Iso2'):
29+
case = cases[key]
30+
output[key] = []
31+
for i in range(n_runs_per_case):
32+
seed = i
33+
number_of_real_droplets = case['ISA'].norm_factor * dv
34+
n_sd = number_of_real_droplets / multiplicity
35+
assert int(n_sd) == n_sd
36+
n_sd = int(n_sd)
37+
38+
data = simulation(seed=i, n_sd=n_sd, dt=dt, dv=dv, spectrum=case['ISA'],
39+
droplet_volume=droplet_volume, multiplicity=multiplicity, J_het=J_het,
40+
total_time=total_time, number_of_real_droplets=number_of_real_droplets)
41+
output[key].append(data)
42+
43+
# Plot
44+
if plot:
45+
for key in output.keys():
46+
for run in range(n_runs_per_case):
47+
label = f"{key}: σ=ln({int(cases[key]['ISA'].s_geom)}),N={int(cases[key]['ISA'].norm_factor * dv)}"
48+
pylab.step(
49+
dt / si.min * np.arange(len(output[key][run])),
50+
output[key][run],
51+
label=label if run == 0 else None,
52+
color=cases[key]['color'],
53+
linewidth=.666
54+
)
55+
output[key].append(np.mean(np.asarray(output[key]), axis=0))
56+
pylab.step(
57+
dt / si.min * np.arange(len(output[key][-1])),
58+
output[key][-1],
59+
color=cases[key]['color'],
60+
linewidth=1.666
61+
)
62+
63+
pylab.legend()
64+
pylab.yscale('log')
65+
pylab.ylim(1e-2, 1)
66+
pylab.xlim(0, total_time / si.min)
67+
pylab.xlabel("t / min")
68+
pylab.ylabel("$f_{ufz}$")
69+
pylab.gca().set_box_aspect(1)
70+
pylab.show()
71+
72+
# Assert
73+
np.testing.assert_array_less(
74+
output['Iso3'][-1][1:int(1 * si.min / dt)],
75+
output['Iso1'][-1][1:int(1 * si.min / dt)]
76+
)
77+
np.testing.assert_array_less(
78+
output['Iso1'][-1][int(2 * si.min / dt):],
79+
output['Iso3'][-1][int(2 * si.min / dt):]
80+
)
81+
np.testing.assert_array_less(
82+
output['Iso2'][int(.5 * si.min / dt):],
83+
output['Iso1'][int(.5 * si.min / dt):]
84+
)
85+
for key in output.keys():
86+
np.testing.assert_array_less(1e-3, output[key][-1][:int(.25 * si.min / dt)])
87+
np.testing.assert_array_less(output[key][-1][:], 1 + 1e-10)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import numpy as np
2+
from matplotlib import pylab
3+
from PySDM.physics import Formulae, constants as const
4+
import inspect
5+
6+
def test_saturation_vapour_pressures(plot=False):
7+
# Arrange
8+
formulae = {
9+
k: Formulae(saturation_vapour_pressure=k)
10+
for k in ('FlatauWalkoCotton', 'AugustRocheMagnus')
11+
}
12+
T = np.linspace(-.2, .4)
13+
14+
# Plot
15+
if plot:
16+
pylab.axhline(const.p_tri, label='triple point', color='red')
17+
pylab.axvline(const.T_tri - const.T0, color='red')
18+
for k, v in formulae.items():
19+
for name, func in inspect.getmembers(v.saturation_vapour_pressure):
20+
if not name.startswith('__'):
21+
pylab.plot(T, func(T), label=f"{k}::{name}")
22+
pylab.grid()
23+
pylab.legend()
24+
pylab.xlabel('T [C]')
25+
pylab.ylabel('p [Pa]')
26+
pylab.show()
27+
28+
# Assert
29+
T = np.linspace(-20, 20, 100)
30+
np.testing.assert_allclose(
31+
Formulae(saturation_vapour_pressure='FlatauWalkoCotton').saturation_vapour_pressure.pvs_Celsius(T),
32+
Formulae(saturation_vapour_pressure='AugustRocheMagnus').saturation_vapour_pressure.pvs_Celsius(T),
33+
rtol=1e-2
34+
)
35+
T = np.linspace(-20, 0.3, 100)
36+
np.testing.assert_array_less(
37+
Formulae(saturation_vapour_pressure='FlatauWalkoCotton').saturation_vapour_pressure.ice_Celsius(T),
38+
Formulae(saturation_vapour_pressure='FlatauWalkoCotton').saturation_vapour_pressure.pvs_Celsius(T)
39+
)
40+
T = np.linspace(0.35, 20, 100)
41+
np.testing.assert_array_less(
42+
Formulae(saturation_vapour_pressure='FlatauWalkoCotton').saturation_vapour_pressure.pvs_Celsius(T),
43+
Formulae(saturation_vapour_pressure='FlatauWalkoCotton').saturation_vapour_pressure.ice_Celsius(T)
44+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from PySDM.physics import spectra
2+
from matplotlib import pylab
3+
import numpy as np
4+
5+
6+
class TestSpectraTopHat:
7+
@staticmethod
8+
def test_cumulative(plot=False):
9+
# arrange
10+
endpoints = (44, 666)
11+
norm_factor = np.pi
12+
spectrum = spectra.TopHat(norm_factor=norm_factor, endpoints=endpoints)
13+
x = np.linspace(0, 1000)
14+
15+
# act
16+
y = spectrum.cumulative(x)
17+
18+
# plot
19+
if plot:
20+
pylab.axhline(0)
21+
pylab.axhline(norm_factor)
22+
pylab.axvline(endpoints[0])
23+
pylab.axvline(endpoints[1])
24+
pylab.plot(x, y, color='red')
25+
pylab.xlim(x[0], x[-1])
26+
pylab.grid()
27+
pylab.show()
28+
29+
# assert
30+
for point in y:
31+
assert 0 <= point <= norm_factor
32+
assert y[-1] == norm_factor
33+
34+
hy, hx = np.histogram(np.diff(y) / np.diff(x))
35+
assert hx[0] == 0
36+
np.testing.assert_approx_equal(hx[-1], norm_factor / (endpoints[1] - endpoints[0]))
37+
assert np.sum(hy[1:-1]) == 2
38+
39+
@staticmethod
40+
def test_percentiles(plot=False):
41+
# arrange
42+
spectrum = spectra.TopHat(norm_factor=np.e, endpoints=(-np.pi, np.pi))
43+
x = np.linspace(0, 1)
44+
45+
# act
46+
y = spectrum.percentiles(x)
47+
48+
# plot
49+
if plot:
50+
pylab.axvline(0)
51+
pylab.axvline(1)
52+
pylab.axhline(spectrum.endpoints[0])
53+
pylab.axhline(spectrum.endpoints[1])
54+
pylab.plot(x, y, color='red')
55+
pylab.show()
56+
57+
# assert
58+
assert y[0] == spectrum.endpoints[0]
59+
assert y[-1] == spectrum.endpoints[1]
60+
np.testing.assert_allclose(
61+
np.diff(y) / np.diff(x),
62+
spectrum.endpoints[1] - spectrum.endpoints[0]
63+
)

test-time-requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ ghapi
44
pytest
55

66
# note: if cloning both PySDM and PySDM examples, consider "pip install -e"
7-
PySDM-examples @ git+git://github.com/atmos-cloud-sim-uj/PySDM-examples@af1663e#egg=PySDM-examples
7+
PySDM-examples @ git+git://github.com/atmos-cloud-sim-uj/PySDM-examples@49a271a#egg=PySDM-examples

0 commit comments

Comments
 (0)