diff --git a/tests/profiles/test_core.py b/tests/profiles/test_core.py index 9323d57..c536cc0 100644 --- a/tests/profiles/test_core.py +++ b/tests/profiles/test_core.py @@ -2,50 +2,44 @@ import pint import pytest import xarray as xr -from numpy.testing import assert_approx_equal, assert_allclose +from numpy.testing import assert_allclose, assert_approx_equal from joseki import unit_registry as ureg from joseki.core import make from joseki.profiles.core import ( - rescale_to_column, - interp, extrapolate, + interp, regularize, + rescale_to_column, select_molecules, ) from joseki.units import to_quantity + @pytest.fixture def test_data_set() -> xr.Dataset: """Test dataset fixture.""" return make(identifier="afgl_1986-tropical") + def test_rescale_to_column_factor_0(): ds = make(identifier="afgl_1986-tropical") with xr.set_options(keep_attrs=True): - reference = ds.copy(deep=True).assign({ - "x_H2O": ds.x_H2O * 0 - }) + reference = ds.copy(deep=True).assign({"x_H2O": ds.x_H2O * 0}) - rescaled = rescale_to_column( - reference=reference, - ds=ds - ) + rescaled = rescale_to_column(reference=reference, ds=ds) assert_allclose(rescaled.x_H2O, 0) + def test_rescale_to_column_impossible(): reference = make(identifier="afgl_1986-tropical") with xr.set_options(keep_attrs=True): - ds = reference.copy(deep=True).assign({ - "x_H2O": reference.x_H2O * 0 - }) + ds = reference.copy(deep=True).assign({"x_H2O": reference.x_H2O * 0}) with pytest.raises(ValueError): - rescale_to_column( - reference=reference, - ds=ds - ) + rescale_to_column(reference=reference, ds=ds) + def test_interp_returns_data_set(test_data_set: xr.Dataset): """Returns an xarray.Dataset.""" @@ -65,12 +59,11 @@ def test_interp_out_of_bound(test_data_set: xr.Dataset): def test_interp_extrapolate(test_data_set: xr.Dataset): """Key-word argument 'fill_value' is processed.""" interpolated = interp( - ds=test_data_set, - z_new=np.linspace(0, 150) * ureg.km, - fill_value="extrapolate" + ds=test_data_set, z_new=np.linspace(0, 150) * ureg.km, fill_value="extrapolate" ) assert interpolated.joseki.is_valid + def test_interp_bounds_error(test_data_set: xr.Dataset): """Key-word argument 'fill_value' is processed.""" interpolated = interp( @@ -86,7 +79,7 @@ def test_interp_bounds_error(test_data_set: xr.Dataset): [ -0.1 * ureg.km, np.linspace(-0.5, -0.1, 5) * ureg.km, - ] + ], ) def test_extrapolate_down(test_data_set: xr.Dataset, z_down: pint.Quantity): """Extrapolate down to 100 m.""" @@ -104,12 +97,13 @@ def test_extrapolate_down(test_data_set: xr.Dataset, z_down: pint.Quantity): ) assert extrapolated.joseki.is_valid + @pytest.mark.parametrize( "z_up", [ 130 * ureg.km, np.linspace(130, 180, 6) * ureg.km, - ] + ], ) def test_extrapolate_up(test_data_set: xr.Dataset, z_up: pint.Quantity): """Extrapolate up to 130 km.""" @@ -137,7 +131,7 @@ def test_extrapolate_invalid_altitude(test_data_set: xr.Dataset): z_extra=10 * ureg.km, direction="down", ) - + with pytest.raises(ValueError): extrapolate( ds=test_data_set, @@ -145,6 +139,7 @@ def test_extrapolate_invalid_altitude(test_data_set: xr.Dataset): direction="up", ) + def test_extrapolate_invalid_direction(test_data_set: xr.Dataset): """Raises when invalid direction is provided.""" with pytest.raises(ValueError): @@ -154,6 +149,7 @@ def test_extrapolate_invalid_direction(test_data_set: xr.Dataset): direction="invalid", ) + def test_regularize(test_data_set: xr.Dataset): """Regularize the profile.""" regularized = regularize(ds=test_data_set) @@ -162,6 +158,7 @@ def test_regularize(test_data_set: xr.Dataset): # grid is regular assert np.all(np.diff(zgrid) == zgrid[1] - zgrid[0]) + def test_regularize_zstep(test_data_set: xr.Dataset): """Regularize the profile.""" zstep = 0.5 * ureg.km @@ -169,12 +166,13 @@ def test_regularize_zstep(test_data_set: xr.Dataset): ds=test_data_set, options={ "zstep": zstep, - } + }, ) zgrid = to_quantity(regularized.z) assert np.all(np.diff(zgrid) == zstep) + def test_regularize_zstep_str_invalid(test_data_set: xr.Dataset): """Invalid zstep raises.""" with pytest.raises(ValueError): @@ -182,9 +180,10 @@ def test_regularize_zstep_str_invalid(test_data_set: xr.Dataset): ds=test_data_set, options={ "zstep": "invalid", - } + }, ) + def test_regularize_zstep_invalid_type(test_data_set: xr.Dataset): """Invalid zstep raises.""" with pytest.raises(ValueError): @@ -192,16 +191,14 @@ def test_regularize_zstep_invalid_type(test_data_set: xr.Dataset): ds=test_data_set, options={ "zstep": ["cannot be a list"], - } + }, ) + def test_regularize_options_invalid(test_data_set: xr.Dataset): """Invalid options raises.""" with pytest.raises(ValueError): - regularize( - ds=test_data_set, - options={} - ) + regularize(ds=test_data_set, options={}) def test_regularize_num(test_data_set: xr.Dataset): @@ -211,7 +208,7 @@ def test_regularize_num(test_data_set: xr.Dataset): ds=test_data_set, options={ "num": num, - } + }, ) assert regularized.z.size == num @@ -228,6 +225,7 @@ def test_select_molecules(test_data_set: xr.Dataset): assert "CO2" in selected.joseki.molecules assert "O3" not in selected.joseki.molecules + def test_select_molecules_invalid(test_data_set: xr.Dataset): """Raise when selected molecules are not available.""" molecules = ["SO2", "NO2"] diff --git a/tests/profiles/test_factory.py b/tests/profiles/test_factory.py index 10bc39f..3d509b1 100644 --- a/tests/profiles/test_factory.py +++ b/tests/profiles/test_factory.py @@ -5,4 +5,4 @@ def test_create(): with pytest.raises(ValueError): - factory.create(identifier="invalid") \ No newline at end of file + factory.create(identifier="invalid") diff --git a/tests/profiles/test_mipas_2007.py b/tests/profiles/test_mipas_2007.py index 3ea6576..c678b6a 100644 --- a/tests/profiles/test_mipas_2007.py +++ b/tests/profiles/test_mipas_2007.py @@ -2,23 +2,24 @@ import numpy as np import pytest -from joseki.profiles.mipas_2007 import parse_content -from joseki.profiles.mipas_2007 import parse_units -from joseki.profiles.mipas_2007 import parse_values_line -from joseki.profiles.mipas_2007 import parse_var_line -from joseki.profiles.mipas_2007 import parse_var_name -from joseki.profiles.mipas_2007 import Identifier -from joseki.profiles.mipas_2007 import read_file_content -from joseki.profiles.mipas_2007 import translate_cfc, to_chemical_formula -from joseki.profiles.mipas_2007 import to_dataset -from joseki.units import ureg from joseki.profiles.mipas_2007 import ( - MIPASMidlatitudeNight, + Identifier, MIPASMidlatitudeDay, + MIPASMidlatitudeNight, MIPASPolarSummer, MIPASPolarWinter, MIPASTropical, + parse_content, + parse_units, + parse_values_line, + parse_var_line, + parse_var_name, + read_file_content, + to_chemical_formula, + to_dataset, + translate_cfc, ) +from joseki.units import ureg def test_parse_unit(): @@ -174,6 +175,7 @@ def test_to_chemical_formula_h2o(): """Returns non-chlorofulorocarbon name unchanged.""" assert to_chemical_formula("H2O") == "H2O" + def test_to_dataset_z(): ds = to_dataset( identifier=Identifier.TROPICAL, @@ -181,14 +183,18 @@ def test_to_dataset_z(): ) assert ds.joseki.is_valid -@pytest.mark.parametrize("profile", [ - MIPASMidlatitudeNight(), - MIPASMidlatitudeDay(), - MIPASPolarSummer(), - MIPASPolarWinter(), - MIPASTropical(), -]) + +@pytest.mark.parametrize( + "profile", + [ + MIPASMidlatitudeNight(), + MIPASMidlatitudeDay(), + MIPASPolarSummer(), + MIPASPolarWinter(), + MIPASTropical(), + ], +) def test_profile_to_dataset(profile): """Returns a dataset.""" ds = profile.to_dataset() - assert ds.joseki.is_valid \ No newline at end of file + assert ds.joseki.is_valid diff --git a/tests/profiles/test_schema.py b/tests/profiles/test_schema.py index adda889..def12c8 100644 --- a/tests/profiles/test_schema.py +++ b/tests/profiles/test_schema.py @@ -1,6 +1,7 @@ from joseki import unit_registry as ureg from joseki.profiles.schema import schema + def test_convert_no_n(): """convert() adds 'n' data variable if missing in input""" converted = schema.convert( @@ -13,14 +14,14 @@ def test_convert_no_n(): "z": [0, 10, 20] * ureg.km, }, attrs={ - "Conventions": "CF-1.10", - "title": "test", - "institution": "test", - "source": "test", - "history": "test", - "references": "test", - "url": "test", - "urldate": "test", - } + "Conventions": "CF-1.10", + "title": "test", + "institution": "test", + "source": "test", + "history": "test", + "references": "test", + "url": "test", + "urldate": "test", + }, ) assert "n" in converted diff --git a/tests/profiles/test_ussa_1976.py b/tests/profiles/test_ussa_1976.py index 660f351..0477e0e 100644 --- a/tests/profiles/test_ussa_1976.py +++ b/tests/profiles/test_ussa_1976.py @@ -1,6 +1,7 @@ """Test cases for the ussa_1976 module.""" from joseki.profiles.ussa_1976 import USSA1976 + def test_ussa_1976(): """Returns a valid dataset.""" profile = USSA1976() diff --git a/tests/profiles/test_util.py b/tests/profiles/test_util.py index 38f2e2c..2a648b2 100644 --- a/tests/profiles/test_util.py +++ b/tests/profiles/test_util.py @@ -3,13 +3,14 @@ import pytest import xarray as xr +from joseki import unit_registry as ureg from joseki.profiles.util import ( air_molar_mass_from_mass_fraction, molar_mass, number_density, ) from joseki.units import to_quantity -from joseki import unit_registry as ureg + @pytest.fixture def test_y(): @@ -25,7 +26,7 @@ def test_y(): coords={ "m": ("m", ["N2", "O2"]), "z": ("z", [0, 50, 100], {"units": "km"}), - } + }, ) @@ -35,6 +36,7 @@ def test_air_molar_mass_from_mass_fraction(test_y): assert q.check("[mass] / [substance]") assert np.allclose(air_mm.values, 29.0, rtol=1e-2) + def test_number_density(): p = 101325 * ureg.Pa t = 273.15 * ureg.K @@ -46,10 +48,10 @@ def test_number_density(): rtol=1e-5, ) + def test_molar_mass(): molecules = ["H2O", "CO2"] da = molar_mass(molecules) assert isinstance(da, xr.DataArray) q = to_quantity(da) assert q.check("[mass] / [substance]") - diff --git a/tests/test_accessor.py b/tests/test_accessor.py index 0b65ca6..e92971b 100644 --- a/tests/test_accessor.py +++ b/tests/test_accessor.py @@ -7,8 +7,7 @@ import joseki from joseki.accessor import _scaling_factor -from joseki.units import to_quantity -from joseki.units import ureg +from joseki.units import to_quantity, ureg @pytest.fixture @@ -73,13 +72,12 @@ def test_dataset_non_standard_units(): "references": "N/A", "url": "N/A", "urldate": "N/A", - } + }, ) @pytest.mark.parametrize( - "identifier", - ["ussa_1976", "afgl_1986-us_standard", "mipas_2007-midlatitude_day"] + "identifier", ["ussa_1976", "afgl_1986-us_standard", "mipas_2007-midlatitude_day"] ) def test_column_mass_density(identifier: str): """Returns a dictionary.""" @@ -92,11 +90,10 @@ def test_column_mass_density(identifier: str): [ ("afgl_1986-us_standard", 14.38 * ureg.kg / ureg.m**2), ("mipas_2007-midlatitude_day", 19.51 * ureg.kg / ureg.m**2), - ] + ], ) def test_water_vapour_column_mass_density_in_cells( - identifier: str, - expected: pint.Quantity + identifier: str, expected: pint.Quantity ): """Column mass density of water vapor is close to expected values.""" ds = joseki.make(identifier) @@ -108,13 +105,10 @@ def test_water_vapour_column_mass_density_in_cells( "identifier, expected", [ ("afgl_1986-us_standard", 345.7 * ureg.dobson_unit), - ("mipas_2007-midlatitude_day", 301.7 * ureg.dobson_unit) - ] + ("mipas_2007-midlatitude_day", 301.7 * ureg.dobson_unit), + ], ) -def test_column_number_density( - identifier: str, - expected: pint.Quantity -): +def test_column_number_density(identifier: str, expected: pint.Quantity): """Column number density of ozone matches expected value.""" ds = joseki.make(identifier) ozone_amount = ds.joseki.column_number_density["O3"].to("dobson_unit") @@ -139,8 +133,8 @@ def test_number_density_at_sea_level(identifier: str): "identifier, expected", [ ("afgl_1986-us_standard", 0.00590723 * ureg.kg / ureg.m**3), - ("mipas_2007-midlatitude_day", 0.00901076 * ureg.kg / ureg.m**3) - ] + ("mipas_2007-midlatitude_day", 0.00901076 * ureg.kg / ureg.m**3), + ], ) def test_mass_density_at_sea_level(identifier: str, expected: pint.Quantity): """Mass density at sea level of H2O matches expected value.""" @@ -153,8 +147,8 @@ def test_mass_density_at_sea_level(identifier: str, expected: pint.Quantity): "identifier, expected", [ ("afgl_1986-us_standard", 0.000330 * ureg.dimensionless), - ("mipas_2007-midlatitude_day", 0.0003685 * ureg.dimensionless) - ] + ("mipas_2007-midlatitude_day", 0.0003685 * ureg.dimensionless), + ], ) def test_mole_fraction_at_sea_level(identifier: str, expected): """CO2 mole mixing fraction at sea level matches expected value.""" @@ -199,9 +193,7 @@ def test_scaling_factors(identifier: str): assert all([k1 == k2 for k1, k2 in zip(target.keys(), factors.keys())]) -@pytest.mark.parametrize( - "identifier", ["afgl_1986-us_standard"] -) +@pytest.mark.parametrize("identifier", ["afgl_1986-us_standard"]) def test_rescale(identifier: str): """Scaling factors are applied.""" ds = joseki.make(identifier) @@ -234,6 +226,7 @@ def test_rescale_invalid(identifier: str): check_x_sum=True, ) + def test_rescale_to_column_mass_density(): """Column mass density of H2O matches target value.""" ds = joseki.make("afgl_1986-midlatitude_summer") @@ -247,12 +240,13 @@ def test_rescale_to_column_mass_density(): significant=3, ) + @pytest.mark.parametrize( "molecules", [ ["H2O"], ["H2O", "CO2"], - ] + ], ) def test_drop_molecules(molecules): """x_M data variable is(are) dropped.""" @@ -261,20 +255,23 @@ def test_drop_molecules(molecules): dropped = ds.joseki.drop_molecules(molecules) assert all([m not in dropped.joseki.molecules for m in molecules]) + def test_valid_True(): """Returns True if the dataset is valid.""" ds = joseki.make("afgl_1986-midlatitude_summer") assert ds.joseki.is_valid + def test_valid_False(): """Returns False if the dataset is not valid.""" ds = joseki.make("afgl_1986-midlatitude_summer") - + # drop variable 't' ds = ds.drop_vars("t") assert not ds.joseki.is_valid + def test_validate(test_dataset_non_standard_units, tmpdir): """Returns True if the dataset is valid.""" path = tmpdir.join("test_dataset.nc") diff --git a/tests/test_core.py b/tests/test_core.py index 2a12e02..9952e7e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4,7 +4,7 @@ from numpy.testing import assert_approx_equal from joseki import unit_registry as ureg -from joseki.core import make, open_dataset, load_dataset, merge, identifiers +from joseki.core import identifiers, load_dataset, make, merge, open_dataset def test_make(): @@ -27,7 +27,7 @@ def test_make_python_dict(): "z": { "value": [0, 10, 20, 30, 60, 90, 120], "units": "km", - } + }, } ds = make(**d) assert ds.joseki.is_valid @@ -93,9 +93,10 @@ def test_make_conserve_column(): assert_approx_equal( ds0.joseki.column_number_density[m].m, ds1.joseki.column_number_density[m].m, - significant=6 + significant=6, ) + def test_make_molecules(): """Returns dataset with molecules corresponding to selection.""" ds = make(identifier="afgl_1986-tropical", molecules=["H2O", "CO2"]) @@ -129,18 +130,13 @@ def test_make_regularize_dict(): @pytest.mark.parametrize( "target", [ - { - "H2O": 25 * ureg.kg / ureg.m**2 - }, - { - "H2O": 25 * ureg.kg / ureg.m**2, - "CO2": 420 * ureg.ppm - }, + {"H2O": 25 * ureg.kg / ureg.m**2}, + {"H2O": 25 * ureg.kg / ureg.m**2, "CO2": 420 * ureg.ppm}, { "H2O": 25 * ureg.kg / ureg.m**2, "CO2": 420 * ureg.ppm, - "O3": 350 * ureg.dobson_unit - } + "O3": 350 * ureg.dobson_unit, + }, ], ) def test_make_rescale_to(target): @@ -161,6 +157,7 @@ def test_make_rescale_to(target): significant=6, ) + def test_open_dataset(tmpdir): """Returns xr.Dataset.""" ds = make(identifier="afgl_1986-tropical") diff --git a/tests/test_units.py b/tests/test_units.py index be3e2e5..bb288c5 100644 --- a/tests/test_units.py +++ b/tests/test_units.py @@ -5,7 +5,7 @@ import xarray as xr from numpy.typing import ArrayLike -from joseki.units import ureg, to_quantity +from joseki.units import to_quantity, ureg @pytest.fixture @@ -19,11 +19,13 @@ def dataset() -> xr.Dataset: coords={"t": ("t", np.linspace(0, 10, 50), {"units": "s"})}, ) + def test_to_quantity_invalid_type(): """Raises a TypeError.""" with pytest.raises(NotImplementedError): to_quantity("not a quantity") + def test_to_quantity_quantity(): """Returns the same quantity.""" q = ureg.Quantity(1.0, "m") @@ -31,40 +33,40 @@ def test_to_quantity_quantity(): assert to_quantity(q, units="cm").units == ureg.Unit("cm") + def test_to_quantity_dict(): """Returns a quantity.""" d = {"value": 1.0, "units": "m"} assert isinstance(to_quantity(d), pint.Quantity) + @pytest.mark.parametrize( "value", - [ - 1, - 1.0, - [1.0, 2.0], - [[1.0, 2.0], [3.0, 4.0]], - np.array([1.0, 2.0]) - ], + [1, 1.0, [1.0, 2.0], [[1.0, 2.0], [3.0, 4.0]], np.array([1.0, 2.0])], ) def test_to_quantity_array_like(value: ArrayLike): """Returns a dimensionless quantity.""" assert to_quantity(value).units == ureg.dimensionless assert to_quantity(value, units="m").units == ureg.Unit("m") + def test_to_quantity_da(dataset: xr.Dataset): """Returns a quantity.""" assert isinstance(to_quantity(dataset.x), pint.Quantity) + def test_to_quantity_da_units(dataset: xr.Dataset): """Returns a quantity with cm units.""" q = to_quantity(dataset.x, units="cm") assert q.units == ureg.Unit("cm") + def test_to_quantity_da_no_units_raise(dataset: xr.Dataset): """Returns a quantity.""" with pytest.raises(ValueError): to_quantity(dataset.y) + def test_to_quantity_da_no_units_no_raise(dataset: xr.Dataset): """Returns a quantity.""" assert isinstance(to_quantity(dataset.y, units="m"), pint.Quantity)