Skip to content

Commit 1b0013d

Browse files
add isotope kinetic fractionation factors to physics (Craig-Gordon and Jouzel-Merlivat-1984) + refactor existing code in examples to use it + add handling of web.archive urls in bib script (#1565)
Co-authored-by: Sylwester Arabas <[email protected]>
1 parent 2918dda commit 1b0013d

File tree

9 files changed

+2006
-18
lines changed

9 files changed

+2006
-18
lines changed

PySDM/formulae.py

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def __init__( # pylint: disable=too-many-locals
4949
heterogeneous_ice_nucleation_rate: str = "Null",
5050
fragmentation_function: str = "AlwaysN",
5151
isotope_equilibrium_fractionation_factors: str = "Null",
52+
isotope_kinetic_fractionation_factors: str = "Null",
5253
isotope_meteoric_water_line: str = "Null",
5354
isotope_ratio_evolution: str = "Null",
5455
isotope_diffusivity_ratios: str = "Null",
@@ -88,6 +89,9 @@ def __init__( # pylint: disable=too-many-locals
8889
self.isotope_equilibrium_fractionation_factors = (
8990
isotope_equilibrium_fractionation_factors
9091
)
92+
self.isotope_kinetic_fractionation_factors = (
93+
isotope_kinetic_fractionation_factors
94+
)
9195
self.isotope_meteoric_water_line = isotope_meteoric_water_line
9296
self.isotope_ratio_evolution = isotope_ratio_evolution
9397
self.isotope_diffusivity_ratios = isotope_diffusivity_ratios

PySDM/physics/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
hygroscopicity,
3232
impl,
3333
isotope_equilibrium_fractionation_factors,
34+
isotope_kinetic_fractionation_factors,
3435
isotope_meteoric_water_line,
3536
isotope_ratio_evolution,
3637
isotope_diffusivity_ratios,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""
2+
kinetic fractionation factors for water isotopologues
3+
"""
4+
5+
from PySDM.impl.null_physics_class import Null
6+
from .jouzel_and_merlivat_1984 import JouzelAndMerlivat1984
7+
from .craig_gordon import CraigGordon
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
kinetic fractionation factor in the framework of the Craig-Gordon model
3+
as given in eq. 1.5 in
4+
[Rozanski_et_al_2001 (UNESCO, ed. Mook)
5+
](https://web.archive.org/web/20160322221332/https://hydrology.nl/images/docs/ihp/Mook_III.pdf)
6+
and as used in [Pierchala et al. 2022](https://doi.org/10.1016/j.gca.2022.01.020)
7+
"""
8+
9+
10+
class CraigGordon: # pylint: disable=too-few-public-methods
11+
def __init__(self, _):
12+
pass
13+
14+
@staticmethod
15+
def alpha_kinetic(*, relative_humidity, turbulence_parameter_n, delta_diff, theta):
16+
"""delta_diff = 1 - heavy_to_light_diffusivity_ratio"""
17+
return 1 + theta * turbulence_parameter_n * delta_diff * (1 - relative_humidity)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""
2+
kinetic fractionation factor from [Jouzel & Merlivat 1984](https://doi.org/10.1029/JD089iD07p11749)
3+
(as eq. 3e for n=1 in [Stewart 1975](https://doi.org/10.1029/JC080i009p01133))
4+
"""
5+
6+
7+
class JouzelAndMerlivat1984: # pylint: disable=too-few-public-methods
8+
def __init__(self, _):
9+
pass
10+
11+
@staticmethod
12+
def alpha_kinetic(
13+
alpha_equilibrium, relative_humidity, heavy_to_light_diffusivity_ratio
14+
):
15+
"""eq. (11) or eq. (14)"""
16+
return relative_humidity / (
17+
alpha_equilibrium
18+
/ heavy_to_light_diffusivity_ratio
19+
* (relative_humidity - 1)
20+
+ 1
21+
)

docs/bibliography.json

+26-2
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@
198198
"examples/PySDM_examples/Pierchala_et_al_2022/fig_3.ipynb",
199199
"examples/PySDM_examples/Pierchala_et_al_2022/fig_4.ipynb",
200200
"examples/PySDM_examples/Pierchala_et_al_2022/__init__.py",
201-
"examples/PySDM_examples/Pierchala_et_al_2022/commons.py"
201+
"examples/PySDM_examples/Pierchala_et_al_2022/commons.py",
202+
"PySDM/physics/isotope_kinetic_fractionation_factors/craig_gordon.py"
202203
],
203204
"label": "Pierchala et al. 2022 (Geochim. Cosmochim. Acta 322)",
204205
"title": "Quantification the diffusion-induced fractionation of <sup>1</sup>H<sub>2</sub><sup>17</sup>O isotopologue in air accompanying the process of water evaporation"
@@ -572,7 +573,8 @@
572573
},
573574
"https://doi.org/10.1029/JC080i009p01133": {
574575
"usages": [
575-
"PySDM/physics/isotope_diffusivity_ratios/stewart_1975.py"
576+
"PySDM/physics/isotope_diffusivity_ratios/stewart_1975.py",
577+
"PySDM/physics/isotope_kinetic_fractionation_factors/jouzel_and_merlivat_1984.py"
576578
],
577579
"label": "Stewart 1975 (J. Geophys. Res. Oceans 80)",
578580
"title": "Stable isotope fractionation due to evaporation and isotopic exchange of falling waterdrops: Applications to atmospheric processes and evaporation of lakes"
@@ -811,5 +813,27 @@
811813
],
812814
"title": "The Growth of Cloud Drops in Uniformly Cooled Air",
813815
"label": "Howell 1949 (J. Atmos. Sci.)"
816+
},
817+
"https://doi.org/10.1029/JD089iD07p11749": {
818+
"usages": [
819+
"PySDM/physics/isotope_kinetic_fractionation_factors/jouzel_and_merlivat_1984.py",
820+
"tests/unit_tests/physics/test_isotope_kinetic_fractionation_factors.py"
821+
],
822+
"title": "Deuterium and oxygen 18 in precipitation: Modeling of the isotopic effects during snow formation",
823+
"label": "Jouzel & Merlivat 1984 (J. Geophys. Res. Atmos. 89)"
824+
},
825+
"https://web.archive.org/web/20160322221332/https://hydrology.nl/images/docs/ihp/Mook_III.pdf": {
826+
"usages": [
827+
"PySDM/physics/isotope_kinetic_fractionation_factors/craig_gordon.py"
828+
],
829+
"title": "Environmental isotopes in the hydrological cycle - Principles and applications: Vol III Surface water",
830+
"label": "Rozanski_et_al_2001 (UNESCO, ed. Mook)"
831+
},
832+
"https://web.archive.org/web/20200729203147/https://nucleus.iaea.org/rpst/documents/VSMOW_SLAP.pdf": {
833+
"usages": [
834+
"PySDM/physics/constants_defaults.py"
835+
],
836+
"title": "IAEA Reference Sheet for International Measurement Standards: VSMOW & SLAP",
837+
"label": "IAEA 2006"
814838
}
815839
}

docs/generate_html.py

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def check_urls(urls_from_json):
6868
r"\b(https://digitallibrary\.un\.org/record/(?:[0-9])+)\b",
6969
r"\b(http://mi\.mathnet\.ru/dan(?:[0-9])+)\b",
7070
r"\b(https://archive.org/details/(?:[0-9a-z_\.-])+)\b",
71+
r"\b(https://web.archive.org/web/(?:[0-9])+/https://(?:[0-9a-zA-Z_\.-/])+)\b",
7172
):
7273
urls = re.findall(pattern, text)
7374
if urls:

examples/PySDM_examples/Pierchala_et_al_2022/fig_4.ipynb

+1,815-16
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# pylint: disable=missing-module-docstring
2+
import numpy as np
3+
import pytest
4+
from matplotlib import pyplot
5+
from PySDM import Formulae
6+
from PySDM import physics
7+
from PySDM.physics.dimensional_analysis import DimensionalAnalysis
8+
9+
10+
class TestIsotopeKineticFractionationFactors:
11+
@staticmethod
12+
@pytest.mark.parametrize(
13+
"variant, kwargs",
14+
(
15+
(
16+
physics.isotope_kinetic_fractionation_factors.CraigGordon,
17+
{
18+
"turbulence_parameter_n": 1,
19+
"delta_diff": 1,
20+
"theta": 1,
21+
},
22+
),
23+
(
24+
physics.isotope_kinetic_fractionation_factors.JouzelAndMerlivat1984,
25+
{
26+
"alpha_equilibrium": 1,
27+
"heavy_to_light_diffusivity_ratio": 1,
28+
},
29+
),
30+
),
31+
)
32+
def test_units(variant, kwargs):
33+
"""checks that alphas are dimensionless"""
34+
with DimensionalAnalysis():
35+
# arrange
36+
sut = variant.alpha_kinetic
37+
38+
# act
39+
result = sut(relative_humidity=1 * physics.si.dimensionless, **kwargs)
40+
41+
# assert
42+
assert result.check("[]")
43+
44+
@staticmethod
45+
def test_fig_9_from_jouzel_and_merlivat_1984(plot=False):
46+
"""[Jouzel & Merlivat 1984](https://doi.org/10.1029/JD089iD07p11749)"""
47+
# arrange
48+
formulae = Formulae(
49+
isotope_kinetic_fractionation_factors="JouzelAndMerlivat1984",
50+
isotope_equilibrium_fractionation_factors="Majoube1970",
51+
isotope_diffusivity_ratios="Stewart1975",
52+
)
53+
temperatures = formulae.trivia.C2K(np.asarray([-30, -20, -10]))
54+
saturation = np.linspace(start=1, stop=1.35)
55+
alpha_s = formulae.isotope_equilibrium_fractionation_factors.alpha_i_18O
56+
heavy_to_light_diffusivity_ratio = formulae.isotope_diffusivity_ratios.ratio_18O
57+
sut = formulae.isotope_kinetic_fractionation_factors.alpha_kinetic
58+
59+
# act
60+
alpha_s = {temperature: alpha_s(temperature) for temperature in temperatures}
61+
alpha_k = {
62+
temperature: sut(
63+
alpha_equilibrium=alpha_s[temperature],
64+
relative_humidity=saturation,
65+
heavy_to_light_diffusivity_ratio=heavy_to_light_diffusivity_ratio(
66+
temperature
67+
),
68+
)
69+
for temperature in temperatures
70+
}
71+
alpha_s_times_alpha_k = {
72+
f"{formulae.trivia.K2C(temperature):.3g}C": alpha_k[temperature]
73+
* alpha_s[temperature]
74+
for temperature in temperatures
75+
}
76+
77+
# plot
78+
pyplot.xlim(saturation[0], saturation[-1])
79+
pyplot.ylim(1.003, 1.022)
80+
pyplot.xlabel("S")
81+
pyplot.ylabel("alpha_k * alpha_s")
82+
for k, v in alpha_s_times_alpha_k.items():
83+
pyplot.plot(saturation, v, label=k)
84+
pyplot.legend()
85+
if plot:
86+
pyplot.show()
87+
else:
88+
pyplot.clf()
89+
90+
# assert
91+
assert (alpha_s_times_alpha_k["-30C"] > alpha_s_times_alpha_k["-20C"]).all()
92+
assert (alpha_s_times_alpha_k["-20C"] > alpha_s_times_alpha_k["-10C"]).all()
93+
for alpha_alpha in alpha_s_times_alpha_k.values():
94+
assert (np.diff(alpha_alpha) < 0).all()
95+
np.testing.assert_approx_equal(
96+
actual=alpha_s_times_alpha_k["-30C"][0],
97+
desired=1.021,
98+
significant=4,
99+
)
100+
np.testing.assert_approx_equal(
101+
actual=alpha_s_times_alpha_k["-30C"][-1],
102+
desired=1.0075,
103+
significant=4,
104+
)
105+
np.testing.assert_approx_equal(
106+
actual=alpha_s_times_alpha_k["-10C"][0],
107+
desired=1.0174,
108+
significant=4,
109+
)
110+
np.testing.assert_approx_equal(
111+
actual=alpha_s_times_alpha_k["-10C"][-1],
112+
desired=1.004,
113+
significant=4,
114+
)

0 commit comments

Comments
 (0)