From 86ddb1d8798017269f598f0ab92b81c5ecc774da Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Tue, 14 Jan 2025 23:55:19 +0100 Subject: [PATCH] Picciotto et al. 1960 isotopic temperature inference and [hydro]meteoric water line formulae (journal club paper); fixes in bibliography checks; code cleanups (#1498) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Agnieszka Żaba Co-authored-by: AgnieszkaZaba <56157996+AgnieszkaZaba@users.noreply.github.com> --- PySDM/formulae.py | 6 +- PySDM/physics/__init__.py | 3 +- PySDM/physics/constants_defaults.py | 19 +++++ .../isotope_meteoric_water_line/__init__.py | 7 ++ .../barkan_and_luz_2007.py | 0 .../dansgaard_1964.py | 0 .../picciotto_et_al_1960.py | 15 ++++ .../__init__.py | 5 -- .../isotope_temperature_inference/__init__.py | 8 ++ .../picciotto_et_al_1960.py | 15 ++++ docs/bibliography.json | 16 +++- docs/generate_html.py | 6 +- .../Gedzelman_and_Arnold_1994/fig_2.ipynb | 6 +- .../Graf_et_al_2019/Table_1.ipynb | 4 +- .../Pierchala_et_al_2022/fig_3.ipynb | 4 +- .../Pierchala_et_al_2022/fig_4.ipynb | 4 +- .../figs_4_5_6.ipynb | 6 +- .../no_env/pierchala_et_al_2022/test_fig_4.py | 2 +- .../pierchala_et_al_2022/test_supplement.py | 6 +- ...py => test_isotope_meteoric_water_line.py} | 75 +++++++++++++++++-- .../test_isotope_temperature_inference.py | 67 +++++++++++++++++ tests/unit_tests/test_formulae.py | 4 +- 22 files changed, 238 insertions(+), 40 deletions(-) create mode 100644 PySDM/physics/isotope_meteoric_water_line/__init__.py rename PySDM/physics/{isotope_meteoric_water_line_excess => isotope_meteoric_water_line}/barkan_and_luz_2007.py (100%) rename PySDM/physics/{isotope_meteoric_water_line_excess => isotope_meteoric_water_line}/dansgaard_1964.py (100%) create mode 100644 PySDM/physics/isotope_meteoric_water_line/picciotto_et_al_1960.py delete mode 100644 PySDM/physics/isotope_meteoric_water_line_excess/__init__.py create mode 100644 PySDM/physics/isotope_temperature_inference/__init__.py create mode 100644 PySDM/physics/isotope_temperature_inference/picciotto_et_al_1960.py rename tests/unit_tests/physics/{test_isotope_meteoric_water_line_excess.py => test_isotope_meteoric_water_line.py} (57%) create mode 100644 tests/unit_tests/physics/test_isotope_temperature_inference.py diff --git a/PySDM/formulae.py b/PySDM/formulae.py index af8dc10c6..93040d286 100644 --- a/PySDM/formulae.py +++ b/PySDM/formulae.py @@ -46,10 +46,11 @@ def __init__( # pylint: disable=too-many-locals heterogeneous_ice_nucleation_rate: str = "Null", fragmentation_function: str = "AlwaysN", isotope_equilibrium_fractionation_factors: str = "Null", - isotope_meteoric_water_line_excess: str = "Null", + isotope_meteoric_water_line: str = "Null", isotope_ratio_evolution: str = "Null", isotope_diffusivity_ratios: str = "Null", isotope_relaxation_timescale: str = "Null", + isotope_temperature_inference: str = "Null", optical_albedo: str = "Null", optical_depth: str = "Null", particle_shape_and_density: str = "LiquidSpheres", @@ -80,10 +81,11 @@ def __init__( # pylint: disable=too-many-locals self.isotope_equilibrium_fractionation_factors = ( isotope_equilibrium_fractionation_factors ) - self.isotope_meteoric_water_line_excess = isotope_meteoric_water_line_excess + self.isotope_meteoric_water_line = isotope_meteoric_water_line self.isotope_ratio_evolution = isotope_ratio_evolution self.isotope_diffusivity_ratios = isotope_diffusivity_ratios self.isotope_relaxation_timescale = isotope_relaxation_timescale + self.isotope_temperature_inference = isotope_temperature_inference self.particle_shape_and_density = particle_shape_and_density self.air_dynamic_viscosity = air_dynamic_viscosity self.terminal_velocity = terminal_velocity diff --git a/PySDM/physics/__init__.py b/PySDM/physics/__init__.py index 93b1c4f3d..65ad336cb 100644 --- a/PySDM/physics/__init__.py +++ b/PySDM/physics/__init__.py @@ -29,10 +29,11 @@ hygroscopicity, impl, isotope_equilibrium_fractionation_factors, - isotope_meteoric_water_line_excess, + isotope_meteoric_water_line, isotope_ratio_evolution, isotope_diffusivity_ratios, isotope_relaxation_timescale, + isotope_temperature_inference, latent_heat, optical_albedo, optical_depth, diff --git a/PySDM/physics/constants_defaults.py b/PySDM/physics/constants_defaults.py index 439738ce8..505511572 100644 --- a/PySDM/physics/constants_defaults.py +++ b/PySDM/physics/constants_defaults.py @@ -437,3 +437,22 @@ def compute_derived_values(c: dict): isotopologue; in the paper timescale is calculated for tritium with assumption of no tritium in the environment around the drop (Table 1). """ + +PICCIOTTO_18O_A = -0.9 * PER_MILLE / si.K +""" linear fit coefficients from [Picciotto et al. 1960](https://doi.org/10.1038/187857a0) +for atmospheric temperature inference from water isotopic composition +(note that the sign of A coefficient is opposite to match the paper plot - typo in the paper?) """ +PICCIOTTO_18O_B = 6.4 * PER_MILLE +"""〃""" +PICCIOTTO_2H_A = -0.8 * PER_CENT / si.K +"""〃""" +PICCIOTTO_2H_B = 8 * PER_CENT +"""〃""" + +PICCIOTTO_18O_TO_2H_SLOPE_COEFF = 0.8 * PER_CENT / PER_MILLE +""" [hydro]meteoric water line [Picciotto et al. 1960](https://doi.org/10.1038/187857a0) coeffs +(note that the delta-2H and delta-18O are swapped to match the paper plot - typo in the paper?) +(note that the sign of INTERCEPT is opposite to match the paper plot - typo in the paper?) +""" +PICCIOTTO_18O_TO_2H_INTERCEPT_COEFF = -1.8 * PER_CENT +"""〃""" diff --git a/PySDM/physics/isotope_meteoric_water_line/__init__.py b/PySDM/physics/isotope_meteoric_water_line/__init__.py new file mode 100644 index 000000000..2349cc57a --- /dev/null +++ b/PySDM/physics/isotope_meteoric_water_line/__init__.py @@ -0,0 +1,7 @@ +""" definitions of meteoric water line parameters +and definitions of the excess quantities""" + +from PySDM.impl.null_physics_class import Null +from .barkan_and_luz_2007 import BarkanAndLuz2007 +from .dansgaard_1964 import Dansgaard1964 +from .picciotto_et_al_1960 import PicciottoEtAl1960 diff --git a/PySDM/physics/isotope_meteoric_water_line_excess/barkan_and_luz_2007.py b/PySDM/physics/isotope_meteoric_water_line/barkan_and_luz_2007.py similarity index 100% rename from PySDM/physics/isotope_meteoric_water_line_excess/barkan_and_luz_2007.py rename to PySDM/physics/isotope_meteoric_water_line/barkan_and_luz_2007.py diff --git a/PySDM/physics/isotope_meteoric_water_line_excess/dansgaard_1964.py b/PySDM/physics/isotope_meteoric_water_line/dansgaard_1964.py similarity index 100% rename from PySDM/physics/isotope_meteoric_water_line_excess/dansgaard_1964.py rename to PySDM/physics/isotope_meteoric_water_line/dansgaard_1964.py diff --git a/PySDM/physics/isotope_meteoric_water_line/picciotto_et_al_1960.py b/PySDM/physics/isotope_meteoric_water_line/picciotto_et_al_1960.py new file mode 100644 index 000000000..a82353051 --- /dev/null +++ b/PySDM/physics/isotope_meteoric_water_line/picciotto_et_al_1960.py @@ -0,0 +1,15 @@ +""" based on [Picciotto et al. 1960](https://doi.org/10.1038/187857a0) + where delta(2H)= slope_coeff * delta(18O) + intercept_coeff formulae is given + with swapped 2H and 18O in a paper + """ + + +class PicciottoEtAl1960: # pylint: disable=too-few-public-methods + def __init__(self, _): + pass + + @staticmethod + def d18O_of_d2H(const, delta_2H): + return ( + delta_2H - const.PICCIOTTO_18O_TO_2H_INTERCEPT_COEFF + ) / const.PICCIOTTO_18O_TO_2H_SLOPE_COEFF diff --git a/PySDM/physics/isotope_meteoric_water_line_excess/__init__.py b/PySDM/physics/isotope_meteoric_water_line_excess/__init__.py deleted file mode 100644 index 8e623c4a8..000000000 --- a/PySDM/physics/isotope_meteoric_water_line_excess/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" definitions of meteoric water line excess parameters """ - -from PySDM.impl.null_physics_class import Null -from .barkan_and_luz_2007 import BarkanAndLuz2007 -from .dansgaard_1964 import Dansgaard1964 diff --git a/PySDM/physics/isotope_temperature_inference/__init__.py b/PySDM/physics/isotope_temperature_inference/__init__.py new file mode 100644 index 000000000..5103bd844 --- /dev/null +++ b/PySDM/physics/isotope_temperature_inference/__init__.py @@ -0,0 +1,8 @@ +""" +formulae for inferring atmospheric temperatures from (ice core) water isotopic composition +""" + +from PySDM.impl.null_physics_class import Null +from PySDM.physics.isotope_temperature_inference.picciotto_et_al_1960 import ( + PicciottoEtAl1960, +) diff --git a/PySDM/physics/isotope_temperature_inference/picciotto_et_al_1960.py b/PySDM/physics/isotope_temperature_inference/picciotto_et_al_1960.py new file mode 100644 index 000000000..47d1c0ced --- /dev/null +++ b/PySDM/physics/isotope_temperature_inference/picciotto_et_al_1960.py @@ -0,0 +1,15 @@ +""" based on [Picciotto et al. 1960](https://doi.org/10.1038/187857a0) + where delta(T)=-(a*T + b) formulae given, here cast as T(delta)=(-delta-b)/a """ + + +class PicciottoEtAl1960: # pylint: disable=too-few-public-methods + def __init__(self, _): + pass + + @staticmethod + def temperature_from_delta_18O(const, delta_18O): + return const.T0 + (-delta_18O - const.PICCIOTTO_18O_B) / const.PICCIOTTO_18O_A + + @staticmethod + def temperature_from_delta_2H(const, delta_2H): + return const.T0 + (-delta_2H - const.PICCIOTTO_2H_B) / const.PICCIOTTO_2H_A diff --git a/docs/bibliography.json b/docs/bibliography.json index dad2ca043..8a9f1d445 100644 --- a/docs/bibliography.json +++ b/docs/bibliography.json @@ -178,7 +178,7 @@ }, "https://doi.org/10.3402/tellusa.v16i4.8993": { "usages": [ - "PySDM/physics/isotope_meteoric_water_line_excess/dansgaard_1964.py" + "PySDM/physics/isotope_meteoric_water_line/dansgaard_1964.py" ], "label": "Dansgaard 1964 (Tellus A 16)", "title": "Stable isotopes in precipitation" @@ -481,7 +481,7 @@ "https://doi.org/10.1002/rcm.3180": { "usages": [ "PySDM/physics/constants_defaults.py", - "PySDM/physics/isotope_meteoric_water_line_excess/barkan_and_luz_2007.py" + "PySDM/physics/isotope_meteoric_water_line/barkan_and_luz_2007.py" ], "label": "Barkan & Luz 2007 (Rapid Commun. Mass Spectrom. 21)", "title": "Diffusivity fractionations of H216O/H217O and H216O/H218O in air and their implications for isotope hydrology" @@ -616,7 +616,7 @@ }, "https://doi.org/10.1126/science.133.3465.1702": { "usages": [ - "tests/unit_tests/physics/test_isotope_meteoric_water_line_excess.py", + "tests/unit_tests/physics/test_isotope_meteoric_water_line.py", "PySDM/physics/constants_defaults.py" ], "label": "Craig 1961 (Science 133)", @@ -691,5 +691,15 @@ "usages": ["PySDM/physics/saturation_vapour_pressure/wexler_1976.py"], "title": "Vapor Pressure Formulation for Water in Range 0 to f 00 °C. A Revision", "label": "Wexler 1976 (J. Res. NBS A Phys. Ch. 80A)" + }, + "https://doi.org/10.1038/187857a0": { + "usages": [ + "PySDM/physics/constants_defaults.py", + "PySDM/physics/isotope_temperature_inference/picciotto_et_al_1960.py", + "PySDM/physics/isotope_meteoric_water_line/picciotto_et_al_1960.py", + "tests/unit_tests/physics/test_isotope_temperature_inference.py" + ], + "title": "Isotopic Composition and Temperature of Formation of Antarctic Snows", + "label": "Picciotto et al. 1960 (Nature 187)" } } diff --git a/docs/generate_html.py b/docs/generate_html.py index 32bf061ce..1e1c83b8c 100644 --- a/docs/generate_html.py +++ b/docs/generate_html.py @@ -86,8 +86,10 @@ def check_urls(urls_from_json): for url in unique_urls_found } for url in unique_urls_read: - assert url_usages_found[url] == sorted(urls_from_json[url]["usages"]), ( - f"{url} usages mismatch:\n" + assert set(url_usages_found[url]) == set( + sorted(urls_from_json[url]["usages"]) + ), ( + f"{url} usages mismatch (please fix docs/bibliography.json):\n" f"\texpected: {url_usages_found[url]}\n" f"\tactual: {urls_from_json[url]['usages']}" ) diff --git a/examples/PySDM_examples/Gedzelman_and_Arnold_1994/fig_2.ipynb b/examples/PySDM_examples/Gedzelman_and_Arnold_1994/fig_2.ipynb index 90b1cc132..78a63b087 100644 --- a/examples/PySDM_examples/Gedzelman_and_Arnold_1994/fig_2.ipynb +++ b/examples/PySDM_examples/Gedzelman_and_Arnold_1994/fig_2.ipynb @@ -11496,11 +11496,11 @@ "formulae = Formulae(\n", " isotope_equilibrium_fractionation_factors='BarkanAndLuz2005+HoritaAndWesolowski1994',\n", " isotope_diffusivity_ratios='HellmannAndHarvey2020',\n", - " isotope_meteoric_water_line_excess='BarkanAndLuz2007+Dansgaard1964',\n", + " isotope_meteoric_water_line='BarkanAndLuz2007+Dansgaard1964',\n", " isotope_ratio_evolution='GedzelmanAndArnold1994',\n", ")\n", - "deltas_18O = [formulae.isotope_meteoric_water_line_excess.d18O_of_d2H(d2H) for d2H in DELTAS_2H]\n", - "deltas_17O = [formulae.isotope_meteoric_water_line_excess.d17O_of_d18O(d18O) for d18O in deltas_18O]\n", + "deltas_18O = [formulae.isotope_meteoric_water_line.d18O_of_d2H(d2H) for d2H in DELTAS_2H]\n", + "deltas_17O = [formulae.isotope_meteoric_water_line.d17O_of_d18O(d18O) for d18O in deltas_18O]\n", "\n", "fig, axs = pyplot.subplots(3, 3, figsize=(10,8), sharex=False, sharey=True, tight_layout=True) \n", "\n", diff --git a/examples/PySDM_examples/Graf_et_al_2019/Table_1.ipynb b/examples/PySDM_examples/Graf_et_al_2019/Table_1.ipynb index 016068c57..8d9c8b569 100644 --- a/examples/PySDM_examples/Graf_et_al_2019/Table_1.ipynb +++ b/examples/PySDM_examples/Graf_et_al_2019/Table_1.ipynb @@ -80,7 +80,7 @@ "source": [ "formulae = Formulae(\n", " isotope_equilibrium_fractionation_factors='Majoube1970+Majoube1971+MerlivatAndNief1967',\n", - " isotope_meteoric_water_line_excess='Dansgaard1964'\n", + " isotope_meteoric_water_line='Dansgaard1964'\n", ")\n", "const = formulae.constants\n", "\n", @@ -90,7 +90,7 @@ " '18O_s': formulae.isotope_equilibrium_fractionation_factors.alpha_i_18O,\n", " '2H_s': formulae.isotope_equilibrium_fractionation_factors.alpha_i_2H\n", "}\n", - "excess_d = formulae.isotope_meteoric_water_line_excess.excess_d\n", + "excess_d = formulae.isotope_meteoric_water_line.excess_d\n", "\n", "CASES = {\n", " 'A': {'18O': -10 * const.PER_MILLE, '2H': -80 * const.PER_MILLE},\n", diff --git a/examples/PySDM_examples/Pierchala_et_al_2022/fig_3.ipynb b/examples/PySDM_examples/Pierchala_et_al_2022/fig_3.ipynb index 35a8b9468..d97be929f 100644 --- a/examples/PySDM_examples/Pierchala_et_al_2022/fig_3.ipynb +++ b/examples/PySDM_examples/Pierchala_et_al_2022/fig_3.ipynb @@ -71,7 +71,7 @@ "source": [ "formulae = Formulae(\n", " isotope_equilibrium_fractionation_factors='BarkanAndLuz2005+HoritaAndWesolowski1994',\n", - " isotope_meteoric_water_line_excess='Dansgaard1964+BarkanAndLuz2007',\n", + " isotope_meteoric_water_line='Dansgaard1964+BarkanAndLuz2007',\n", " isotope_ratio_evolution='RayleighDistillation'\n", ")\n", "const = formulae.constants\n", @@ -118,7 +118,7 @@ "alpha_l_2H = alphas.alpha_l_2H(T=T)\n", "alpha_l_17O = alphas.alpha_l_17O(np.nan, alpha_l_18O=alpha_l_18O)\n", "\n", - "excess = formulae.isotope_meteoric_water_line_excess\n", + "excess = formulae.isotope_meteoric_water_line\n", "\n", "deltas = {}\n", "enrichments = {}\n", diff --git a/examples/PySDM_examples/Pierchala_et_al_2022/fig_4.ipynb b/examples/PySDM_examples/Pierchala_et_al_2022/fig_4.ipynb index ba00d07dd..ffffa3f6e 100644 --- a/examples/PySDM_examples/Pierchala_et_al_2022/fig_4.ipynb +++ b/examples/PySDM_examples/Pierchala_et_al_2022/fig_4.ipynb @@ -72,7 +72,7 @@ "\n", "formulae = Formulae(\n", " isotope_equilibrium_fractionation_factors='BarkanAndLuz2005+HoritaAndWesolowski1994',\n", - " isotope_meteoric_water_line_excess='Dansgaard1964+BarkanAndLuz2007',\n", + " isotope_meteoric_water_line='Dansgaard1964+BarkanAndLuz2007',\n", " isotope_ratio_evolution='RayleighDistillation'\n", ")\n", "const = formulae.constants\n", @@ -165,7 +165,7 @@ " axs[1].plot(\n", " x,\n", " in_unit(\n", - " formulae.isotope_meteoric_water_line_excess.excess_17O(deltas['17O'], deltas['18O']),\n", + " formulae.isotope_meteoric_water_line.excess_17O(deltas['17O'], deltas['18O']),\n", " const.PER_MEG\n", " ),\n", " **kwargs\n", diff --git a/examples/PySDM_examples/Rozanski_and_Sonntag_1982/figs_4_5_6.ipynb b/examples/PySDM_examples/Rozanski_and_Sonntag_1982/figs_4_5_6.ipynb index 063031cd8..faba12f42 100644 --- a/examples/PySDM_examples/Rozanski_and_Sonntag_1982/figs_4_5_6.ipynb +++ b/examples/PySDM_examples/Rozanski_and_Sonntag_1982/figs_4_5_6.ipynb @@ -76,7 +76,7 @@ "source": [ "formulae = Formulae(\n", " isotope_equilibrium_fractionation_factors='HoritaAndWesolowski1994',\n", - " isotope_meteoric_water_line_excess='Dansgaard1964',\n", + " isotope_meteoric_water_line='Dansgaard1964',\n", " isotope_ratio_evolution='MerlivatAndJouzel1979',\n", ")\n", "BACKEND = CPU(formulae, override_jit_flags={'parallel': False})\n", @@ -96,7 +96,7 @@ "}\n", "\n", "ARBITRARY_PARAMS = {\n", - " 'delta_18O_init': formulae.isotope_meteoric_water_line_excess.d18O_of_d2H(\n", + " 'delta_18O_init': formulae.isotope_meteoric_water_line.d18O_of_d2H(\n", " delta_2H=FIG4_CAPTION_PARAMS['delta_2H_init']\n", " ),\n", " 'N_SUPER_DROPLETS': 1,\n", @@ -12299,7 +12299,7 @@ " )\n", " axs[plot_row, 3].plot(\n", " in_unit(\n", - " formulae.isotope_meteoric_water_line_excess.excess_d(delta_2H=deltas[\"2H\"], delta_18O=deltas[\"18O\"]),\n", + " formulae.isotope_meteoric_water_line.excess_d(delta_2H=deltas[\"2H\"], delta_18O=deltas[\"18O\"]),\n", " const.PER_MILLE\n", " ),\n", " temp_C[level_indices['CB']:],\n", diff --git a/tests/smoke_tests/no_env/pierchala_et_al_2022/test_fig_4.py b/tests/smoke_tests/no_env/pierchala_et_al_2022/test_fig_4.py index 2bf8b6ebe..7a5d17d33 100644 --- a/tests/smoke_tests/no_env/pierchala_et_al_2022/test_fig_4.py +++ b/tests/smoke_tests/no_env/pierchala_et_al_2022/test_fig_4.py @@ -62,7 +62,7 @@ def test_bottom_panel(notebook_local_variables, RH, delta_18O, excess_17O): np.testing.assert_approx_equal( actual=notebook_local_variables[ "formulae" - ].isotope_meteoric_water_line_excess.excess_17O( + ].isotope_meteoric_water_line.excess_17O( deltas_per_rh[RH]["17O"][index], deltas_per_rh[RH]["18O"][index] ), desired=excess_17O, diff --git a/tests/smoke_tests/no_env/pierchala_et_al_2022/test_supplement.py b/tests/smoke_tests/no_env/pierchala_et_al_2022/test_supplement.py index 92b95e608..8741bb3d7 100644 --- a/tests/smoke_tests/no_env/pierchala_et_al_2022/test_supplement.py +++ b/tests/smoke_tests/no_env/pierchala_et_al_2022/test_supplement.py @@ -9,10 +9,8 @@ def test_cracow_water_excesses(): """checking if d-excess and 17O-excess values match those computed from deltas""" # arrange - formulae = Formulae( - isotope_meteoric_water_line_excess="Dansgaard1964+BarkanAndLuz2007" - ) - sut = formulae.isotope_meteoric_water_line_excess + formulae = Formulae(isotope_meteoric_water_line="Dansgaard1964+BarkanAndLuz2007") + sut = formulae.isotope_meteoric_water_line # act/assert np.testing.assert_approx_equal( diff --git a/tests/unit_tests/physics/test_isotope_meteoric_water_line_excess.py b/tests/unit_tests/physics/test_isotope_meteoric_water_line.py similarity index 57% rename from tests/unit_tests/physics/test_isotope_meteoric_water_line_excess.py rename to tests/unit_tests/physics/test_isotope_meteoric_water_line.py index ef2924c07..5a54012e7 100644 --- a/tests/unit_tests/physics/test_isotope_meteoric_water_line_excess.py +++ b/tests/unit_tests/physics/test_isotope_meteoric_water_line.py @@ -5,10 +5,10 @@ from PySDM import Formulae from PySDM.physics import in_unit -from PySDM.physics.constants_defaults import PER_MILLE +from PySDM.physics.constants_defaults import PER_MILLE, PER_CENT -class TestIsotopeMeteoricWaterLineExcess: +class TestIsotopeMeteoricWaterLine: @staticmethod def test_craig_1961_science_fig_1(plot=False): """see Fig. 1 in [Craig 1961](https://doi.org/10.1126/science.133.3465.1702)""" @@ -87,15 +87,15 @@ def test_barkan_and_luz_2007_fig_4(plot=False): ) def test_d18O_of_d2H(delta_2H): # arrange - formulae = Formulae(isotope_meteoric_water_line_excess="Dansgaard1964") - sut = formulae.isotope_meteoric_water_line_excess.d18O_of_d2H + formulae = Formulae(isotope_meteoric_water_line="Dansgaard1964") + sut = formulae.isotope_meteoric_water_line.d18O_of_d2H # act delta_18O = sut(delta_2H=delta_2H) # assert np.testing.assert_approx_equal( - actual=formulae.isotope_meteoric_water_line_excess.excess_d( + actual=formulae.isotope_meteoric_water_line.excess_d( delta_18O=delta_18O, delta_2H=delta_2H ), desired=formulae.constants.CRAIG_1961_INTERCEPT_COEFF, @@ -108,16 +108,75 @@ def test_d18O_of_d2H(delta_2H): ) def test_d17O_of_d18O(delta_18O): # arrange - formulae = Formulae(isotope_meteoric_water_line_excess="BarkanAndLuz2007") - sut = formulae.isotope_meteoric_water_line_excess.d17O_of_d18O + formulae = Formulae(isotope_meteoric_water_line="BarkanAndLuz2007") + sut = formulae.isotope_meteoric_water_line.d17O_of_d18O # act delta_17O = sut(delta_18O=delta_18O) # assert np.testing.assert_almost_equal( - actual=formulae.isotope_meteoric_water_line_excess.excess_17O( + actual=formulae.isotope_meteoric_water_line.excess_17O( delta_18O=delta_18O, delta_17O=delta_17O ), desired=0, ) + + @staticmethod + def test_picciotto_et_al_1960_fig_2(plot=False): + # arrange + delta_deuterium = np.linspace(-31, -10) * PER_CENT + # act + + delta_18O = { + paper: Formulae( + isotope_meteoric_water_line=paper + ).isotope_meteoric_water_line.d18O_of_d2H(delta_deuterium) + for paper in ("PicciottoEtAl1960", "Dansgaard1964") + } + + # plot + pyplot.figure(figsize=(5, 6)) + for paper, delta in delta_18O.items(): + pyplot.plot( + in_unit(delta_deuterium, PER_CENT), + in_unit(delta, PER_MILLE), + label=paper, + ) + + pyplot.xlabel(r"$\delta$ $^2$H [%]") + pyplot.ylabel(r"$\delta$ $^{18}$O [‰]") + + pyplot.grid(color="k") + pyplot.legend() + if plot: + pyplot.show() + else: + pyplot.clf() + + # assert + delta = delta_18O["PicciottoEtAl1960"] + monotonic = (np.diff(delta) > 0).all() + assert monotonic + assert -37 < in_unit(delta[0], PER_MILLE) < -36 + assert -11 < in_unit(delta[-1], PER_MILLE) < -10 + + @staticmethod + @pytest.mark.parametrize( + "delta_2H", (-30 * PER_MILLE, -10 * PER_MILLE, 10 * PER_MILLE, 30 * PER_MILLE) + ) + def test_picciotto_et_al_1960_d18O_of_d2H(delta_2H): + # arrange + formulae = Formulae(isotope_meteoric_water_line="PicciottoEtAl1960") + sut = formulae.isotope_meteoric_water_line.d18O_of_d2H + + # act + delta_18O = sut(delta_2H=delta_2H) + + # assert + np.testing.assert_approx_equal( + actual=delta_2H + - formulae.constants.PICCIOTTO_18O_TO_2H_SLOPE_COEFF * delta_18O, + desired=formulae.constants.PICCIOTTO_18O_TO_2H_INTERCEPT_COEFF, + significant=3, + ) diff --git a/tests/unit_tests/physics/test_isotope_temperature_inference.py b/tests/unit_tests/physics/test_isotope_temperature_inference.py new file mode 100644 index 000000000..05568e211 --- /dev/null +++ b/tests/unit_tests/physics/test_isotope_temperature_inference.py @@ -0,0 +1,67 @@ +""" checking values in Fig. 3 in [Picciotto et al. 1960](https://doi.org/10.1038/187857a0) """ + +import numpy as np +from matplotlib import pyplot + +from PySDM import Formulae +from PySDM.physics.constants import PER_MILLE, PER_CENT +from PySDM.physics import in_unit, si + + +class TestPicciottoEtAl1960: + @staticmethod + def test_fig_3(plot=False): + # arrange + formulae = Formulae(isotope_temperature_inference="PicciottoEtAl1960") + delta_18O = np.linspace(-35, -9) * PER_MILLE + T0 = formulae.constants.T0 + + # act + temperature = formulae.isotope_temperature_inference.temperature_from_delta_18O( + delta_18O + ) + + # plot + pyplot.figure(figsize=(5, 6)) + for side in ("top", "right"): + pyplot.gca().spines[side].set_visible(False) + pyplot.plot(temperature - T0, in_unit(delta_18O, PER_MILLE), color="k") + + pyplot.xlabel("temperature [°C]") + pyplot.xlim(-45, 2) + pyplot.xticks(range(-45, 2, 5)) + + pyplot.ylabel("δ$^{18}$O [‰]") + pyplot.ylim(in_unit(delta_18O[0], PER_MILLE), in_unit(delta_18O[-1], PER_MILLE)) + pyplot.yticks(range(-35, -9, 5)) + + pyplot.grid(color="k") + if plot: + pyplot.show() + else: + pyplot.clf() + + # assert + monotonic_temperature = (np.diff(temperature) > 0).all() + assert monotonic_temperature + assert -32 < (temperature[0] - T0) < -31 + assert -3 < (temperature[-1] - T0) < -2 + + @staticmethod + def test_temperature_from_delta_2H(): + # arrange + formulae = Formulae( + isotope_temperature_inference="PicciottoEtAl1960", + isotope_meteoric_water_line="PicciottoEtAl1960", + ) + delta_2H = np.linspace(-35, -9) * PER_CENT + delta_18O = formulae.isotope_meteoric_water_line.d18O_of_d2H(delta_2H) + T18O = formulae.isotope_temperature_inference.temperature_from_delta_18O( + delta_18O + ) + + # act + T2H = formulae.isotope_temperature_inference.temperature_from_delta_2H(delta_2H) + + # assert + np.testing.assert_allclose(T18O, T2H, atol=6 * si.K) diff --git a/tests/unit_tests/test_formulae.py b/tests/unit_tests/test_formulae.py index 45cfa3876..855a9c6d1 100644 --- a/tests/unit_tests/test_formulae.py +++ b/tests/unit_tests/test_formulae.py @@ -94,10 +94,10 @@ def test_get_constant(): @pytest.mark.parametrize("arg", ("Dansgaard1964+BarkanAndLuz2007", "Dansgaard1964")) def test_plus_separated_ctor_arg(arg): # arrange - sut = formulae.Formulae(isotope_meteoric_water_line_excess=arg) + sut = formulae.Formulae(isotope_meteoric_water_line=arg) # act - class_name = sut.isotope_meteoric_water_line_excess.__name__ + class_name = sut.isotope_meteoric_water_line.__name__ # assert assert class_name == arg