roms-tools 3.1.2__py3-none-any.whl → 3.3.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 +3 -0
- roms_tools/analysis/cdr_analysis.py +203 -0
- roms_tools/analysis/cdr_ensemble.py +198 -0
- roms_tools/analysis/roms_output.py +80 -46
- roms_tools/data/grids/GLORYS_global_grid.nc +0 -0
- roms_tools/download.py +4 -0
- roms_tools/plot.py +113 -51
- roms_tools/setup/boundary_forcing.py +45 -20
- roms_tools/setup/cdr_forcing.py +122 -8
- roms_tools/setup/cdr_release.py +161 -8
- roms_tools/setup/grid.py +150 -141
- roms_tools/setup/initial_conditions.py +113 -48
- roms_tools/setup/{datasets.py → lat_lon_datasets.py} +443 -938
- roms_tools/setup/mask.py +63 -7
- roms_tools/setup/nesting.py +314 -117
- roms_tools/setup/river_datasets.py +527 -0
- roms_tools/setup/river_forcing.py +46 -20
- roms_tools/setup/surface_forcing.py +7 -9
- roms_tools/setup/tides.py +2 -3
- roms_tools/setup/topography.py +8 -10
- roms_tools/setup/utils.py +396 -23
- roms_tools/tests/test_analysis/test_cdr_analysis.py +144 -0
- roms_tools/tests/test_analysis/test_cdr_ensemble.py +202 -0
- roms_tools/tests/test_analysis/test_roms_output.py +61 -3
- roms_tools/tests/test_setup/test_boundary_forcing.py +54 -52
- roms_tools/tests/test_setup/test_cdr_forcing.py +54 -0
- roms_tools/tests/test_setup/test_cdr_release.py +118 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zarr.json +406 -406
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_south/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_south/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_south/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_east/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_north/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_south/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_west/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_east/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_north/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_south/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_west/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_east/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_north/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_south/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_west/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zarr.json +182 -182
- roms_tools/tests/test_setup/test_data/grid.zarr/h/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/zarr.json +191 -191
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/c/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/zarr.json +210 -210
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOCr/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DON/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DONr/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOPr/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Fe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Lig/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NH4/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NO3/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/O2/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/PO4/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatChl/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatFe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatSi/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazChl/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazFe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/salt/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spCaCO3/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spChl/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spFe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/temp/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/u/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ubar/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/v/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/vbar/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zarr.json +182 -182
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zooC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/salt/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/temp/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/u/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ubar/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/v/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/vbar/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/zarr.json +187 -187
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Im/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Re/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Im/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Re/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/zarr.json +66 -66
- roms_tools/tests/test_setup/test_grid.py +236 -115
- roms_tools/tests/test_setup/test_initial_conditions.py +94 -41
- roms_tools/tests/test_setup/{test_datasets.py → test_lat_lon_datasets.py} +409 -100
- roms_tools/tests/test_setup/test_nesting.py +119 -31
- roms_tools/tests/test_setup/test_river_datasets.py +48 -0
- roms_tools/tests/test_setup/test_surface_forcing.py +2 -1
- roms_tools/tests/test_setup/test_utils.py +92 -2
- roms_tools/tests/test_setup/utils.py +71 -0
- roms_tools/tests/test_tiling/test_join.py +241 -0
- roms_tools/tests/test_utils.py +139 -17
- roms_tools/tiling/join.py +189 -0
- roms_tools/utils.py +131 -99
- {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/METADATA +12 -2
- {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/RECORD +221 -211
- {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/WHEEL +0 -0
- {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/licenses/LICENSE +0 -0
- {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/top_level.txt +0 -0
roms_tools/plot.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Any, Literal
|
|
2
2
|
|
|
3
3
|
import cartopy.crs as ccrs
|
|
4
|
+
import matplotlib.dates as mdates
|
|
4
5
|
import matplotlib.pyplot as plt
|
|
5
6
|
import numpy as np
|
|
6
7
|
import xarray as xr
|
|
@@ -9,18 +10,16 @@ from matplotlib.figure import Figure
|
|
|
9
10
|
|
|
10
11
|
from roms_tools.regrid import LateralRegridFromROMS, VerticalRegridFromROMS
|
|
11
12
|
from roms_tools.utils import (
|
|
12
|
-
|
|
13
|
-
_remove_edge_nans,
|
|
13
|
+
generate_coordinate_range,
|
|
14
14
|
infer_nominal_horizontal_resolution,
|
|
15
15
|
normalize_longitude,
|
|
16
|
+
remove_edge_nans,
|
|
16
17
|
)
|
|
17
18
|
from roms_tools.vertical_coordinate import compute_depth_coordinates
|
|
18
19
|
|
|
19
20
|
LABEL_COLOR = "k"
|
|
20
21
|
LABEL_SZ = 10
|
|
21
22
|
FONT_SZ = 10
|
|
22
|
-
EDGE_POS_START = "start"
|
|
23
|
-
EDGE_POS_END = "end"
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
def _add_gridlines(ax: Axes) -> None:
|
|
@@ -97,12 +96,6 @@ def plot_2d_horizontal_field(
|
|
|
97
96
|
lon_deg = field.lon
|
|
98
97
|
lat_deg = field.lat
|
|
99
98
|
|
|
100
|
-
# check if North or South pole are in domain
|
|
101
|
-
if lat_deg.max().values > 89 or lat_deg.min().values < -89:
|
|
102
|
-
raise NotImplementedError(
|
|
103
|
-
"Plotting is not implemented for the case that the domain contains the North or South pole."
|
|
104
|
-
)
|
|
105
|
-
|
|
106
99
|
trans = get_projection(lon_deg, lat_deg)
|
|
107
100
|
|
|
108
101
|
if ax is None:
|
|
@@ -187,25 +180,8 @@ def plot_nesting(parent_grid_ds, child_grid_ds, parent_straddle, with_dim_names=
|
|
|
187
180
|
with_dim_names=with_dim_names,
|
|
188
181
|
)
|
|
189
182
|
|
|
190
|
-
vmax = 3
|
|
191
|
-
vmin = 0
|
|
192
|
-
cmap = plt.colormaps.get_cmap("Blues")
|
|
193
|
-
cmap.set_bad(color="gray")
|
|
194
|
-
kwargs = {"vmax": vmax, "vmin": vmin, "cmap": cmap}
|
|
195
|
-
|
|
196
|
-
field = parent_grid_ds.mask_rho
|
|
197
|
-
field = field.where(field)
|
|
198
|
-
|
|
199
|
-
_add_field_to_ax(
|
|
200
|
-
ax,
|
|
201
|
-
parent_lon_deg,
|
|
202
|
-
parent_lat_deg,
|
|
203
|
-
field,
|
|
204
|
-
add_colorbar=False,
|
|
205
|
-
kwargs=kwargs,
|
|
206
|
-
)
|
|
207
|
-
|
|
208
183
|
_add_gridlines(ax)
|
|
184
|
+
ax.coastlines()
|
|
209
185
|
|
|
210
186
|
ax.legend(loc="best")
|
|
211
187
|
|
|
@@ -497,16 +473,16 @@ def line_plot(
|
|
|
497
473
|
|
|
498
474
|
|
|
499
475
|
def _get_edge(
|
|
500
|
-
arr: xr.DataArray, dim_name: str, pos: Literal[
|
|
476
|
+
arr: xr.DataArray, dim_name: str, pos: Literal["start", "end"]
|
|
501
477
|
) -> xr.DataArray:
|
|
502
478
|
"""Extract the first ("start") or last ("end") slice along the given dimension."""
|
|
503
|
-
if pos ==
|
|
479
|
+
if pos == "start":
|
|
504
480
|
return arr.isel({dim_name: 0})
|
|
505
481
|
|
|
506
|
-
if pos ==
|
|
482
|
+
if pos == "end":
|
|
507
483
|
return arr.isel({dim_name: -1})
|
|
508
484
|
|
|
509
|
-
raise ValueError(
|
|
485
|
+
raise ValueError("pos must be `start` or `end`")
|
|
510
486
|
|
|
511
487
|
|
|
512
488
|
def _add_boundary_to_ax(
|
|
@@ -538,23 +514,23 @@ def _add_boundary_to_ax(
|
|
|
538
514
|
|
|
539
515
|
edges = [
|
|
540
516
|
(
|
|
541
|
-
_get_edge(lon_deg, xi_dim,
|
|
542
|
-
_get_edge(lat_deg, xi_dim,
|
|
517
|
+
_get_edge(lon_deg, xi_dim, "start"),
|
|
518
|
+
_get_edge(lat_deg, xi_dim, "start"),
|
|
543
519
|
r"$\eta$",
|
|
544
520
|
), # left
|
|
545
521
|
(
|
|
546
|
-
_get_edge(lon_deg, xi_dim,
|
|
547
|
-
_get_edge(lat_deg, xi_dim,
|
|
522
|
+
_get_edge(lon_deg, xi_dim, "end"),
|
|
523
|
+
_get_edge(lat_deg, xi_dim, "end"),
|
|
548
524
|
r"$\eta$",
|
|
549
525
|
), # right
|
|
550
526
|
(
|
|
551
|
-
_get_edge(lon_deg, eta_dim,
|
|
552
|
-
_get_edge(lat_deg, eta_dim,
|
|
527
|
+
_get_edge(lon_deg, eta_dim, "start"),
|
|
528
|
+
_get_edge(lat_deg, eta_dim, "start"),
|
|
553
529
|
r"$\xi$",
|
|
554
530
|
), # bottom
|
|
555
531
|
(
|
|
556
|
-
_get_edge(lon_deg, eta_dim,
|
|
557
|
-
_get_edge(lat_deg, eta_dim,
|
|
532
|
+
_get_edge(lon_deg, eta_dim, "end"),
|
|
533
|
+
_get_edge(lat_deg, eta_dim, "end"),
|
|
558
534
|
r"$\xi$",
|
|
559
535
|
), # top
|
|
560
536
|
]
|
|
@@ -660,7 +636,7 @@ def _add_field_to_ax(
|
|
|
660
636
|
proj = ccrs.PlateCarree()
|
|
661
637
|
|
|
662
638
|
p = ax.pcolormesh(lon_deg, lat_deg, field, transform=proj, **kwargs)
|
|
663
|
-
if hasattr(field, "long_name"):
|
|
639
|
+
if hasattr(field, "long_name") and hasattr(field, "units"):
|
|
664
640
|
label = f"{field.long_name} [{field.units}]"
|
|
665
641
|
elif hasattr(field, "Long_name"):
|
|
666
642
|
# this is the case for matlab generated grids
|
|
@@ -675,10 +651,36 @@ def _add_field_to_ax(
|
|
|
675
651
|
ax.clabel(cs, inline=True, fontsize=FONT_SZ)
|
|
676
652
|
|
|
677
653
|
|
|
678
|
-
def get_projection(lon, lat):
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
654
|
+
def get_projection(lon: xr.DataArray, lat: xr.DataArray):
|
|
655
|
+
"""
|
|
656
|
+
Return a Cartopy projection appropriate for the given lon/lat domain.
|
|
657
|
+
|
|
658
|
+
- Raises an error if the domain includes either pole.
|
|
659
|
+
- Uses PlateCarree if the longitudinal span > 90°.
|
|
660
|
+
- Otherwise uses NearsidePerspective centered on the domain.
|
|
661
|
+
|
|
662
|
+
Parameters
|
|
663
|
+
----------
|
|
664
|
+
lon, lat : xr.DataArray
|
|
665
|
+
Longitude and latitude values in degrees.
|
|
666
|
+
|
|
667
|
+
Returns
|
|
668
|
+
-------
|
|
669
|
+
cartopy.crs.Projection
|
|
670
|
+
The chosen Cartopy projection.
|
|
671
|
+
"""
|
|
672
|
+
# check if North or South pole are in domain
|
|
673
|
+
if lat.max() > 89 or lat.min() < -89:
|
|
674
|
+
raise NotImplementedError(
|
|
675
|
+
"Plotting is not implemented for the case that the domain contains the North or South pole."
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
if lon.max() - lon.min() > 90:
|
|
679
|
+
return ccrs.PlateCarree(central_longitude=lon.mean().values)
|
|
680
|
+
else:
|
|
681
|
+
return ccrs.NearsidePerspective(
|
|
682
|
+
central_longitude=lon.mean().values, central_latitude=lat.mean().values
|
|
683
|
+
)
|
|
682
684
|
|
|
683
685
|
|
|
684
686
|
def _validate_plot_inputs(
|
|
@@ -817,9 +819,10 @@ def plot(
|
|
|
817
819
|
yincrease: bool | None = None,
|
|
818
820
|
use_coarse_grid: bool = False,
|
|
819
821
|
with_dim_names: bool = False,
|
|
822
|
+
apply_mask: bool = True,
|
|
820
823
|
ax: Axes | None = None,
|
|
821
824
|
save_path: str | None = None,
|
|
822
|
-
cmap_name: str
|
|
825
|
+
cmap_name: str = "YlOrRd",
|
|
823
826
|
add_colorbar: bool = True,
|
|
824
827
|
) -> None:
|
|
825
828
|
"""Generate a plot of a 2D or 3D ROMS field for a horizontal or vertical slice.
|
|
@@ -890,6 +893,9 @@ def plot(
|
|
|
890
893
|
with_dim_names : bool, optional
|
|
891
894
|
Add grid dimension names (`xi`, `eta`) to the outer plot edges. Only for 2D plots. Default is False.
|
|
892
895
|
|
|
896
|
+
apply_mask: bool, optional
|
|
897
|
+
Whether to apply the land mask to the field. Default is True.
|
|
898
|
+
|
|
893
899
|
ax : Axes, optional
|
|
894
900
|
Matplotlib axes object. If None, a new figure is created. Default is None.
|
|
895
901
|
|
|
@@ -972,7 +978,8 @@ def plot(
|
|
|
972
978
|
field = field.assign_coords({"lon": lon_deg, "lat": lat_deg})
|
|
973
979
|
|
|
974
980
|
# Mask the field
|
|
975
|
-
|
|
981
|
+
if apply_mask:
|
|
982
|
+
field = field.where(mask)
|
|
976
983
|
|
|
977
984
|
# Assign eta and xi as coordinates
|
|
978
985
|
coords_to_assign = {dim: field[dim] for dim in horizontal_dims.values()}
|
|
@@ -1052,7 +1059,7 @@ def plot(
|
|
|
1052
1059
|
title = title + f", lat = {lat}°N"
|
|
1053
1060
|
else:
|
|
1054
1061
|
resolution = infer_nominal_horizontal_resolution(grid_ds)
|
|
1055
|
-
lats =
|
|
1062
|
+
lats = generate_coordinate_range(
|
|
1056
1063
|
field.lat.min().values, field.lat.max().values, resolution
|
|
1057
1064
|
)
|
|
1058
1065
|
lats = xr.DataArray(lats, dims=["lat"], attrs={"units": "°N"})
|
|
@@ -1062,7 +1069,7 @@ def plot(
|
|
|
1062
1069
|
title = title + f", lon = {lon}°E"
|
|
1063
1070
|
else:
|
|
1064
1071
|
resolution = infer_nominal_horizontal_resolution(grid_ds, lat)
|
|
1065
|
-
lons =
|
|
1072
|
+
lons = generate_coordinate_range(
|
|
1066
1073
|
field.lon.min().values, field.lon.max().values, resolution
|
|
1067
1074
|
)
|
|
1068
1075
|
lons = xr.DataArray(lons, dims=["lon"], attrs={"units": "°E"})
|
|
@@ -1079,11 +1086,11 @@ def plot(
|
|
|
1079
1086
|
field = field.assign_coords({"layer_depth": layer_depth})
|
|
1080
1087
|
|
|
1081
1088
|
if lat is not None:
|
|
1082
|
-
field, layer_depth =
|
|
1089
|
+
field, layer_depth = remove_edge_nans(
|
|
1083
1090
|
field, "lon", layer_depth if "layer_depth" in locals() else None
|
|
1084
1091
|
)
|
|
1085
1092
|
if lon is not None:
|
|
1086
|
-
field, layer_depth =
|
|
1093
|
+
field, layer_depth = remove_edge_nans(
|
|
1087
1094
|
field, "lat", layer_depth if "layer_depth" in locals() else None
|
|
1088
1095
|
)
|
|
1089
1096
|
|
|
@@ -1252,3 +1259,58 @@ def plot_location(
|
|
|
1252
1259
|
|
|
1253
1260
|
if include_legend:
|
|
1254
1261
|
ax.legend(loc="center left", bbox_to_anchor=(1.1, 0.5))
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
def plot_uptake_efficiency(ds: xr.Dataset) -> None:
|
|
1265
|
+
"""
|
|
1266
|
+
Plot Carbon Dioxide Removal (CDR) uptake efficiency over time.
|
|
1267
|
+
|
|
1268
|
+
This function plots two estimates of uptake efficiency stored in the dataset:
|
|
1269
|
+
1. `cdr_efficiency`, computed from CO2 flux differences.
|
|
1270
|
+
2. `cdr_efficiency_from_delta_diff`, computed from DIC differences.
|
|
1271
|
+
|
|
1272
|
+
The x-axis shows absolute time, formatted as YYYY-MM-DD, and the y-axis shows
|
|
1273
|
+
the uptake efficiency values. The plot includes a legend and grid for clarity.
|
|
1274
|
+
|
|
1275
|
+
Parameters
|
|
1276
|
+
----------
|
|
1277
|
+
ds : xarray.Dataset
|
|
1278
|
+
Dataset containing the following variables:
|
|
1279
|
+
- "abs_time": array of timestamps (datetime-like)
|
|
1280
|
+
- "cdr_efficiency": uptake efficiency from flux differences
|
|
1281
|
+
- "cdr_efficiency_from_delta_diff": uptake efficiency from DIC differences
|
|
1282
|
+
|
|
1283
|
+
Raises
|
|
1284
|
+
------
|
|
1285
|
+
ValueError
|
|
1286
|
+
If required variables are missing or empty.
|
|
1287
|
+
|
|
1288
|
+
Returns
|
|
1289
|
+
-------
|
|
1290
|
+
None
|
|
1291
|
+
"""
|
|
1292
|
+
required_vars = ["abs_time", "cdr_efficiency", "cdr_efficiency_from_delta_diff"]
|
|
1293
|
+
for var in required_vars:
|
|
1294
|
+
if var not in ds or ds[var].size == 0:
|
|
1295
|
+
raise ValueError(f"Dataset must contain non-empty variable '{var}'.")
|
|
1296
|
+
|
|
1297
|
+
times = ds["abs_time"]
|
|
1298
|
+
|
|
1299
|
+
# Check for monotonically increasing times
|
|
1300
|
+
if not np.all(times[1:] >= times[:-1]):
|
|
1301
|
+
raise ValueError("abs_time must be strictly increasing.")
|
|
1302
|
+
|
|
1303
|
+
fig, ax = plt.subplots(figsize=(10, 4))
|
|
1304
|
+
|
|
1305
|
+
ax.plot(times, ds["cdr_efficiency"], label="from CO2 flux differences", lw=2)
|
|
1306
|
+
ax.plot(
|
|
1307
|
+
times, ds["cdr_efficiency_from_delta_diff"], label="from DIC differences", lw=2
|
|
1308
|
+
)
|
|
1309
|
+
ax.grid()
|
|
1310
|
+
ax.set_title("CDR uptake efficiency")
|
|
1311
|
+
ax.legend()
|
|
1312
|
+
|
|
1313
|
+
# Format x-axis as YYYY-MM-DD
|
|
1314
|
+
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
|
|
1315
|
+
fig.autofmt_xdate()
|
|
1316
|
+
plt.show()
|
|
@@ -13,14 +13,14 @@ from scipy.ndimage import label
|
|
|
13
13
|
from roms_tools import Grid
|
|
14
14
|
from roms_tools.plot import line_plot, section_plot
|
|
15
15
|
from roms_tools.regrid import LateralRegridToROMS, VerticalRegridToROMS
|
|
16
|
-
from roms_tools.setup.
|
|
16
|
+
from roms_tools.setup.lat_lon_datasets import (
|
|
17
17
|
CESMBGCDataset,
|
|
18
|
-
Dataset,
|
|
19
18
|
GLORYSDataset,
|
|
20
19
|
GLORYSDefaultDataset,
|
|
21
20
|
UnifiedBGCDataset,
|
|
22
21
|
)
|
|
23
22
|
from roms_tools.setup.utils import (
|
|
23
|
+
RawDataSource,
|
|
24
24
|
add_time_info_to_ds,
|
|
25
25
|
compute_barotropic_velocity,
|
|
26
26
|
compute_missing_bgc_variables,
|
|
@@ -63,7 +63,7 @@ class BoundaryForcing:
|
|
|
63
63
|
If no time filtering is desired, set it to None. Default is None.
|
|
64
64
|
boundaries : Dict[str, bool], optional
|
|
65
65
|
Dictionary specifying which boundaries are forced (south, east, north, west). Default is all True.
|
|
66
|
-
source :
|
|
66
|
+
source : RawDataSource
|
|
67
67
|
Dictionary specifying the source of the boundary forcing data. Keys include:
|
|
68
68
|
|
|
69
69
|
- "name" (str): Name of the data source (e.g., "GLORYS").
|
|
@@ -71,7 +71,9 @@ class BoundaryForcing:
|
|
|
71
71
|
|
|
72
72
|
- A single string (with or without wildcards).
|
|
73
73
|
- A single Path object.
|
|
74
|
-
- A list of strings or Path objects
|
|
74
|
+
- A list of strings or Path objects.
|
|
75
|
+
If omitted, the data will be streamed via the Copernicus Marine Toolkit.
|
|
76
|
+
Note: streaming is currently not recommended due to performance limitations.
|
|
75
77
|
- "climatology" (bool): Indicates if the data is climatology data. Defaults to False.
|
|
76
78
|
|
|
77
79
|
type : str
|
|
@@ -124,7 +126,7 @@ class BoundaryForcing:
|
|
|
124
126
|
}
|
|
125
127
|
)
|
|
126
128
|
"""Dictionary specifying which boundaries are forced (south, east, north, west)."""
|
|
127
|
-
source:
|
|
129
|
+
source: RawDataSource
|
|
128
130
|
"""Dictionary specifying the source of the boundary forcing data."""
|
|
129
131
|
type: str = "physics"
|
|
130
132
|
"""Specifies the type of forcing data ("physics", "bgc")."""
|
|
@@ -157,7 +159,6 @@ class BoundaryForcing:
|
|
|
157
159
|
if self.apply_2d_horizontal_fill:
|
|
158
160
|
data.choose_subdomain(
|
|
159
161
|
target_coords,
|
|
160
|
-
buffer_points=20, # lateral fill needs good buffer from data margin
|
|
161
162
|
)
|
|
162
163
|
# Enforce double precision to ensure reproducibility
|
|
163
164
|
data.convert_to_float64()
|
|
@@ -297,14 +298,12 @@ class BoundaryForcing:
|
|
|
297
298
|
zeta_v = zeta_v.isel(**self.bdry_coords["v"][direction])
|
|
298
299
|
|
|
299
300
|
if not self.apply_2d_horizontal_fill and bdry_data.needs_lateral_fill:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
bdry_data.dim_names["depth"],
|
|
307
|
-
)
|
|
301
|
+
if not self.bypass_validation:
|
|
302
|
+
self._validate_1d_fill(
|
|
303
|
+
processed_fields,
|
|
304
|
+
direction,
|
|
305
|
+
bdry_data.dim_names["depth"],
|
|
306
|
+
)
|
|
308
307
|
for var_name in processed_fields:
|
|
309
308
|
processed_fields[var_name] = apply_1d_horizontal_fill(
|
|
310
309
|
processed_fields[var_name]
|
|
@@ -435,7 +434,9 @@ class BoundaryForcing:
|
|
|
435
434
|
"Sea surface height will NOT be used to adjust depth coordinates."
|
|
436
435
|
)
|
|
437
436
|
|
|
438
|
-
def _get_data(
|
|
437
|
+
def _get_data(
|
|
438
|
+
self,
|
|
439
|
+
) -> GLORYSDataset | GLORYSDefaultDataset | CESMBGCDataset | UnifiedBGCDataset:
|
|
439
440
|
"""Determine the correct `Dataset` type and return an instance.
|
|
440
441
|
|
|
441
442
|
Returns
|
|
@@ -444,7 +445,21 @@ class BoundaryForcing:
|
|
|
444
445
|
The `Dataset` instance
|
|
445
446
|
|
|
446
447
|
"""
|
|
447
|
-
dataset_map: dict[
|
|
448
|
+
dataset_map: dict[
|
|
449
|
+
str,
|
|
450
|
+
dict[
|
|
451
|
+
str,
|
|
452
|
+
dict[
|
|
453
|
+
str,
|
|
454
|
+
type[
|
|
455
|
+
GLORYSDataset
|
|
456
|
+
| GLORYSDefaultDataset
|
|
457
|
+
| CESMBGCDataset
|
|
458
|
+
| UnifiedBGCDataset
|
|
459
|
+
],
|
|
460
|
+
],
|
|
461
|
+
],
|
|
462
|
+
] = {
|
|
448
463
|
"physics": {
|
|
449
464
|
"GLORYS": {
|
|
450
465
|
"external": GLORYSDataset,
|
|
@@ -471,13 +486,16 @@ class BoundaryForcing:
|
|
|
471
486
|
|
|
472
487
|
data_type = dataset_map[self.type][source_name][variant]
|
|
473
488
|
|
|
489
|
+
if isinstance(self.source["path"], bool):
|
|
490
|
+
raise ValueError('source["path"] cannot be a boolean here')
|
|
491
|
+
|
|
474
492
|
return data_type(
|
|
475
493
|
filename=self.source["path"],
|
|
476
494
|
start_time=self.start_time,
|
|
477
495
|
end_time=self.end_time,
|
|
478
|
-
climatology=self.source["climatology"],
|
|
496
|
+
climatology=self.source["climatology"], # type: ignore[arg-type]
|
|
479
497
|
use_dask=self.use_dask,
|
|
480
|
-
)
|
|
498
|
+
)
|
|
481
499
|
|
|
482
500
|
def _set_variable_info(self, data):
|
|
483
501
|
"""Sets up a dictionary with metadata for variables based on the type of data
|
|
@@ -756,6 +774,9 @@ class BoundaryForcing:
|
|
|
756
774
|
None
|
|
757
775
|
If a boundary is divided by land, a warning is issued. No return value is provided.
|
|
758
776
|
"""
|
|
777
|
+
if not hasattr(self, "_warned_directions"):
|
|
778
|
+
self._warned_directions = set()
|
|
779
|
+
|
|
759
780
|
for var_name in processed_fields.keys():
|
|
760
781
|
if self.variable_info[var_name]["validate"]:
|
|
761
782
|
location = self.variable_info[var_name]["location"]
|
|
@@ -778,16 +799,20 @@ class BoundaryForcing:
|
|
|
778
799
|
wet_nans = xr.where(da.where(mask).isnull(), 1, 0)
|
|
779
800
|
# Apply label to find connected components of wet NaNs
|
|
780
801
|
labeled_array, num_features = label(wet_nans)
|
|
802
|
+
|
|
781
803
|
left_margin = labeled_array[0]
|
|
782
804
|
right_margin = labeled_array[-1]
|
|
783
805
|
if left_margin != 0:
|
|
784
806
|
num_features = num_features - 1
|
|
785
807
|
if right_margin != 0:
|
|
786
808
|
num_features = num_features - 1
|
|
787
|
-
|
|
809
|
+
|
|
810
|
+
if num_features > 0 and direction not in self._warned_directions:
|
|
788
811
|
logging.warning(
|
|
789
|
-
f"
|
|
812
|
+
f"The {direction}ern boundary is divided by land. "
|
|
813
|
+
"It would be safer (but slower and more memory-intensive) to use `apply_2d_horizontal_fill = True`."
|
|
790
814
|
)
|
|
815
|
+
self._warned_directions.add(direction)
|
|
791
816
|
|
|
792
817
|
def _validate(self, ds):
|
|
793
818
|
"""Validate the dataset for NaN values at the first time step (bry_time=0) for
|
roms_tools/setup/cdr_forcing.py
CHANGED
|
@@ -2,19 +2,19 @@ import itertools
|
|
|
2
2
|
import logging
|
|
3
3
|
from collections import Counter
|
|
4
4
|
from collections.abc import Iterator
|
|
5
|
-
from datetime import datetime
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Annotated
|
|
8
8
|
|
|
9
9
|
import matplotlib.gridspec as gridspec
|
|
10
10
|
import matplotlib.pyplot as plt
|
|
11
11
|
import numpy as np
|
|
12
|
+
import pandas as pd
|
|
12
13
|
import xarray as xr
|
|
13
14
|
from pydantic import (
|
|
14
15
|
BaseModel,
|
|
15
16
|
Field,
|
|
16
17
|
RootModel,
|
|
17
|
-
conlist,
|
|
18
18
|
model_serializer,
|
|
19
19
|
model_validator,
|
|
20
20
|
)
|
|
@@ -40,6 +40,7 @@ from roms_tools.setup.utils import (
|
|
|
40
40
|
from_yaml,
|
|
41
41
|
gc_dist,
|
|
42
42
|
get_target_coords,
|
|
43
|
+
get_tracer_metadata_dict,
|
|
43
44
|
to_dict,
|
|
44
45
|
validate_names,
|
|
45
46
|
write_to_yaml,
|
|
@@ -103,14 +104,16 @@ class ReleaseSimulationManager(BaseModel):
|
|
|
103
104
|
class ReleaseCollector(RootModel):
|
|
104
105
|
"""Collects and validates multiple releases against each other."""
|
|
105
106
|
|
|
106
|
-
root:
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
root: Annotated[
|
|
108
|
+
list[
|
|
109
|
+
Annotated[
|
|
110
|
+
VolumeRelease | TracerPerturbation, Field(discriminator="release_type")
|
|
111
|
+
]
|
|
109
112
|
],
|
|
110
|
-
min_length=1,
|
|
111
|
-
|
|
113
|
+
Field(alias="releases", min_length=1),
|
|
114
|
+
]
|
|
112
115
|
|
|
113
|
-
_release_type: ReleaseType = None
|
|
116
|
+
_release_type: ReleaseType | None = None
|
|
114
117
|
|
|
115
118
|
def __iter__(self) -> Iterator[Release]:
|
|
116
119
|
return iter(self.root)
|
|
@@ -126,6 +129,9 @@ class ReleaseCollector(RootModel):
|
|
|
126
129
|
else:
|
|
127
130
|
raise TypeError(f"Invalid key type: {type(item)}. Must be int or str.")
|
|
128
131
|
|
|
132
|
+
def __len__(self):
|
|
133
|
+
return len(self.root)
|
|
134
|
+
|
|
129
135
|
@model_validator(mode="before")
|
|
130
136
|
@classmethod
|
|
131
137
|
def unpack_dict(cls, data):
|
|
@@ -774,6 +780,62 @@ class CDRForcing(BaseModel):
|
|
|
774
780
|
fig.subplots_adjust(hspace=0.45)
|
|
775
781
|
fig.suptitle(f"Release distribution for: {release_name}")
|
|
776
782
|
|
|
783
|
+
def compute_total_cdr_source(self, dt: float) -> pd.DataFrame:
|
|
784
|
+
"""
|
|
785
|
+
Compute integrated tracer quantities for all releases and return a DataFrame.
|
|
786
|
+
|
|
787
|
+
Parameters
|
|
788
|
+
----------
|
|
789
|
+
dt : float
|
|
790
|
+
Time step in seconds for reconstructing ROMS time stamps.
|
|
791
|
+
|
|
792
|
+
Returns
|
|
793
|
+
-------
|
|
794
|
+
pd.DataFrame
|
|
795
|
+
DataFrame with one row per release and one row of units at the top.
|
|
796
|
+
Columns 'temp' and 'salt' are excluded from integrated totals.
|
|
797
|
+
"""
|
|
798
|
+
# Reconstruct ROMS time stamps
|
|
799
|
+
_, rel_seconds = _reconstruct_roms_time_stamps(
|
|
800
|
+
self.start_time, self.end_time, dt, self.model_reference_date
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
# Collect accounting results for all releases
|
|
804
|
+
records = []
|
|
805
|
+
release_names = []
|
|
806
|
+
for release in self.releases:
|
|
807
|
+
result = release._do_accounting(rel_seconds, self.model_reference_date)
|
|
808
|
+
records.append(result)
|
|
809
|
+
release_names.append(getattr(release, "name", f"release_{len(records)}"))
|
|
810
|
+
|
|
811
|
+
# Build DataFrame: rows = releases, columns = tracer names
|
|
812
|
+
df = pd.DataFrame(records, index=release_names)
|
|
813
|
+
|
|
814
|
+
# Exclude temp and salt from units row and integrated totals
|
|
815
|
+
integrated_tracers = [col for col in df.columns if col not in ("temp", "salt")]
|
|
816
|
+
|
|
817
|
+
# Add a row of units only for integrated tracers
|
|
818
|
+
tracer_meta = get_tracer_metadata_dict(include_bgc=True, unit_type="integrated")
|
|
819
|
+
units_row = {
|
|
820
|
+
col: tracer_meta.get(col, {}).get("units", "") for col in integrated_tracers
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
df_units = pd.DataFrame([units_row], index=["units"])
|
|
824
|
+
|
|
825
|
+
# Keep only integrated_tracers columns in df, drop temp and salt
|
|
826
|
+
df_integrated = df[integrated_tracers]
|
|
827
|
+
|
|
828
|
+
# Concatenate units row on top
|
|
829
|
+
df_final = pd.concat([df_units, df_integrated])
|
|
830
|
+
|
|
831
|
+
# Store dt as metadata
|
|
832
|
+
df_final.attrs["time_step"] = dt
|
|
833
|
+
df_final.attrs["start_time"] = self.start_time
|
|
834
|
+
df_final.attrs["end_time"] = self.end_time
|
|
835
|
+
df_final.attrs["title"] = "Integrated tracer releases"
|
|
836
|
+
|
|
837
|
+
return df_final
|
|
838
|
+
|
|
777
839
|
def save(
|
|
778
840
|
self,
|
|
779
841
|
filepath: str | Path,
|
|
@@ -1051,3 +1113,55 @@ def _map_3d_gaussian(
|
|
|
1051
1113
|
distribution_3d /= distribution_3d.sum()
|
|
1052
1114
|
|
|
1053
1115
|
return distribution_3d
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
def _reconstruct_roms_time_stamps(
|
|
1119
|
+
start_time: datetime,
|
|
1120
|
+
end_time: datetime,
|
|
1121
|
+
dt: float,
|
|
1122
|
+
model_reference_date: datetime,
|
|
1123
|
+
) -> tuple[list[datetime], np.ndarray]:
|
|
1124
|
+
"""
|
|
1125
|
+
Reconstruct ROMS time stamps between `start_time` and `end_time` with step `dt`.
|
|
1126
|
+
|
|
1127
|
+
Parameters
|
|
1128
|
+
----------
|
|
1129
|
+
start_time : datetime
|
|
1130
|
+
Beginning of the time series.
|
|
1131
|
+
end_time : datetime
|
|
1132
|
+
End of the time series (inclusive if it falls exactly on a step).
|
|
1133
|
+
dt : float
|
|
1134
|
+
Time step in seconds (can be fractional if needed).
|
|
1135
|
+
model_reference_date : datetime
|
|
1136
|
+
The reference date for ROMS time (elapsed time will be relative to this).
|
|
1137
|
+
|
|
1138
|
+
Returns
|
|
1139
|
+
-------
|
|
1140
|
+
times : list of datetime
|
|
1141
|
+
Sequence of datetimes from `start_time` to `end_time`.
|
|
1142
|
+
rel_days : np.ndarray
|
|
1143
|
+
Array of elapsed times in **seconds** relative to `model_reference_date`.
|
|
1144
|
+
|
|
1145
|
+
Raises
|
|
1146
|
+
------
|
|
1147
|
+
ValueError
|
|
1148
|
+
If `end_time` is not after `start_time` or if `dt` is not positive.
|
|
1149
|
+
"""
|
|
1150
|
+
if end_time <= start_time:
|
|
1151
|
+
raise ValueError("end_time must be after start_time")
|
|
1152
|
+
if dt <= 0:
|
|
1153
|
+
raise ValueError("dt must be positive")
|
|
1154
|
+
|
|
1155
|
+
# Generate absolute times
|
|
1156
|
+
delta = timedelta(seconds=dt)
|
|
1157
|
+
times: list[datetime] = []
|
|
1158
|
+
t = start_time
|
|
1159
|
+
while t <= end_time:
|
|
1160
|
+
times.append(t)
|
|
1161
|
+
t += delta
|
|
1162
|
+
|
|
1163
|
+
# Convert to relative ROMS time (days since model_reference_date)
|
|
1164
|
+
rel_days = convert_to_relative_days(times, model_reference_date)
|
|
1165
|
+
rel_seconds = rel_days * 3600 * 24
|
|
1166
|
+
|
|
1167
|
+
return times, rel_seconds
|