Skip to content

Commit d2ed10c

Browse files
Add Bolin 1958 Table 1 example (isotopic adjustment timescale, formulae-only no-environment setup) (#1431)
Co-authored-by: Agnieszka Żaba <azaba@agh.edu.pl> Co-authored-by: Sylwester Arabas <sylwester.arabas@agh.edu.pl>
1 parent e6e8aab commit d2ed10c

File tree

8 files changed

+386
-1
lines changed

8 files changed

+386
-1
lines changed

PySDM/physics/constants_defaults.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,3 +428,12 @@ def compute_derived_values(c: dict):
428428
bulk_phase_partitioning_T_cold = 235 * si.K
429429
bulk_phase_partitioning_T_warm = 273 * si.K
430430
bulk_phase_partitioning_exponent = np.nan
431+
432+
433+
BOLIN_ISOTOPE_TIMESCALE_COEFF_C1 = np.nan * si.dimensionless
434+
"""
435+
Coeffitient c1 used in [Bolin 1958](https://https://digitallibrary.un.org/record/3892725)
436+
for the falling drop evaporation timescale of equilibration with ambient air void of a given
437+
isotopologue; in the paper timescale is calculated for tritium with assumption of no tritium
438+
in the environment around the drop (Table 1).
439+
"""

PySDM/physics/isotope_relaxation_timescale/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
from PySDM.impl.null_physics_class import Null
66
from .miyake_et_al_1968 import MiyakeEtAl1968
7+
from .bolin_1958 import Bolin1958
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
""" Timescale derived for tritium with assumption of zero ambient concentration - see text above
2+
Table 1 [Bolin 1958](https://digitallibrary.un.org/record/3892725) """
3+
4+
import numpy as np
5+
6+
7+
class Bolin1958: # pylint: disable=too-few-public-methods
8+
"""Implementation of timescale used by Bolin 1958"""
9+
10+
def __init__(self, const):
11+
assert np.isfinite(const.BOLIN_ISOTOPE_TIMESCALE_COEFF_C1)
12+
13+
@staticmethod
14+
# pylint: disable=too-many-arguments
15+
def tau(const, radius, r_dr_dt):
16+
"""timescale for evaporation of a falling drop with tritium"""
17+
return (-3 / radius**2 * r_dr_dt * const.BOLIN_ISOTOPE_TIMESCALE_COEFF_C1) ** -1

examples/PySDM_examples/Bolin_1958/__init__.py

Whitespace-only changes.
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "e3f1edc815e974f4",
6+
"metadata": {},
7+
"source": [
8+
"[![preview notebook](https://img.shields.io/static/v1?label=render%20on&logo=github&color=87ce3e&message=GitHub)](https://github.com/open-atmos/devops_tests/blob/main/test_files/template.ipynb)\n",
9+
"[![launch on mybinder.org](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/open-atmos/devops_tests.git/main?urlpath=lab/tree/test_files/template.ipynb)\n",
10+
"[![launch on Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/open-atmos/devops_tests/blob/main/test_files/template.ipynb)"
11+
]
12+
},
13+
{
14+
"cell_type": "markdown",
15+
"id": "bb84c52c57a729ec",
16+
"metadata": {},
17+
"source": "### based on Table 1 from B.Bolin 1958 \"On the use of tritium as a tracer for water in nature\" (https://digitallibrary.un.org/record/3892725)"
18+
},
19+
{
20+
"cell_type": "code",
21+
"id": "5e4e58050cc01f64",
22+
"metadata": {
23+
"ExecuteTime": {
24+
"end_time": "2024-12-05T21:30:20.214060Z",
25+
"start_time": "2024-12-05T21:30:20.209298Z"
26+
}
27+
},
28+
"source": [
29+
"import sys\n",
30+
"if 'google.colab' in sys.modules:\n",
31+
" !pip --quiet install open-atmos-jupyter-utils\n",
32+
" from open_atmos_jupyter_utils import pip_install_on_colab\n",
33+
" pip_install_on_colab('PySDM-examples')"
34+
],
35+
"outputs": [],
36+
"execution_count": 1
37+
},
38+
{
39+
"cell_type": "code",
40+
"id": "95f360dc62f373f9",
41+
"metadata": {
42+
"ExecuteTime": {
43+
"end_time": "2024-12-05T21:30:21.622672Z",
44+
"start_time": "2024-12-05T21:30:20.218383Z"
45+
}
46+
},
47+
"source": [
48+
"import numpy as np\n",
49+
"import pandas\n",
50+
"from PySDM.physics import in_unit, si\n",
51+
"from PySDM import Formulae"
52+
],
53+
"outputs": [],
54+
"execution_count": 2
55+
},
56+
{
57+
"cell_type": "code",
58+
"id": "e29a92d6680c4e51",
59+
"metadata": {
60+
"ExecuteTime": {
61+
"end_time": "2024-12-05T21:30:21.689116Z",
62+
"start_time": "2024-12-05T21:30:21.687254Z"
63+
}
64+
},
65+
"source": "radii = np.asarray([0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.15, 0.20]) * si.cm",
66+
"outputs": [],
67+
"execution_count": 3
68+
},
69+
{
70+
"metadata": {
71+
"ExecuteTime": {
72+
"end_time": "2024-12-05T21:30:24.420354Z",
73+
"start_time": "2024-12-05T21:30:21.698737Z"
74+
}
75+
},
76+
"cell_type": "code",
77+
"source": [
78+
"formulae = Formulae(\n",
79+
" terminal_velocity='RogersYau',\n",
80+
" drop_growth='Mason1951',\n",
81+
" diffusion_thermics='Neglect',\n",
82+
" saturation_vapour_pressure='AugustRocheMagnus',\n",
83+
" ventilation='Froessling1938',\n",
84+
" particle_shape_and_density='LiquidSpheres',\n",
85+
" air_dynamic_viscosity='ZografosEtAl1987',\n",
86+
" constants={'BOLIN_ISOTOPE_TIMESCALE_COEFF_C1': 1.63},\n",
87+
" isotope_relaxation_timescale='Bolin1958',\n",
88+
")\n",
89+
"const = formulae.constants\n",
90+
"any_non_zero_value = 44.\n",
91+
"temperature = const.T0 + 10 * si.K\n",
92+
"pressure = const.p_STP\n",
93+
"pvs = formulae.saturation_vapour_pressure.pvs_water(temperature)\n",
94+
"v_term = formulae.terminal_velocity.v_term(radii)\n",
95+
"eta_air=formulae.air_dynamic_viscosity.eta_air(temperature)\n",
96+
"D=formulae.diffusion_thermics.D(T=temperature, p=pressure)\n",
97+
"\n",
98+
"air_density = pressure/const.Rd/temperature\n",
99+
"assert abs(air_density - 1)/air_density <.3\n",
100+
"Re = formulae.particle_shape_and_density.reynolds_number(\n",
101+
" radius=radii,\n",
102+
" velocity_wrt_air=v_term,\n",
103+
" dynamic_viscosity=eta_air,\n",
104+
" density=air_density,\n",
105+
")\n",
106+
"Sc = formulae.trivia.air_schmidt_number(\n",
107+
" dynamic_viscosity=eta_air, \n",
108+
" diffusivity=D, \n",
109+
" density=air_density,\n",
110+
")\n",
111+
"F = formulae.ventilation.ventilation_coefficient(sqrt_re_times_cbrt_sc=Re**(1/2) * Sc**(1/3))\n",
112+
"\n",
113+
"r_dr_dt = formulae.drop_growth.r_dr_dt(\n",
114+
" RH_eq=1,\n",
115+
" T=temperature,\n",
116+
" RH=0,\n",
117+
" lv=0,\n",
118+
" pvs=pvs,\n",
119+
" D=D,\n",
120+
" K=any_non_zero_value,\n",
121+
" ventilation_factor=F\n",
122+
")\n",
123+
"adjustment_time = formulae.isotope_relaxation_timescale.tau(radius = radii, r_dr_dt = r_dr_dt)\n",
124+
"\n",
125+
"\n",
126+
"pandas.options.display.float_format = '{:>,.2g}'.format\n",
127+
"data = pandas.DataFrame({\n",
128+
" 'radius [cm]': in_unit(radii, si.cm),\n",
129+
" 'adjustment time [s]': adjustment_time,\n",
130+
" 'terminal velocity [m/s]': v_term,\n",
131+
" 'distance [m]': v_term * adjustment_time,\n",
132+
"})\n",
133+
"\n",
134+
"data # pylint: disable=pointless-statement"
135+
],
136+
"id": "314f42c310883cfa",
137+
"outputs": [
138+
{
139+
"data": {
140+
"text/plain": [
141+
" radius [cm] adjustment time [s] terminal velocity [m/s] distance [m]\n",
142+
"0 0.005 1.7 0.4 0.69\n",
143+
"1 0.01 5.4 0.8 4.3\n",
144+
"2 0.025 20 2 40\n",
145+
"3 0.05 48 4 1.9e+02\n",
146+
"4 0.075 81 5.5 4.4e+02\n",
147+
"5 0.1 1.2e+02 6.4 7.6e+02\n",
148+
"6 0.15 2e+02 7.8 1.6e+03\n",
149+
"7 0.2 3e+02 9 2.7e+03"
150+
],
151+
"text/html": [
152+
"<div>\n",
153+
"<style scoped>\n",
154+
" .dataframe tbody tr th:only-of-type {\n",
155+
" vertical-align: middle;\n",
156+
" }\n",
157+
"\n",
158+
" .dataframe tbody tr th {\n",
159+
" vertical-align: top;\n",
160+
" }\n",
161+
"\n",
162+
" .dataframe thead th {\n",
163+
" text-align: right;\n",
164+
" }\n",
165+
"</style>\n",
166+
"<table border=\"1\" class=\"dataframe\">\n",
167+
" <thead>\n",
168+
" <tr style=\"text-align: right;\">\n",
169+
" <th></th>\n",
170+
" <th>radius [cm]</th>\n",
171+
" <th>adjustment time [s]</th>\n",
172+
" <th>terminal velocity [m/s]</th>\n",
173+
" <th>distance [m]</th>\n",
174+
" </tr>\n",
175+
" </thead>\n",
176+
" <tbody>\n",
177+
" <tr>\n",
178+
" <th>0</th>\n",
179+
" <td>0.005</td>\n",
180+
" <td>1.7</td>\n",
181+
" <td>0.4</td>\n",
182+
" <td>0.69</td>\n",
183+
" </tr>\n",
184+
" <tr>\n",
185+
" <th>1</th>\n",
186+
" <td>0.01</td>\n",
187+
" <td>5.4</td>\n",
188+
" <td>0.8</td>\n",
189+
" <td>4.3</td>\n",
190+
" </tr>\n",
191+
" <tr>\n",
192+
" <th>2</th>\n",
193+
" <td>0.025</td>\n",
194+
" <td>20</td>\n",
195+
" <td>2</td>\n",
196+
" <td>40</td>\n",
197+
" </tr>\n",
198+
" <tr>\n",
199+
" <th>3</th>\n",
200+
" <td>0.05</td>\n",
201+
" <td>48</td>\n",
202+
" <td>4</td>\n",
203+
" <td>1.9e+02</td>\n",
204+
" </tr>\n",
205+
" <tr>\n",
206+
" <th>4</th>\n",
207+
" <td>0.075</td>\n",
208+
" <td>81</td>\n",
209+
" <td>5.5</td>\n",
210+
" <td>4.4e+02</td>\n",
211+
" </tr>\n",
212+
" <tr>\n",
213+
" <th>5</th>\n",
214+
" <td>0.1</td>\n",
215+
" <td>1.2e+02</td>\n",
216+
" <td>6.4</td>\n",
217+
" <td>7.6e+02</td>\n",
218+
" </tr>\n",
219+
" <tr>\n",
220+
" <th>6</th>\n",
221+
" <td>0.15</td>\n",
222+
" <td>2e+02</td>\n",
223+
" <td>7.8</td>\n",
224+
" <td>1.6e+03</td>\n",
225+
" </tr>\n",
226+
" <tr>\n",
227+
" <th>7</th>\n",
228+
" <td>0.2</td>\n",
229+
" <td>3e+02</td>\n",
230+
" <td>9</td>\n",
231+
" <td>2.7e+03</td>\n",
232+
" </tr>\n",
233+
" </tbody>\n",
234+
"</table>\n",
235+
"</div>"
236+
]
237+
},
238+
"execution_count": 4,
239+
"metadata": {},
240+
"output_type": "execute_result"
241+
}
242+
],
243+
"execution_count": 4
244+
}
245+
],
246+
"metadata": {
247+
"kernelspec": {
248+
"display_name": "Python 3 (ipykernel)",
249+
"language": "python",
250+
"name": "python3"
251+
},
252+
"language_info": {
253+
"codemirror_mode": {
254+
"name": "ipython",
255+
"version": 3
256+
},
257+
"file_extension": ".py",
258+
"mimetype": "text/x-python",
259+
"name": "python",
260+
"nbconvert_exporter": "python",
261+
"pygments_lexer": "ipython3",
262+
"version": "3.9.6"
263+
}
264+
},
265+
"nbformat": 4,
266+
"nbformat_minor": 5
267+
}

tests/examples_tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def findfiles(path, regex):
3232
"Miyake_et_al_1968",
3333
"Rozanski_and_Sonntag_1982",
3434
"Abade_and_Albuquerque_2024",
35+
"Bolin_1958",
3536
],
3637
"condensation_a": [
3738
"Lowe_et_al_2019",
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
test checking values in the notebook table against those listed in the paper
3+
"""
4+
5+
from pathlib import Path
6+
from collections import defaultdict
7+
import numpy as np
8+
import pytest
9+
from PySDM_examples.utils import notebook_vars
10+
from PySDM_examples import Bolin_1958
11+
12+
13+
@pytest.fixture(scope="session", name="notebook_variables")
14+
def notebook_variables_fixture():
15+
"""returns variables from the notebook Bolin_1958/table_1.ipynb"""
16+
return notebook_vars(
17+
file=Path(Bolin_1958.__file__).parent / "table_1.ipynb",
18+
plot=False,
19+
)
20+
21+
22+
COLUMNS = {
23+
"row": None,
24+
"radius_cm": "radius [cm]",
25+
"adjustment_time": "adjustment time [s]",
26+
"terminal_velocity": "terminal velocity [m/s]",
27+
"distance": "distance [m]",
28+
}
29+
30+
31+
@pytest.mark.parametrize(
32+
",".join(COLUMNS.keys()),
33+
(
34+
(0, 0.005, 3.3, 0.27, 0.9),
35+
(1, 0.01, 7.1, 0.72, 5.1),
36+
(2, 0.025, 33, 2.1, 69),
37+
(3, 0.05, 93, 4.0, 370),
38+
(4, 0.075, 165, 5.4, 890),
39+
(5, 0.1, 245, 6.5, 1600),
40+
(6, 0.15, 365, 8.1, 3000),
41+
(7, 0.2, 435, 8.8, 3800),
42+
),
43+
)
44+
@pytest.mark.parametrize(
45+
"column_var, column_label",
46+
{k: v for k, v in COLUMNS.items() if k != "row"}.items(),
47+
)
48+
def test_table_1_against_values_from_the_paper(
49+
# pylint: disable=unused-argument
50+
# (used via locals())
51+
*,
52+
notebook_variables,
53+
column_var,
54+
column_label,
55+
row,
56+
radius_cm,
57+
adjustment_time,
58+
terminal_velocity,
59+
distance,
60+
):
61+
"""comparison of the calculated values of isotopic adjustment time
62+
and terminal velocity for drops with different radii with those presented
63+
in the paper
64+
"""
65+
np.testing.assert_allclose(
66+
actual=notebook_variables["data"][column_label][row],
67+
desired=locals()[column_var],
68+
rtol=defaultdict(lambda: 0.53, radius_cm=0)[column_var],
69+
)

0 commit comments

Comments
 (0)