roms-tools 2.4.0__py3-none-any.whl → 2.5.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.
- roms_tools/__init__.py +1 -1
- roms_tools/analysis/roms_output.py +77 -98
- roms_tools/plot.py +4 -2
- roms_tools/setup/boundary_forcing.py +29 -15
- roms_tools/setup/datasets.py +91 -32
- roms_tools/setup/grid.py +4 -5
- roms_tools/setup/initial_conditions.py +7 -6
- roms_tools/setup/nesting.py +237 -63
- roms_tools/setup/river_forcing.py +243 -72
- roms_tools/setup/surface_forcing.py +26 -15
- roms_tools/setup/tides.py +3 -6
- roms_tools/setup/topography.py +25 -2
- roms_tools/setup/utils.py +28 -12
- roms_tools/tests/test_analysis/test_roms_output.py +233 -70
- 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 +587 -269
- roms_tools/tests/test_setup/test_surface_forcing.py +47 -0
- roms_tools/tests/test_setup/test_validation.py +34 -2
- {roms_tools-2.4.0.dist-info → roms_tools-2.5.0.dist-info}/METADATA +1 -1
- {roms_tools-2.4.0.dist-info → roms_tools-2.5.0.dist-info}/RECORD +208 -202
- {roms_tools-2.4.0.dist-info → roms_tools-2.5.0.dist-info}/WHEEL +1 -1
- {roms_tools-2.4.0.dist-info → roms_tools-2.5.0.dist-info}/LICENSE +0 -0
- {roms_tools-2.4.0.dist-info → roms_tools-2.5.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
|
|
@@ -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,40 +95,47 @@ 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
|
+
object.__setattr__(self, "original_indices", original_indices)
|
|
122
|
+
updated_indices = self._move_rivers_to_closest_coast(target_coords, data)
|
|
123
|
+
object.__setattr__(self, "indices", updated_indices)
|
|
92
124
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
ds
|
|
97
|
-
self.
|
|
125
|
+
else:
|
|
126
|
+
logging.info("Use provided river indices.")
|
|
127
|
+
object.__setattr__(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
|
+
object.__setattr__(self, "ds", ds)
|
|
110
139
|
|
|
111
140
|
def _input_checks(self):
|
|
112
141
|
if self.source is None:
|
|
@@ -125,6 +154,63 @@ class RiverForcing:
|
|
|
125
154
|
{**self.source, "climatology": self.source.get("climatology", False)},
|
|
126
155
|
)
|
|
127
156
|
|
|
157
|
+
# Check if 'indices' is provided and has the correct format
|
|
158
|
+
if self.indices is not None:
|
|
159
|
+
if not isinstance(self.indices, dict):
|
|
160
|
+
raise ValueError("`indices` must be a dictionary.")
|
|
161
|
+
|
|
162
|
+
# Ensure the dictionary contains at least one river
|
|
163
|
+
if len(self.indices) == 0:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"The provided 'indices' dictionary must contain at least one river."
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
for river_name, river_data in self.indices.items():
|
|
169
|
+
if not isinstance(river_name, str):
|
|
170
|
+
raise ValueError(f"River name `{river_name}` must be a string.")
|
|
171
|
+
|
|
172
|
+
if not isinstance(river_data, list):
|
|
173
|
+
raise ValueError(
|
|
174
|
+
f"Data for river `{river_name}` must be a list of tuples."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Ensure each element in the list is a tuple of length 2
|
|
178
|
+
seen_tuples = set()
|
|
179
|
+
for idx_pair in river_data:
|
|
180
|
+
if not isinstance(idx_pair, tuple) or len(idx_pair) != 2:
|
|
181
|
+
raise ValueError(
|
|
182
|
+
f"Each item for river `{river_name}` must be a tuple of length 2 representing (eta_rho, xi_rho)."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
eta_rho, xi_rho = idx_pair
|
|
186
|
+
|
|
187
|
+
# Ensure both eta_rho and xi_rho are integers
|
|
188
|
+
if not isinstance(eta_rho, int):
|
|
189
|
+
raise ValueError(
|
|
190
|
+
f"First element of tuple for river `{river_name}` must be an integer (eta_rho), but got {type(eta_rho)}."
|
|
191
|
+
)
|
|
192
|
+
if not isinstance(xi_rho, int):
|
|
193
|
+
raise ValueError(
|
|
194
|
+
f"Second element of tuple for river `{river_name}` must be an integer (xi_rho), but got {type(xi_rho)}."
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Check that eta_rho and xi_rho are within the valid range
|
|
198
|
+
if not (0 <= eta_rho < len(self.grid.ds.eta_rho)):
|
|
199
|
+
raise ValueError(
|
|
200
|
+
f"Value of eta_rho for river `{river_name}` ({eta_rho}) is out of valid range [0, {len(self.grid.ds.eta_rho)-1}]."
|
|
201
|
+
)
|
|
202
|
+
if not (0 <= xi_rho < len(self.grid.ds.xi_rho)):
|
|
203
|
+
raise ValueError(
|
|
204
|
+
f"Value of xi_rho for river `{river_name}` ({xi_rho}) is out of valid range [0, {len(self.grid.ds.xi_rho)-1}]."
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Check for duplicate tuples
|
|
208
|
+
if idx_pair in seen_tuples:
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"Duplicate location {idx_pair} found for river `{river_name}`."
|
|
211
|
+
)
|
|
212
|
+
seen_tuples.add(idx_pair)
|
|
213
|
+
|
|
128
214
|
def _get_data(self):
|
|
129
215
|
|
|
130
216
|
data_dict = {
|
|
@@ -197,13 +283,19 @@ class RiverForcing:
|
|
|
197
283
|
river_volume = river_volume.rename(
|
|
198
284
|
{data.dim_names["time"]: "river_time", data.dim_names["station"]: "nriver"}
|
|
199
285
|
)
|
|
286
|
+
|
|
200
287
|
name = data.ds[data.var_names["name"]].rename(
|
|
201
288
|
{data.dim_names["station"]: "nriver"}
|
|
202
289
|
)
|
|
203
290
|
name.attrs["long_name"] = "River name"
|
|
204
291
|
river_volume.coords["river_name"] = name
|
|
292
|
+
|
|
205
293
|
ds["river_volume"] = river_volume
|
|
206
294
|
|
|
295
|
+
nriver = xr.DataArray(np.arange(1, len(ds.nriver) + 1), dims="nriver")
|
|
296
|
+
nriver.attrs["long_name"] = "River ID (1-based Fortran indexing)"
|
|
297
|
+
ds = ds.assign_coords({"nriver": nriver})
|
|
298
|
+
|
|
207
299
|
if self.include_bgc:
|
|
208
300
|
ntracers = 2 + 32
|
|
209
301
|
else:
|
|
@@ -272,15 +364,13 @@ class RiverForcing:
|
|
|
272
364
|
|
|
273
365
|
ds = ds.assign_coords({"river_time": time})
|
|
274
366
|
|
|
275
|
-
ds = ds.drop_vars("nriver")
|
|
276
|
-
|
|
277
367
|
return ds
|
|
278
368
|
|
|
279
369
|
def _move_rivers_to_closest_coast(self, target_coords, data):
|
|
280
370
|
"""Move river mouths to the closest coastal grid cell.
|
|
281
371
|
|
|
282
372
|
This method computes the closest coastal grid point to each river mouth
|
|
283
|
-
based on geographical distance.
|
|
373
|
+
based on geographical distance. It identifies the nearest grid point on the coast and returns the updated river mouth indices.
|
|
284
374
|
|
|
285
375
|
Parameters:
|
|
286
376
|
-----------
|
|
@@ -296,11 +386,11 @@ class RiverForcing:
|
|
|
296
386
|
- `var_names`: A dictionary of variable names in the dataset (e.g., longitude, latitude, station names).
|
|
297
387
|
- `dim_names`: A dictionary containing dimension names for the dataset (e.g., "station", "eta_rho", "xi_rho").
|
|
298
388
|
|
|
299
|
-
Returns
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
of
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
indices : dict[str, list[tuple]]
|
|
392
|
+
A dictionary consisting of river names as keys, and each value is a list of tuples. Each tuple represents
|
|
393
|
+
a pair of indices corresponding to the `eta_rho` and `xi_rho` grid coordinates of the river.
|
|
304
394
|
"""
|
|
305
395
|
|
|
306
396
|
# Retrieve longitude and latitude of river mouths
|
|
@@ -333,27 +423,29 @@ class RiverForcing:
|
|
|
333
423
|
|
|
334
424
|
# Find the indices of the closest coastal grid cell to the river mouth
|
|
335
425
|
indices = np.where(dist_coast == dist_coast_min)
|
|
426
|
+
stations = indices[0]
|
|
427
|
+
eta_rho_values = indices[1]
|
|
428
|
+
xi_rho_values = indices[2]
|
|
336
429
|
names = (
|
|
337
430
|
data.ds[data.var_names["name"]]
|
|
338
|
-
.isel({data.dim_names["station"]:
|
|
431
|
+
.isel({data.dim_names["station"]: stations})
|
|
339
432
|
.values
|
|
340
433
|
)
|
|
341
|
-
|
|
342
434
|
# Return the indices in a dictionary format
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
435
|
+
river_indices = {}
|
|
436
|
+
for i in range(len(stations)):
|
|
437
|
+
river_name = names[i]
|
|
438
|
+
river_indices[river_name] = [
|
|
439
|
+
(int(eta_rho_values[i]), int(xi_rho_values[i]))
|
|
440
|
+
] # list of tuples
|
|
441
|
+
|
|
442
|
+
return river_indices
|
|
350
443
|
|
|
351
444
|
def _write_indices_into_dataset(self, ds):
|
|
352
445
|
"""Adds river location indices to the dataset as the "river_location" variable.
|
|
353
446
|
|
|
354
|
-
This method
|
|
355
|
-
|
|
356
|
-
using river station indices from `self.updated_indices` and assigns it to the dataset.
|
|
447
|
+
This method creates a new "river_location" variable
|
|
448
|
+
using river station indices from `self.indices` and assigns it to the dataset.
|
|
357
449
|
The indices specify the river station locations in terms of eta_rho and xi_rho grid cell indices.
|
|
358
450
|
|
|
359
451
|
Parameters
|
|
@@ -367,17 +459,21 @@ class RiverForcing:
|
|
|
367
459
|
The modified dataset with the "river_location" variable added.
|
|
368
460
|
"""
|
|
369
461
|
|
|
370
|
-
|
|
371
|
-
|
|
462
|
+
river_locations = xr.zeros_like(self.grid.ds.h)
|
|
463
|
+
|
|
464
|
+
for nriver in ds.nriver:
|
|
465
|
+
river_name = str(ds.river_name.sel(nriver=nriver).values)
|
|
466
|
+
indices = self.indices[river_name]
|
|
467
|
+
fraction = 1.0 / len(indices)
|
|
372
468
|
|
|
373
|
-
|
|
374
|
-
for i in range(len(self.updated_indices["name"])):
|
|
375
|
-
station = self.updated_indices["station"][i]
|
|
376
|
-
eta_index = self.updated_indices["eta_rho"][i]
|
|
377
|
-
xi_index = self.updated_indices["xi_rho"][i]
|
|
378
|
-
river_locations[eta_index, xi_index] = station + 2
|
|
469
|
+
for eta_index, xi_index in indices:
|
|
379
470
|
|
|
380
|
-
|
|
471
|
+
river_locations[eta_index, xi_index] = (
|
|
472
|
+
nriver # assign unique nriver ID (Fortran-based indexing)
|
|
473
|
+
+ fraction # Fractional contribution for multiple grid points
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
river_locations.attrs["long_name"] = "River ID plus local volume fraction"
|
|
381
477
|
river_locations.attrs["units"] = "none"
|
|
382
478
|
ds["river_location"] = river_locations
|
|
383
479
|
|
|
@@ -463,26 +559,46 @@ class RiverForcing:
|
|
|
463
559
|
"color": "black",
|
|
464
560
|
} # Customize latitude label style
|
|
465
561
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
562
|
+
proj = ccrs.PlateCarree()
|
|
563
|
+
|
|
564
|
+
if len(self.indices) <= 10:
|
|
565
|
+
color_map = cm.get_cmap("tab10")
|
|
566
|
+
elif len(self.indices) <= 20:
|
|
567
|
+
color_map = cm.get_cmap("tab20")
|
|
568
|
+
else:
|
|
569
|
+
color_map = cm.get_cmap("tab20b")
|
|
570
|
+
# Create a dictionary of colors
|
|
571
|
+
colors = {name: color_map(i) for i, name in enumerate(self.indices.keys())}
|
|
572
|
+
|
|
573
|
+
for ax, indices in zip(axs, [self.original_indices, self.indices]):
|
|
574
|
+
added_labels = set()
|
|
575
|
+
for name in indices.keys():
|
|
576
|
+
for tuple in indices[name]:
|
|
577
|
+
eta_index = tuple[0]
|
|
578
|
+
xi_index = tuple[1]
|
|
579
|
+
|
|
580
|
+
# transform coordinates to projected space
|
|
581
|
+
transformed_lon, transformed_lat = trans.transform_point(
|
|
582
|
+
self.grid.ds.lon_rho[eta_index, xi_index],
|
|
583
|
+
self.grid.ds.lat_rho[eta_index, xi_index],
|
|
584
|
+
proj,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
if name not in added_labels:
|
|
588
|
+
added_labels.add(name)
|
|
589
|
+
label = name
|
|
590
|
+
else:
|
|
591
|
+
label = "_None"
|
|
592
|
+
|
|
593
|
+
ax.plot(
|
|
594
|
+
transformed_lon,
|
|
595
|
+
transformed_lat,
|
|
596
|
+
marker="x",
|
|
597
|
+
markersize=8,
|
|
598
|
+
markeredgewidth=2,
|
|
599
|
+
label=label,
|
|
600
|
+
color=colors[name],
|
|
601
|
+
)
|
|
486
602
|
|
|
487
603
|
axs[0].set_title("Original river locations")
|
|
488
604
|
axs[1].set_title("Updated river locations")
|
|
@@ -647,4 +763,59 @@ class RiverForcing:
|
|
|
647
763
|
grid = Grid.from_yaml(filepath)
|
|
648
764
|
params = _from_yaml(cls, filepath)
|
|
649
765
|
|
|
766
|
+
def convert_indices_format(indices):
|
|
767
|
+
# Remove the '_convention' key from the dictionary if present
|
|
768
|
+
indices = {
|
|
769
|
+
key: value for key, value in indices.items() if key != "_convention"
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
# Convert the string of indices into tuples
|
|
773
|
+
for river, index_list in indices.items():
|
|
774
|
+
# Split the string by ',' and convert to tuples of integers
|
|
775
|
+
indices[river] = [tuple(map(int, idx.split(","))) for idx in index_list]
|
|
776
|
+
|
|
777
|
+
return indices
|
|
778
|
+
|
|
779
|
+
params["indices"] = convert_indices_format(params["indices"])
|
|
780
|
+
|
|
650
781
|
return cls(grid=grid, **params)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def check_river_locations_are_along_coast(mask, indices):
|
|
785
|
+
"""Check if the river locations are along the coast.
|
|
786
|
+
|
|
787
|
+
This function checks if the river locations specified in the `indices` dictionary are located on coastal grid cells.
|
|
788
|
+
A coastal grid cell is defined as a land grid cell adjacent to an ocean grid cell.
|
|
789
|
+
|
|
790
|
+
Parameters
|
|
791
|
+
----------
|
|
792
|
+
mask : xarray.DataArray
|
|
793
|
+
A mask representing the land and ocean cells in the grid, where 1 represents ocean and 0 represents land.
|
|
794
|
+
|
|
795
|
+
indices : dict
|
|
796
|
+
A dictionary where the keys are river names, and the values are dictionaries containing the river's grid locations (`eta_rho` and `xi_rho`).
|
|
797
|
+
Each entry should have keys `"eta_rho"` and `"xi_rho"`, which are lists of grid cell indices representing river mouth locations.
|
|
798
|
+
|
|
799
|
+
Raises
|
|
800
|
+
------
|
|
801
|
+
ValueError
|
|
802
|
+
If any river is not located on the coast.
|
|
803
|
+
"""
|
|
804
|
+
|
|
805
|
+
faces = (
|
|
806
|
+
mask.shift(eta_rho=1)
|
|
807
|
+
+ mask.shift(eta_rho=-1)
|
|
808
|
+
+ mask.shift(xi_rho=1)
|
|
809
|
+
+ mask.shift(xi_rho=-1)
|
|
810
|
+
)
|
|
811
|
+
coast = (1 - mask) * (faces > 0)
|
|
812
|
+
|
|
813
|
+
for key, river_data in indices.items():
|
|
814
|
+
for idx_pair in river_data:
|
|
815
|
+
eta_rho, xi_rho = idx_pair
|
|
816
|
+
|
|
817
|
+
# Check if the river location is along the coast
|
|
818
|
+
if not coast[eta_rho, xi_rho]:
|
|
819
|
+
raise ValueError(
|
|
820
|
+
f"River `{key}` is not located on the coast at grid cell ({eta_rho}, {xi_rho})."
|
|
821
|
+
)
|
|
@@ -6,7 +6,7 @@ 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
12
|
from roms_tools.regrid import LateralRegrid
|
|
@@ -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
|
|
@@ -126,6 +130,8 @@ class SurfaceForcing:
|
|
|
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
|
|
|
@@ -168,6 +174,18 @@ class SurfaceForcing:
|
|
|
168
174
|
object.__setattr__(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'.")
|
|
@@ -606,7 +624,6 @@ class SurfaceForcing:
|
|
|
606
624
|
cls,
|
|
607
625
|
filepath: Union[str, Path],
|
|
608
626
|
use_dask: bool = False,
|
|
609
|
-
bypass_validation: bool = False,
|
|
610
627
|
) -> "SurfaceForcing":
|
|
611
628
|
"""Create an instance of the SurfaceForcing class from a YAML file.
|
|
612
629
|
|
|
@@ -616,10 +633,6 @@ class SurfaceForcing:
|
|
|
616
633
|
The path to the YAML file from which the parameters will be read.
|
|
617
634
|
use_dask: bool, optional
|
|
618
635
|
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
636
|
|
|
624
637
|
Returns
|
|
625
638
|
-------
|
|
@@ -631,6 +644,4 @@ class SurfaceForcing:
|
|
|
631
644
|
grid = Grid.from_yaml(filepath)
|
|
632
645
|
params = _from_yaml(cls, filepath)
|
|
633
646
|
|
|
634
|
-
return cls(
|
|
635
|
-
grid=grid, **params, use_dask=use_dask, bypass_validation=bypass_validation
|
|
636
|
-
)
|
|
647
|
+
return cls(grid=grid, **params, use_dask=use_dask)
|
roms_tools/setup/tides.py
CHANGED
|
@@ -84,6 +84,9 @@ class TidalForcing:
|
|
|
84
84
|
target_coords,
|
|
85
85
|
buffer_points=20,
|
|
86
86
|
)
|
|
87
|
+
# Enforce double precision to ensure reproducibility
|
|
88
|
+
data.convert_to_float64()
|
|
89
|
+
|
|
87
90
|
# select desired number of constituents
|
|
88
91
|
object.__setattr__(data, "ds", data.ds.isel(ntides=slice(None, self.ntides)))
|
|
89
92
|
self._correct_tides(data)
|
|
@@ -416,7 +419,6 @@ class TidalForcing:
|
|
|
416
419
|
cls,
|
|
417
420
|
filepath: Union[str, Path],
|
|
418
421
|
use_dask: bool = False,
|
|
419
|
-
bypass_validation: bool = False,
|
|
420
422
|
) -> "TidalForcing":
|
|
421
423
|
"""Create an instance of the TidalForcing class from a YAML file.
|
|
422
424
|
|
|
@@ -426,10 +428,6 @@ class TidalForcing:
|
|
|
426
428
|
The path to the YAML file from which the parameters will be read.
|
|
427
429
|
use_dask: bool, optional
|
|
428
430
|
Indicates whether to use dask for processing. If True, data is processed with dask; if False, data is processed eagerly. Defaults to False.
|
|
429
|
-
bypass_validation: bool, optional
|
|
430
|
-
Indicates whether to skip validation checks in the processed data. When set to True,
|
|
431
|
-
the validation process that ensures no NaN values exist at wet points
|
|
432
|
-
in the processed dataset is bypassed. Defaults to False.
|
|
433
431
|
|
|
434
432
|
Returns
|
|
435
433
|
-------
|
|
@@ -444,7 +442,6 @@ class TidalForcing:
|
|
|
444
442
|
grid=grid,
|
|
445
443
|
**tidal_forcing_params,
|
|
446
444
|
use_dask=use_dask,
|
|
447
|
-
bypass_validation=bypass_validation,
|
|
448
445
|
)
|
|
449
446
|
|
|
450
447
|
def _correct_tides(self, data):
|
roms_tools/setup/topography.py
CHANGED
|
@@ -145,6 +145,8 @@ def _make_raw_topography(
|
|
|
145
145
|
The regridded topography data with the sign flipped (bathymetry positive).
|
|
146
146
|
"""
|
|
147
147
|
data.choose_subdomain(target_coords, buffer_points=3, verbose=verbose)
|
|
148
|
+
# Enforce double precision to ensure reproducibility
|
|
149
|
+
data.convert_to_float64()
|
|
148
150
|
|
|
149
151
|
if verbose:
|
|
150
152
|
start_time = time.time()
|
|
@@ -233,7 +235,7 @@ def _smooth_topography_locally(h, hmin=5, rmax=0.2):
|
|
|
233
235
|
rmax_log = 0.0
|
|
234
236
|
|
|
235
237
|
# Apply hmin threshold
|
|
236
|
-
h =
|
|
238
|
+
h = _clip_depth(h, hmin)
|
|
237
239
|
|
|
238
240
|
# Perform logarithmic transformation of the height field
|
|
239
241
|
h_log = np.log(h / hmin)
|
|
@@ -316,7 +318,7 @@ def _smooth_topography_locally(h, hmin=5, rmax=0.2):
|
|
|
316
318
|
h = hmin * np.exp(h_log)
|
|
317
319
|
|
|
318
320
|
# Apply hmin threshold again
|
|
319
|
-
h =
|
|
321
|
+
h = _clip_depth(h, hmin)
|
|
320
322
|
|
|
321
323
|
# Compute maximum slope parameter r
|
|
322
324
|
r_eta, r_xi = _compute_rfactor(h)
|
|
@@ -327,6 +329,27 @@ def _smooth_topography_locally(h, hmin=5, rmax=0.2):
|
|
|
327
329
|
return h
|
|
328
330
|
|
|
329
331
|
|
|
332
|
+
def _clip_depth(h: xr.DataArray, hmin: float) -> xr.DataArray:
|
|
333
|
+
"""Ensures that depth values do not fall below a minimum threshold.
|
|
334
|
+
|
|
335
|
+
This function replaces all depth values in `h` that are less than `hmin` with `hmin`,
|
|
336
|
+
ensuring a minimum depth constraint.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
h : xr.DataArray
|
|
341
|
+
The depth (bathymetry) array.
|
|
342
|
+
hmin : float
|
|
343
|
+
The minimum allowable depth value.
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
xr.DataArray
|
|
348
|
+
The modified depth array with values clipped at `hmin`.
|
|
349
|
+
"""
|
|
350
|
+
return xr.where(h < hmin, hmin, h)
|
|
351
|
+
|
|
352
|
+
|
|
330
353
|
def _compute_rfactor(h):
|
|
331
354
|
"""Computes the slope parameter (r-factor) in both horizontal directions.
|
|
332
355
|
|