roms-tools 2.4.0__py3-none-any.whl → 2.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ci/environment-with-xesmf.yml +16 -0
- roms_tools/__init__.py +1 -1
- roms_tools/analysis/roms_output.py +339 -234
- roms_tools/analysis/utils.py +137 -0
- roms_tools/plot.py +353 -214
- roms_tools/regrid.py +154 -9
- roms_tools/setup/boundary_forcing.py +51 -37
- roms_tools/setup/datasets.py +129 -74
- roms_tools/setup/grid.py +32 -33
- roms_tools/setup/initial_conditions.py +30 -37
- roms_tools/setup/nesting.py +238 -64
- roms_tools/setup/river_forcing.py +256 -86
- roms_tools/setup/surface_forcing.py +40 -28
- roms_tools/setup/tides.py +10 -13
- roms_tools/setup/topography.py +27 -4
- roms_tools/setup/utils.py +28 -12
- roms_tools/tests/test_analysis/test_roms_output.py +299 -80
- roms_tools/tests/test_regrid.py +85 -2
- roms_tools/tests/test_setup/test_boundary_forcing.py +63 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zattrs +3 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zmetadata +3 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/.zattrs +2 -2
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/.zmetadata +8 -7
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/abs_time/.zattrs +1 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/dust/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/dust_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/iron/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/iron_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nhy/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nhy_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nox/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nox_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/pco2_air/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/pco2_air_alt/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/pco2_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zattrs +2 -2
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zmetadata +2 -2
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/dust/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/iron/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nhy/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nox/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air_alt/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/.zmetadata +2 -2
- roms_tools/tests/test_setup/test_data/grid.zarr/angle/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/angle_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/f/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/h/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/h/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/lat_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/lat_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/lat_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/lat_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/pm/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/f/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pm/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pn/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zmetadata +1 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/salt/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/temp/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/u/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ubar/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/v/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/vbar/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zeta/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +27 -1
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/nriver/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/nriver/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/nriver/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_location/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/.zmetadata +27 -1
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/nriver/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/nriver/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/nriver/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_location/.zattrs +1 -1
- roms_tools/tests/test_setup/test_initial_conditions.py +16 -0
- roms_tools/tests/test_setup/test_nesting.py +141 -104
- roms_tools/tests/test_setup/test_river_forcing.py +580 -266
- roms_tools/tests/test_setup/test_surface_forcing.py +47 -0
- roms_tools/tests/test_setup/test_validation.py +34 -2
- roms_tools/utils.py +11 -7
- roms_tools/vertical_coordinate.py +1 -0
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/METADATA +22 -11
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/RECORD +214 -206
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/WHEEL +1 -1
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info/licenses}/LICENSE +0 -0
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/top_level.txt +0 -0
|
@@ -4,9 +4,10 @@ import logging
|
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
import cartopy.crs as ccrs
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import Dict, Union, List
|
|
7
|
+
from typing import Dict, Union, List, Optional
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
import matplotlib.pyplot as plt
|
|
10
|
+
import matplotlib.cm as cm
|
|
10
11
|
from roms_tools import Grid
|
|
11
12
|
from roms_tools.plot import _get_projection, _add_field_to_ax
|
|
12
13
|
from roms_tools.utils import save_datasets
|
|
@@ -22,7 +23,7 @@ from roms_tools.setup.utils import (
|
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
@dataclass(
|
|
26
|
+
@dataclass(kw_only=True)
|
|
26
27
|
class RiverForcing:
|
|
27
28
|
"""Represents river forcing input data for ROMS.
|
|
28
29
|
|
|
@@ -57,6 +58,24 @@ class RiverForcing:
|
|
|
57
58
|
Whether to include BGC tracers. Defaults to `False`.
|
|
58
59
|
model_reference_date : datetime, optional
|
|
59
60
|
Reference date for the model. Default is January 1, 2000.
|
|
61
|
+
indices : dict[str, list[tuple]], optional
|
|
62
|
+
A dictionary specifying the river indices for each river to be included in the river forcing. This parameter is optional. If not provided,
|
|
63
|
+
the river indices will be automatically determined based on the grid and the source dataset. If provided, it allows for explicit specification
|
|
64
|
+
of river locations. The dictionary structure consists of river names as keys, and each value is a list of tuples. Each tuple represents
|
|
65
|
+
a pair of indices corresponding to the `eta_rho` and `xi_rho` grid coordinates of the river.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
indices = {
|
|
69
|
+
'Hvita(Olfusa)': [(8, 6), (7, 6)],
|
|
70
|
+
'Thjorsa': [(8, 6)],
|
|
71
|
+
'JkulsFjll': [(11, 12)],
|
|
72
|
+
'Lagarfljot': [(9, 13), (8, 13), (10, 13)],
|
|
73
|
+
'Bruara': [(8, 6)],
|
|
74
|
+
'Svarta': [(12, 9)]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
In the example, the dictionary provides the river names as keys, and the values are lists of tuples, where each tuple represents the
|
|
78
|
+
`(eta_rho, xi_rho)` indices for a river location.
|
|
60
79
|
|
|
61
80
|
Attributes
|
|
62
81
|
----------
|
|
@@ -64,6 +83,9 @@ class RiverForcing:
|
|
|
64
83
|
The xarray Dataset containing the river forcing data.
|
|
65
84
|
climatology : bool
|
|
66
85
|
Indicates whether the final river forcing is climatological.
|
|
86
|
+
Dict[str, Union[int, List[int]]]
|
|
87
|
+
A dictionary of river indices. If not provided during initialization, it will be automatically determined
|
|
88
|
+
based on the grid and the source dataset. The dictionary structure is the same as described in the `indices` parameter docstring.
|
|
67
89
|
"""
|
|
68
90
|
|
|
69
91
|
grid: Grid
|
|
@@ -73,44 +95,51 @@ class RiverForcing:
|
|
|
73
95
|
convert_to_climatology: str = "if_any_missing"
|
|
74
96
|
include_bgc: bool = False
|
|
75
97
|
model_reference_date: datetime = datetime(2000, 1, 1)
|
|
98
|
+
indices: Optional[Dict[str, Dict[str, Union[int, List[int]]]]] = None
|
|
76
99
|
|
|
77
100
|
ds: xr.Dataset = field(init=False, repr=False)
|
|
78
101
|
climatology: xr.Dataset = field(init=False, repr=False)
|
|
79
102
|
|
|
80
103
|
def __post_init__(self):
|
|
81
104
|
self._input_checks()
|
|
82
|
-
target_coords = get_target_coords(self.grid)
|
|
83
|
-
# maximum dx in grid
|
|
84
|
-
dx = (
|
|
85
|
-
np.sqrt((1 / self.grid.ds.pm) ** 2 + (1 / self.grid.ds.pn) ** 2) / 2
|
|
86
|
-
).max()
|
|
87
|
-
|
|
88
105
|
data = self._get_data()
|
|
89
106
|
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
if self.indices is None:
|
|
108
|
+
logging.info(
|
|
109
|
+
"No river indices provided. Identify all rivers within the ROMS domain and assign each of them to the nearest coastal point."
|
|
110
|
+
)
|
|
111
|
+
target_coords = get_target_coords(self.grid)
|
|
112
|
+
# maximum dx in grid
|
|
113
|
+
dx = (
|
|
114
|
+
np.sqrt((1 / self.grid.ds.pm) ** 2 + (1 / self.grid.ds.pn) ** 2) / 2
|
|
115
|
+
).max()
|
|
116
|
+
original_indices = data.extract_relevant_rivers(target_coords, dx)
|
|
117
|
+
if len(original_indices) == 0:
|
|
118
|
+
raise ValueError(
|
|
119
|
+
"No relevant rivers found. Consider increasing domain size or using a different river dataset."
|
|
120
|
+
)
|
|
121
|
+
self.original_indices = original_indices
|
|
122
|
+
updated_indices = self._move_rivers_to_closest_coast(target_coords, data)
|
|
123
|
+
self.indices = updated_indices
|
|
92
124
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
self.
|
|
96
|
-
ds
|
|
97
|
-
self.
|
|
125
|
+
else:
|
|
126
|
+
logging.info("Use provided river indices.")
|
|
127
|
+
self.original_indices = self.indices
|
|
128
|
+
check_river_locations_are_along_coast(self.grid.ds.mask_rho, self.indices)
|
|
129
|
+
data.extract_named_rivers(self.indices)
|
|
98
130
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
131
|
+
ds = self._create_river_forcing(data)
|
|
132
|
+
ds = self._write_indices_into_dataset(ds)
|
|
133
|
+
self._validate(ds)
|
|
103
134
|
|
|
104
|
-
|
|
135
|
+
for var_name in ds.data_vars:
|
|
136
|
+
ds[var_name] = substitute_nans_by_fillvalue(ds[var_name], fill_value=0.0)
|
|
105
137
|
|
|
106
|
-
|
|
107
|
-
raise ValueError(
|
|
108
|
-
"No relevant rivers found. Consider increasing domain size or using a different river dataset."
|
|
109
|
-
)
|
|
138
|
+
self.ds = ds
|
|
110
139
|
|
|
111
140
|
def _input_checks(self):
|
|
112
141
|
if self.source is None:
|
|
113
|
-
|
|
142
|
+
self.source = {"name": "DAI"}
|
|
114
143
|
|
|
115
144
|
if "name" not in self.source:
|
|
116
145
|
raise ValueError("`source` must include a 'name'.")
|
|
@@ -119,11 +148,67 @@ class RiverForcing:
|
|
|
119
148
|
raise ValueError("`source` must include a 'path'.")
|
|
120
149
|
|
|
121
150
|
# Set 'climatology' to False if not provided in 'source'
|
|
122
|
-
|
|
123
|
-
self,
|
|
124
|
-
"source",
|
|
125
|
-
|
|
126
|
-
|
|
151
|
+
self.source = {
|
|
152
|
+
**self.source,
|
|
153
|
+
"climatology": self.source.get("climatology", False),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Check if 'indices' is provided and has the correct format
|
|
157
|
+
if self.indices is not None:
|
|
158
|
+
if not isinstance(self.indices, dict):
|
|
159
|
+
raise ValueError("`indices` must be a dictionary.")
|
|
160
|
+
|
|
161
|
+
# Ensure the dictionary contains at least one river
|
|
162
|
+
if len(self.indices) == 0:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
"The provided 'indices' dictionary must contain at least one river."
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
for river_name, river_data in self.indices.items():
|
|
168
|
+
if not isinstance(river_name, str):
|
|
169
|
+
raise ValueError(f"River name `{river_name}` must be a string.")
|
|
170
|
+
|
|
171
|
+
if not isinstance(river_data, list):
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"Data for river `{river_name}` must be a list of tuples."
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Ensure each element in the list is a tuple of length 2
|
|
177
|
+
seen_tuples = set()
|
|
178
|
+
for idx_pair in river_data:
|
|
179
|
+
if not isinstance(idx_pair, tuple) or len(idx_pair) != 2:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
f"Each item for river `{river_name}` must be a tuple of length 2 representing (eta_rho, xi_rho)."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
eta_rho, xi_rho = idx_pair
|
|
185
|
+
|
|
186
|
+
# Ensure both eta_rho and xi_rho are integers
|
|
187
|
+
if not isinstance(eta_rho, int):
|
|
188
|
+
raise ValueError(
|
|
189
|
+
f"First element of tuple for river `{river_name}` must be an integer (eta_rho), but got {type(eta_rho)}."
|
|
190
|
+
)
|
|
191
|
+
if not isinstance(xi_rho, int):
|
|
192
|
+
raise ValueError(
|
|
193
|
+
f"Second element of tuple for river `{river_name}` must be an integer (xi_rho), but got {type(xi_rho)}."
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Check that eta_rho and xi_rho are within the valid range
|
|
197
|
+
if not (0 <= eta_rho < len(self.grid.ds.eta_rho)):
|
|
198
|
+
raise ValueError(
|
|
199
|
+
f"Value of eta_rho for river `{river_name}` ({eta_rho}) is out of valid range [0, {len(self.grid.ds.eta_rho)-1}]."
|
|
200
|
+
)
|
|
201
|
+
if not (0 <= xi_rho < len(self.grid.ds.xi_rho)):
|
|
202
|
+
raise ValueError(
|
|
203
|
+
f"Value of xi_rho for river `{river_name}` ({xi_rho}) is out of valid range [0, {len(self.grid.ds.xi_rho)-1}]."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Check for duplicate tuples
|
|
207
|
+
if idx_pair in seen_tuples:
|
|
208
|
+
raise ValueError(
|
|
209
|
+
f"Duplicate location {idx_pair} found for river `{river_name}`."
|
|
210
|
+
)
|
|
211
|
+
seen_tuples.add(idx_pair)
|
|
127
212
|
|
|
128
213
|
def _get_data(self):
|
|
129
214
|
|
|
@@ -169,23 +254,23 @@ class RiverForcing:
|
|
|
169
254
|
- `river_tracer`: A `DataArray` representing tracer data for temperature, salinity and BGC tracers (if specified) for each river over time.
|
|
170
255
|
"""
|
|
171
256
|
if self.source["climatology"]:
|
|
172
|
-
|
|
257
|
+
self.climatology = True
|
|
173
258
|
else:
|
|
174
259
|
if self.convert_to_climatology in ["never", "if_any_missing"]:
|
|
175
260
|
data_ds = data.select_relevant_times(data.ds)
|
|
176
261
|
if self.convert_to_climatology == "if_any_missing":
|
|
177
262
|
if data_ds[data.var_names["flux"]].isnull().any():
|
|
178
263
|
data.compute_climatology()
|
|
179
|
-
|
|
264
|
+
self.climatology = True
|
|
180
265
|
else:
|
|
181
|
-
|
|
182
|
-
|
|
266
|
+
data.ds = data_ds
|
|
267
|
+
self.climatology = False
|
|
183
268
|
else:
|
|
184
|
-
|
|
185
|
-
|
|
269
|
+
data.ds = data_ds
|
|
270
|
+
self.climatology = False
|
|
186
271
|
elif self.convert_to_climatology == "always":
|
|
187
272
|
data.compute_climatology()
|
|
188
|
-
|
|
273
|
+
self.climatology = True
|
|
189
274
|
|
|
190
275
|
ds = xr.Dataset()
|
|
191
276
|
|
|
@@ -197,13 +282,19 @@ class RiverForcing:
|
|
|
197
282
|
river_volume = river_volume.rename(
|
|
198
283
|
{data.dim_names["time"]: "river_time", data.dim_names["station"]: "nriver"}
|
|
199
284
|
)
|
|
285
|
+
|
|
200
286
|
name = data.ds[data.var_names["name"]].rename(
|
|
201
287
|
{data.dim_names["station"]: "nriver"}
|
|
202
288
|
)
|
|
203
289
|
name.attrs["long_name"] = "River name"
|
|
204
290
|
river_volume.coords["river_name"] = name
|
|
291
|
+
|
|
205
292
|
ds["river_volume"] = river_volume
|
|
206
293
|
|
|
294
|
+
nriver = xr.DataArray(np.arange(1, len(ds.nriver) + 1), dims="nriver")
|
|
295
|
+
nriver.attrs["long_name"] = "River ID (1-based Fortran indexing)"
|
|
296
|
+
ds = ds.assign_coords({"nriver": nriver})
|
|
297
|
+
|
|
207
298
|
if self.include_bgc:
|
|
208
299
|
ntracers = 2 + 32
|
|
209
300
|
else:
|
|
@@ -272,15 +363,13 @@ class RiverForcing:
|
|
|
272
363
|
|
|
273
364
|
ds = ds.assign_coords({"river_time": time})
|
|
274
365
|
|
|
275
|
-
ds = ds.drop_vars("nriver")
|
|
276
|
-
|
|
277
366
|
return ds
|
|
278
367
|
|
|
279
368
|
def _move_rivers_to_closest_coast(self, target_coords, data):
|
|
280
369
|
"""Move river mouths to the closest coastal grid cell.
|
|
281
370
|
|
|
282
371
|
This method computes the closest coastal grid point to each river mouth
|
|
283
|
-
based on geographical distance.
|
|
372
|
+
based on geographical distance. It identifies the nearest grid point on the coast and returns the updated river mouth indices.
|
|
284
373
|
|
|
285
374
|
Parameters:
|
|
286
375
|
-----------
|
|
@@ -296,11 +385,11 @@ class RiverForcing:
|
|
|
296
385
|
- `var_names`: A dictionary of variable names in the dataset (e.g., longitude, latitude, station names).
|
|
297
386
|
- `dim_names`: A dictionary containing dimension names for the dataset (e.g., "station", "eta_rho", "xi_rho").
|
|
298
387
|
|
|
299
|
-
Returns
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
of
|
|
388
|
+
Returns
|
|
389
|
+
-------
|
|
390
|
+
indices : dict[str, list[tuple]]
|
|
391
|
+
A dictionary consisting of river names as keys, and each value is a list of tuples. Each tuple represents
|
|
392
|
+
a pair of indices corresponding to the `eta_rho` and `xi_rho` grid coordinates of the river.
|
|
304
393
|
"""
|
|
305
394
|
|
|
306
395
|
# Retrieve longitude and latitude of river mouths
|
|
@@ -333,27 +422,29 @@ class RiverForcing:
|
|
|
333
422
|
|
|
334
423
|
# Find the indices of the closest coastal grid cell to the river mouth
|
|
335
424
|
indices = np.where(dist_coast == dist_coast_min)
|
|
425
|
+
stations = indices[0]
|
|
426
|
+
eta_rho_values = indices[1]
|
|
427
|
+
xi_rho_values = indices[2]
|
|
336
428
|
names = (
|
|
337
429
|
data.ds[data.var_names["name"]]
|
|
338
|
-
.isel({data.dim_names["station"]:
|
|
430
|
+
.isel({data.dim_names["station"]: stations})
|
|
339
431
|
.values
|
|
340
432
|
)
|
|
341
|
-
|
|
342
433
|
# Return the indices in a dictionary format
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
434
|
+
river_indices = {}
|
|
435
|
+
for i in range(len(stations)):
|
|
436
|
+
river_name = names[i]
|
|
437
|
+
river_indices[river_name] = [
|
|
438
|
+
(int(eta_rho_values[i]), int(xi_rho_values[i]))
|
|
439
|
+
] # list of tuples
|
|
440
|
+
|
|
441
|
+
return river_indices
|
|
350
442
|
|
|
351
443
|
def _write_indices_into_dataset(self, ds):
|
|
352
444
|
"""Adds river location indices to the dataset as the "river_location" variable.
|
|
353
445
|
|
|
354
|
-
This method
|
|
355
|
-
|
|
356
|
-
using river station indices from `self.updated_indices` and assigns it to the dataset.
|
|
446
|
+
This method creates a new "river_location" variable
|
|
447
|
+
using river station indices from `self.indices` and assigns it to the dataset.
|
|
357
448
|
The indices specify the river station locations in terms of eta_rho and xi_rho grid cell indices.
|
|
358
449
|
|
|
359
450
|
Parameters
|
|
@@ -367,17 +458,21 @@ class RiverForcing:
|
|
|
367
458
|
The modified dataset with the "river_location" variable added.
|
|
368
459
|
"""
|
|
369
460
|
|
|
370
|
-
|
|
371
|
-
ds = ds.drop_vars("river_location")
|
|
461
|
+
river_locations = xr.zeros_like(self.grid.ds.h)
|
|
372
462
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
xi_index = self.updated_indices["xi_rho"][i]
|
|
378
|
-
river_locations[eta_index, xi_index] = station + 2
|
|
463
|
+
for nriver in ds.nriver:
|
|
464
|
+
river_name = str(ds.river_name.sel(nriver=nriver).values)
|
|
465
|
+
indices = self.indices[river_name]
|
|
466
|
+
fraction = 1.0 / len(indices)
|
|
379
467
|
|
|
380
|
-
|
|
468
|
+
for eta_index, xi_index in indices:
|
|
469
|
+
|
|
470
|
+
river_locations[eta_index, xi_index] = (
|
|
471
|
+
nriver # assign unique nriver ID (Fortran-based indexing)
|
|
472
|
+
+ fraction # Fractional contribution for multiple grid points
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
river_locations.attrs["long_name"] = "River ID plus local volume fraction"
|
|
381
476
|
river_locations.attrs["units"] = "none"
|
|
382
477
|
ds["river_location"] = river_locations
|
|
383
478
|
|
|
@@ -463,26 +558,46 @@ class RiverForcing:
|
|
|
463
558
|
"color": "black",
|
|
464
559
|
} # Customize latitude label style
|
|
465
560
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
561
|
+
proj = ccrs.PlateCarree()
|
|
562
|
+
|
|
563
|
+
if len(self.indices) <= 10:
|
|
564
|
+
color_map = cm.get_cmap("tab10")
|
|
565
|
+
elif len(self.indices) <= 20:
|
|
566
|
+
color_map = cm.get_cmap("tab20")
|
|
567
|
+
else:
|
|
568
|
+
color_map = cm.get_cmap("tab20b")
|
|
569
|
+
# Create a dictionary of colors
|
|
570
|
+
colors = {name: color_map(i) for i, name in enumerate(self.indices.keys())}
|
|
571
|
+
|
|
572
|
+
for ax, indices in zip(axs, [self.original_indices, self.indices]):
|
|
573
|
+
added_labels = set()
|
|
574
|
+
for name in indices.keys():
|
|
575
|
+
for tuple in indices[name]:
|
|
576
|
+
eta_index = tuple[0]
|
|
577
|
+
xi_index = tuple[1]
|
|
578
|
+
|
|
579
|
+
# transform coordinates to projected space
|
|
580
|
+
transformed_lon, transformed_lat = trans.transform_point(
|
|
581
|
+
self.grid.ds.lon_rho[eta_index, xi_index],
|
|
582
|
+
self.grid.ds.lat_rho[eta_index, xi_index],
|
|
583
|
+
proj,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
if name not in added_labels:
|
|
587
|
+
added_labels.add(name)
|
|
588
|
+
label = name
|
|
589
|
+
else:
|
|
590
|
+
label = "_None"
|
|
591
|
+
|
|
592
|
+
ax.plot(
|
|
593
|
+
transformed_lon,
|
|
594
|
+
transformed_lat,
|
|
595
|
+
marker="x",
|
|
596
|
+
markersize=8,
|
|
597
|
+
markeredgewidth=2,
|
|
598
|
+
label=label,
|
|
599
|
+
color=colors[name],
|
|
600
|
+
)
|
|
486
601
|
|
|
487
602
|
axs[0].set_title("Original river locations")
|
|
488
603
|
axs[1].set_title("Updated river locations")
|
|
@@ -647,4 +762,59 @@ class RiverForcing:
|
|
|
647
762
|
grid = Grid.from_yaml(filepath)
|
|
648
763
|
params = _from_yaml(cls, filepath)
|
|
649
764
|
|
|
765
|
+
def convert_indices_format(indices):
|
|
766
|
+
# Remove the '_convention' key from the dictionary if present
|
|
767
|
+
indices = {
|
|
768
|
+
key: value for key, value in indices.items() if key != "_convention"
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
# Convert the string of indices into tuples
|
|
772
|
+
for river, index_list in indices.items():
|
|
773
|
+
# Split the string by ',' and convert to tuples of integers
|
|
774
|
+
indices[river] = [tuple(map(int, idx.split(","))) for idx in index_list]
|
|
775
|
+
|
|
776
|
+
return indices
|
|
777
|
+
|
|
778
|
+
params["indices"] = convert_indices_format(params["indices"])
|
|
779
|
+
|
|
650
780
|
return cls(grid=grid, **params)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def check_river_locations_are_along_coast(mask, indices):
|
|
784
|
+
"""Check if the river locations are along the coast.
|
|
785
|
+
|
|
786
|
+
This function checks if the river locations specified in the `indices` dictionary are located on coastal grid cells.
|
|
787
|
+
A coastal grid cell is defined as a land grid cell adjacent to an ocean grid cell.
|
|
788
|
+
|
|
789
|
+
Parameters
|
|
790
|
+
----------
|
|
791
|
+
mask : xarray.DataArray
|
|
792
|
+
A mask representing the land and ocean cells in the grid, where 1 represents ocean and 0 represents land.
|
|
793
|
+
|
|
794
|
+
indices : dict
|
|
795
|
+
A dictionary where the keys are river names, and the values are dictionaries containing the river's grid locations (`eta_rho` and `xi_rho`).
|
|
796
|
+
Each entry should have keys `"eta_rho"` and `"xi_rho"`, which are lists of grid cell indices representing river mouth locations.
|
|
797
|
+
|
|
798
|
+
Raises
|
|
799
|
+
------
|
|
800
|
+
ValueError
|
|
801
|
+
If any river is not located on the coast.
|
|
802
|
+
"""
|
|
803
|
+
|
|
804
|
+
faces = (
|
|
805
|
+
mask.shift(eta_rho=1)
|
|
806
|
+
+ mask.shift(eta_rho=-1)
|
|
807
|
+
+ mask.shift(xi_rho=1)
|
|
808
|
+
+ mask.shift(xi_rho=-1)
|
|
809
|
+
)
|
|
810
|
+
coast = (1 - mask) * (faces > 0)
|
|
811
|
+
|
|
812
|
+
for key, river_data in indices.items():
|
|
813
|
+
for idx_pair in river_data:
|
|
814
|
+
eta_rho, xi_rho = idx_pair
|
|
815
|
+
|
|
816
|
+
# Check if the river location is along the coast
|
|
817
|
+
if not coast[eta_rho, xi_rho]:
|
|
818
|
+
raise ValueError(
|
|
819
|
+
f"River `{key}` is not located on the coast at grid cell ({eta_rho}, {xi_rho})."
|
|
820
|
+
)
|
|
@@ -6,10 +6,10 @@ import numpy as np
|
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
import logging
|
|
9
|
-
from typing import Dict, Union, List
|
|
9
|
+
from typing import Dict, Union, List, Optional
|
|
10
10
|
from roms_tools import Grid
|
|
11
11
|
from roms_tools.utils import save_datasets
|
|
12
|
-
from roms_tools.regrid import
|
|
12
|
+
from roms_tools.regrid import LateralRegridToROMS
|
|
13
13
|
from roms_tools.plot import _plot
|
|
14
14
|
from roms_tools.setup.datasets import (
|
|
15
15
|
ERA5Dataset,
|
|
@@ -30,7 +30,7 @@ from roms_tools.setup.utils import (
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
@dataclass(
|
|
33
|
+
@dataclass(kw_only=True)
|
|
34
34
|
class SurfaceForcing:
|
|
35
35
|
"""Represents surface forcing input data for ROMS.
|
|
36
36
|
|
|
@@ -38,10 +38,14 @@ class SurfaceForcing:
|
|
|
38
38
|
----------
|
|
39
39
|
grid : Grid
|
|
40
40
|
Object representing the grid information.
|
|
41
|
-
start_time : datetime
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
start_time : datetime, optional
|
|
42
|
+
The start time of the desired surface forcing data. This time is used to filter the dataset
|
|
43
|
+
to include only records on or after this time, with a single record at or before this time.
|
|
44
|
+
If no time filtering is desired, set it to None. Default is None.
|
|
45
|
+
end_time : datetime, optional
|
|
46
|
+
The end time of the desired surface forcing data. This time is used to filter the dataset
|
|
47
|
+
to include only records on or before this time, with a single record at or after this time.
|
|
48
|
+
If no time filtering is desired, set it to None. Default is None.
|
|
45
49
|
source : Dict[str, Union[str, Path, List[Union[str, Path]]], bool]
|
|
46
50
|
Dictionary specifying the source of the surface forcing data. Keys include:
|
|
47
51
|
|
|
@@ -90,8 +94,8 @@ class SurfaceForcing:
|
|
|
90
94
|
"""
|
|
91
95
|
|
|
92
96
|
grid: Grid
|
|
93
|
-
start_time: datetime
|
|
94
|
-
end_time: datetime
|
|
97
|
+
start_time: Optional[datetime] = None
|
|
98
|
+
end_time: Optional[datetime] = None
|
|
95
99
|
source: Dict[str, Union[str, Path, List[Union[str, Path]]]]
|
|
96
100
|
type: str = "physics"
|
|
97
101
|
correct_radiation: bool = True
|
|
@@ -117,15 +121,17 @@ class SurfaceForcing:
|
|
|
117
121
|
logging.info("Data will be interpolated onto grid coarsened by factor 2.")
|
|
118
122
|
else:
|
|
119
123
|
logging.info("Data will be interpolated onto fine grid.")
|
|
120
|
-
|
|
124
|
+
self.use_coarse_grid = use_coarse_grid
|
|
121
125
|
|
|
122
126
|
target_coords = get_target_coords(self.grid, self.use_coarse_grid)
|
|
123
|
-
|
|
127
|
+
self.target_coords = target_coords
|
|
124
128
|
|
|
125
129
|
data.choose_subdomain(
|
|
126
130
|
target_coords,
|
|
127
131
|
buffer_points=20, # lateral fill needs some buffer from data margin
|
|
128
132
|
)
|
|
133
|
+
# Enforce double precision to ensure reproducibility
|
|
134
|
+
data.convert_to_float64()
|
|
129
135
|
|
|
130
136
|
data.apply_lateral_fill()
|
|
131
137
|
|
|
@@ -134,7 +140,7 @@ class SurfaceForcing:
|
|
|
134
140
|
|
|
135
141
|
processed_fields = {}
|
|
136
142
|
# lateral regridding
|
|
137
|
-
lateral_regrid =
|
|
143
|
+
lateral_regrid = LateralRegridToROMS(target_coords, data.dim_names)
|
|
138
144
|
for var_name in var_names:
|
|
139
145
|
if var_name in data.var_names.keys():
|
|
140
146
|
processed_fields[var_name] = lateral_regrid.apply(
|
|
@@ -165,9 +171,21 @@ class SurfaceForcing:
|
|
|
165
171
|
for var_name in ds.data_vars:
|
|
166
172
|
ds[var_name] = substitute_nans_by_fillvalue(ds[var_name])
|
|
167
173
|
|
|
168
|
-
|
|
174
|
+
self.ds = ds
|
|
169
175
|
|
|
170
176
|
def _input_checks(self):
|
|
177
|
+
# Check that start_time and end_time are both None or none of them is
|
|
178
|
+
if (self.start_time is None) != (self.end_time is None):
|
|
179
|
+
raise ValueError(
|
|
180
|
+
"Both `start_time` and `end_time` must be provided together as datetime objects or both should be None."
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Trigger a warning if both are None
|
|
184
|
+
if self.start_time is None and self.end_time is None:
|
|
185
|
+
logging.warning(
|
|
186
|
+
"Both `start_time` and `end_time` are None. No time filtering will be applied to the source data."
|
|
187
|
+
)
|
|
188
|
+
|
|
171
189
|
# Validate the 'type' parameter
|
|
172
190
|
if self.type not in ["physics", "bgc"]:
|
|
173
191
|
raise ValueError("`type` must be either 'physics' or 'bgc'.")
|
|
@@ -179,11 +197,10 @@ class SurfaceForcing:
|
|
|
179
197
|
raise ValueError("`source` must include a 'path'.")
|
|
180
198
|
|
|
181
199
|
# Set 'climatology' to False if not provided in 'source'
|
|
182
|
-
|
|
183
|
-
self,
|
|
184
|
-
"source",
|
|
185
|
-
|
|
186
|
-
)
|
|
200
|
+
self.source = {
|
|
201
|
+
**self.source,
|
|
202
|
+
"climatology": self.source.get("climatology", False),
|
|
203
|
+
}
|
|
187
204
|
|
|
188
205
|
# Validate 'coarse_grid_mode'
|
|
189
206
|
valid_modes = ["auto", "always", "never"]
|
|
@@ -321,7 +338,7 @@ class SurfaceForcing:
|
|
|
321
338
|
else:
|
|
322
339
|
variable_info[var_name] = {**default_info, "validate": False}
|
|
323
340
|
|
|
324
|
-
|
|
341
|
+
self.variable_info = variable_info
|
|
325
342
|
|
|
326
343
|
def _apply_correction(self, processed_fields, data):
|
|
327
344
|
|
|
@@ -364,7 +381,9 @@ class SurfaceForcing:
|
|
|
364
381
|
)
|
|
365
382
|
|
|
366
383
|
# Spatial regridding
|
|
367
|
-
lateral_regrid =
|
|
384
|
+
lateral_regrid = LateralRegridToROMS(
|
|
385
|
+
self.target_coords, correction_data.dim_names
|
|
386
|
+
)
|
|
368
387
|
corr_factor = lateral_regrid.apply(corr_factor)
|
|
369
388
|
|
|
370
389
|
processed_fields["swrad"] = processed_fields["swrad"] * corr_factor
|
|
@@ -606,7 +625,6 @@ class SurfaceForcing:
|
|
|
606
625
|
cls,
|
|
607
626
|
filepath: Union[str, Path],
|
|
608
627
|
use_dask: bool = False,
|
|
609
|
-
bypass_validation: bool = False,
|
|
610
628
|
) -> "SurfaceForcing":
|
|
611
629
|
"""Create an instance of the SurfaceForcing class from a YAML file.
|
|
612
630
|
|
|
@@ -616,10 +634,6 @@ class SurfaceForcing:
|
|
|
616
634
|
The path to the YAML file from which the parameters will be read.
|
|
617
635
|
use_dask: bool, optional
|
|
618
636
|
Indicates whether to use dask for processing. If True, data is processed with dask; if False, data is processed eagerly. Defaults to False.
|
|
619
|
-
bypass_validation: bool, optional
|
|
620
|
-
Indicates whether to skip validation checks in the processed data. When set to True,
|
|
621
|
-
the validation process that ensures no NaN values exist at wet points
|
|
622
|
-
in the processed dataset is bypassed. Defaults to False.
|
|
623
637
|
|
|
624
638
|
Returns
|
|
625
639
|
-------
|
|
@@ -631,6 +645,4 @@ class SurfaceForcing:
|
|
|
631
645
|
grid = Grid.from_yaml(filepath)
|
|
632
646
|
params = _from_yaml(cls, filepath)
|
|
633
647
|
|
|
634
|
-
return cls(
|
|
635
|
-
grid=grid, **params, use_dask=use_dask, bypass_validation=bypass_validation
|
|
636
|
-
)
|
|
648
|
+
return cls(grid=grid, **params, use_dask=use_dask)
|