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.
Files changed (214) hide show
  1. ci/environment-with-xesmf.yml +16 -0
  2. roms_tools/__init__.py +1 -1
  3. roms_tools/analysis/roms_output.py +339 -234
  4. roms_tools/analysis/utils.py +137 -0
  5. roms_tools/plot.py +353 -214
  6. roms_tools/regrid.py +154 -9
  7. roms_tools/setup/boundary_forcing.py +51 -37
  8. roms_tools/setup/datasets.py +129 -74
  9. roms_tools/setup/grid.py +32 -33
  10. roms_tools/setup/initial_conditions.py +30 -37
  11. roms_tools/setup/nesting.py +238 -64
  12. roms_tools/setup/river_forcing.py +256 -86
  13. roms_tools/setup/surface_forcing.py +40 -28
  14. roms_tools/setup/tides.py +10 -13
  15. roms_tools/setup/topography.py +27 -4
  16. roms_tools/setup/utils.py +28 -12
  17. roms_tools/tests/test_analysis/test_roms_output.py +299 -80
  18. roms_tools/tests/test_regrid.py +85 -2
  19. roms_tools/tests/test_setup/test_boundary_forcing.py +63 -0
  20. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zattrs +3 -1
  21. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zmetadata +3 -1
  22. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_east/0.0.0 +0 -0
  23. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_south/0.0.0 +0 -0
  24. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/0.0.0 +0 -0
  25. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_east/0.0.0 +0 -0
  26. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_south/0.0.0 +0 -0
  27. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/0.0.0 +0 -0
  28. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_east/0.0.0 +0 -0
  29. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_south/0.0.0 +0 -0
  30. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/0.0.0 +0 -0
  31. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_east/0.0.0 +0 -0
  32. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_south/0.0.0 +0 -0
  33. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/0.0.0 +0 -0
  34. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_east/0.0.0 +0 -0
  35. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_south/0.0.0 +0 -0
  36. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/0.0.0 +0 -0
  37. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_east/0.0.0 +0 -0
  38. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_south/0.0.0 +0 -0
  39. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/0.0.0 +0 -0
  40. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_east/0.0.0 +0 -0
  41. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_south/0.0.0 +0 -0
  42. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/0.0.0 +0 -0
  43. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_east/0.0.0 +0 -0
  44. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_south/0.0.0 +0 -0
  45. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/0.0.0 +0 -0
  46. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_east/0.0.0 +0 -0
  47. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_south/0.0.0 +0 -0
  48. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/0.0.0 +0 -0
  49. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_east/0.0.0 +0 -0
  50. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_south/0.0.0 +0 -0
  51. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/0.0.0 +0 -0
  52. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_east/0.0.0 +0 -0
  53. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_south/0.0.0 +0 -0
  54. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/0.0.0 +0 -0
  55. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_east/0.0.0 +0 -0
  56. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_south/0.0.0 +0 -0
  57. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/0.0.0 +0 -0
  58. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_east/0.0.0 +0 -0
  59. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_north/0.0.0 +0 -0
  60. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_south/0.0.0 +0 -0
  61. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/0.0.0 +0 -0
  62. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_east/0.0.0 +0 -0
  63. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_south/0.0.0 +0 -0
  64. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/0.0.0 +0 -0
  65. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_east/0.0.0 +0 -0
  66. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_south/0.0.0 +0 -0
  67. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/0.0.0 +0 -0
  68. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_east/0.0.0 +0 -0
  69. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_south/0.0.0 +0 -0
  70. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/0.0.0 +0 -0
  71. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_east/0.0.0 +0 -0
  72. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_south/0.0.0 +0 -0
  73. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/0.0.0 +0 -0
  74. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_east/0.0.0 +0 -0
  75. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_north/0.0.0 +0 -0
  76. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_south/0.0.0 +0 -0
  77. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/0.0.0 +0 -0
  78. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_east/0.0.0 +0 -0
  79. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_north/0.0.0 +0 -0
  80. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_south/0.0.0 +0 -0
  81. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/0.0.0 +0 -0
  82. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_east/0.0.0 +0 -0
  83. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_north/0.0.0 +0 -0
  84. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_south/0.0.0 +0 -0
  85. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/0.0.0 +0 -0
  86. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_east/0.0.0 +0 -0
  87. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_north/0.0.0 +0 -0
  88. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_south/0.0.0 +0 -0
  89. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/0.0.0 +0 -0
  90. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_east/0.0.0 +0 -0
  91. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_north/0.0.0 +0 -0
  92. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_south/0.0.0 +0 -0
  93. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/0.0.0 +0 -0
  94. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_east/0.0.0 +0 -0
  95. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_north/0.0.0 +0 -0
  96. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_south/0.0.0 +0 -0
  97. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/0.0.0 +0 -0
  98. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_east/0.0.0 +0 -0
  99. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_north/0.0.0 +0 -0
  100. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_south/0.0.0 +0 -0
  101. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/0.0.0 +0 -0
  102. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_east/0.0.0 +0 -0
  103. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_north/0.0.0 +0 -0
  104. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_south/0.0.0 +0 -0
  105. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/0.0.0 +0 -0
  106. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_east/0.0.0 +0 -0
  107. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_north/0.0.0 +0 -0
  108. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_south/0.0.0 +0 -0
  109. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/0.0.0 +0 -0
  110. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_east/0.0.0 +0 -0
  111. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_north/0.0.0 +0 -0
  112. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_south/0.0.0 +0 -0
  113. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/0.0.0 +0 -0
  114. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_east/0.0.0 +0 -0
  115. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_north/0.0.0 +0 -0
  116. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_south/0.0.0 +0 -0
  117. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/0.0.0 +0 -0
  118. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_east/0.0.0 +0 -0
  119. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_north/0.0.0 +0 -0
  120. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_south/0.0.0 +0 -0
  121. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/0.0.0 +0 -0
  122. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_east/0.0.0 +0 -0
  123. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_north/0.0.0 +0 -0
  124. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_south/0.0.0 +0 -0
  125. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/0.0.0 +0 -0
  126. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_east/0.0.0 +0 -0
  127. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_north/0.0.0 +0 -0
  128. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_south/0.0.0 +0 -0
  129. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/0.0.0 +0 -0
  130. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_east/0.0.0 +0 -0
  131. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_north/0.0.0 +0 -0
  132. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_south/0.0.0 +0 -0
  133. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/0.0.0 +0 -0
  134. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/.zattrs +2 -2
  135. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/.zmetadata +8 -7
  136. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/abs_time/.zattrs +1 -0
  137. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/dust/0.0.0 +0 -0
  138. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/dust_time/.zattrs +1 -1
  139. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/iron/0.0.0 +0 -0
  140. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/iron_time/.zattrs +1 -1
  141. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nhy/0.0.0 +0 -0
  142. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nhy_time/.zattrs +1 -1
  143. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nox/0.0.0 +0 -0
  144. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nox_time/.zattrs +1 -1
  145. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/pco2_air/0.0.0 +0 -0
  146. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/pco2_air_alt/0.0.0 +0 -0
  147. roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/pco2_time/.zattrs +1 -1
  148. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zattrs +2 -2
  149. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zmetadata +2 -2
  150. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/dust/0.0.0 +0 -0
  151. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/iron/0.0.0 +0 -0
  152. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nhy/0.0.0 +0 -0
  153. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nox/0.0.0 +0 -0
  154. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air/0.0.0 +0 -0
  155. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air_alt/0.0.0 +0 -0
  156. roms_tools/tests/test_setup/test_data/grid.zarr/.zattrs +1 -1
  157. roms_tools/tests/test_setup/test_data/grid.zarr/.zmetadata +2 -2
  158. roms_tools/tests/test_setup/test_data/grid.zarr/angle/0.0 +0 -0
  159. roms_tools/tests/test_setup/test_data/grid.zarr/angle_coarse/0.0 +0 -0
  160. roms_tools/tests/test_setup/test_data/grid.zarr/f/0.0 +0 -0
  161. roms_tools/tests/test_setup/test_data/grid.zarr/h/.zattrs +1 -1
  162. roms_tools/tests/test_setup/test_data/grid.zarr/h/0.0 +0 -0
  163. roms_tools/tests/test_setup/test_data/grid.zarr/lat_coarse/0.0 +0 -0
  164. roms_tools/tests/test_setup/test_data/grid.zarr/lat_rho/0.0 +0 -0
  165. roms_tools/tests/test_setup/test_data/grid.zarr/lat_u/0.0 +0 -0
  166. roms_tools/tests/test_setup/test_data/grid.zarr/lat_v/0.0 +0 -0
  167. roms_tools/tests/test_setup/test_data/grid.zarr/pm/0.0 +0 -0
  168. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +1 -1
  169. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +1 -1
  170. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle/0.0 +0 -0
  171. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle_coarse/0.0 +0 -0
  172. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/f/0.0 +0 -0
  173. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/0.0 +0 -0
  174. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_coarse/0.0 +0 -0
  175. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_rho/0.0 +0 -0
  176. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_u/0.0 +0 -0
  177. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_v/0.0 +0 -0
  178. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_coarse/0.0 +0 -0
  179. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_rho/0.0 +0 -0
  180. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_u/0.0 +0 -0
  181. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_v/0.0 +0 -0
  182. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pm/0.0 +0 -0
  183. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pn/0.0 +0 -0
  184. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zattrs +1 -1
  185. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zmetadata +1 -1
  186. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/salt/0.0.0.0 +0 -0
  187. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/temp/0.0.0.0 +0 -0
  188. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/u/0.0.0.0 +0 -0
  189. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ubar/0.0.0 +0 -0
  190. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/v/0.0.0.0 +0 -0
  191. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/vbar/0.0.0 +0 -0
  192. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zeta/0.0.0 +0 -0
  193. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +27 -1
  194. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/nriver/.zarray +20 -0
  195. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/nriver/.zattrs +6 -0
  196. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/nriver/0 +0 -0
  197. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_location/.zattrs +1 -1
  198. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/.zmetadata +27 -1
  199. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/nriver/.zarray +20 -0
  200. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/nriver/.zattrs +6 -0
  201. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/nriver/0 +0 -0
  202. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_location/.zattrs +1 -1
  203. roms_tools/tests/test_setup/test_initial_conditions.py +16 -0
  204. roms_tools/tests/test_setup/test_nesting.py +141 -104
  205. roms_tools/tests/test_setup/test_river_forcing.py +580 -266
  206. roms_tools/tests/test_setup/test_surface_forcing.py +47 -0
  207. roms_tools/tests/test_setup/test_validation.py +34 -2
  208. roms_tools/utils.py +11 -7
  209. roms_tools/vertical_coordinate.py +1 -0
  210. {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/METADATA +22 -11
  211. {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/RECORD +214 -206
  212. {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/WHEEL +1 -1
  213. {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info/licenses}/LICENSE +0 -0
  214. {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/top_level.txt +0 -0
@@ -5,22 +5,26 @@ from datetime import datetime
5
5
  import textwrap
6
6
  from pathlib import Path
7
7
  import pytest
8
+ import logging
8
9
  from conftest import calculate_file_hash
9
- from roms_tools.download import download_river_data
10
10
 
11
11
 
12
12
  @pytest.fixture
13
- def river_forcing_climatology():
14
- """Fixture for creating a RiverForcing object from the global Dai river dataset."""
15
- grid = Grid(
13
+ def iceland_test_grid():
14
+ return Grid(
16
15
  nx=18, ny=18, size_x=800, size_y=800, center_lon=-18, center_lat=65, rot=20, N=3
17
16
  )
18
17
 
18
+
19
+ @pytest.fixture
20
+ def river_forcing_climatology(iceland_test_grid):
21
+ """Fixture for creating a RiverForcing object from the global Dai river dataset."""
22
+
19
23
  start_time = datetime(1998, 1, 1)
20
24
  end_time = datetime(1998, 3, 1)
21
25
 
22
26
  return RiverForcing(
23
- grid=grid,
27
+ grid=iceland_test_grid,
24
28
  start_time=start_time,
25
29
  end_time=end_time,
26
30
  convert_to_climatology="always",
@@ -52,325 +56,635 @@ def river_forcing_for_grid_that_straddles_dateline():
52
56
  )
53
57
 
54
58
 
55
- def compare_dictionaries(dict1, dict2):
56
- assert dict1.keys() == dict2.keys()
59
+ @pytest.fixture
60
+ def single_cell_indices():
61
+ # These are the indices that the `river_forcing` fixture generates automatically.
62
+ return {
63
+ "Hvita(Olfusa)": [(8, 6)],
64
+ "Thjorsa": [(8, 6)],
65
+ "JkulsFjll": [(11, 12)],
66
+ "Lagarfljot": [(9, 13)],
67
+ "Bruara": [(8, 6)],
68
+ "Svarta": [(12, 9)],
69
+ }
57
70
 
58
- for key in dict1.keys():
59
- assert np.array_equal(dict1[key], dict2[key])
71
+
72
+ @pytest.fixture
73
+ def multi_cell_indices():
74
+ # These are the indices that the `river_forcing` fixture generates automatically.
75
+ return {
76
+ "Hvita(Olfusa)": [(8, 6)],
77
+ "Thjorsa": [(8, 6)],
78
+ "JkulsFjll": [(11, 12)],
79
+ "Lagarfljot": [(9, 13), (10, 13)],
80
+ "Bruara": [(8, 6)],
81
+ "Svarta": [(12, 8), (12, 9), (12, 10)],
82
+ }
60
83
 
61
84
 
62
- @pytest.mark.parametrize(
63
- "river_forcing_fixture",
64
- [
65
- "river_forcing",
66
- "river_forcing_for_grid_that_straddles_dateline",
67
- "river_forcing_with_bgc",
68
- ],
69
- )
70
- def test_successful_initialization_with_climatological_dai_data(
71
- river_forcing_fixture, request
85
+ @pytest.fixture
86
+ def river_forcing_with_prescribed_single_cell_indices(
87
+ single_cell_indices, iceland_test_grid
72
88
  ):
89
+ """Fixture for creating a RiverForcing object based on the global Dai river dataset,
90
+ using manually specified single-cell river indices instead of relying on automatic
91
+ detection."""
73
92
 
74
- river_forcing = request.getfixturevalue(river_forcing_fixture)
75
-
76
- assert isinstance(river_forcing.ds, xr.Dataset)
77
- assert len(river_forcing.ds.nriver) > 0
78
- assert len(river_forcing.original_indices["name"]) > 0
79
- assert "river_volume" in river_forcing.ds
80
- assert "river_tracer" in river_forcing.ds
81
- assert river_forcing.climatology
82
- assert "river_time" in river_forcing.ds
83
- assert hasattr(
84
- river_forcing.ds.river_time,
85
- "cycle_length",
93
+ start_time = datetime(1998, 1, 1)
94
+ end_time = datetime(1998, 3, 1)
95
+
96
+ return RiverForcing(
97
+ grid=iceland_test_grid,
98
+ start_time=start_time,
99
+ end_time=end_time,
100
+ indices=single_cell_indices,
86
101
  )
87
- assert hasattr(river_forcing.ds, "climatology")
88
102
 
89
103
 
90
- def test_successful_initialization_with_monthly_dai_data(river_forcing_no_climatology):
104
+ @pytest.fixture
105
+ def river_forcing_with_prescribed_multi_cell_indices(
106
+ multi_cell_indices, iceland_test_grid
107
+ ):
108
+ """Fixture for creating a RiverForcing object based on the global Dai river dataset,
109
+ using manually specified multi-cell river indices instead of relying on automatic
110
+ detection."""
111
+
112
+ start_time = datetime(1998, 1, 1)
113
+ end_time = datetime(1998, 3, 1)
91
114
 
92
- assert isinstance(river_forcing_no_climatology.ds, xr.Dataset)
93
- assert "river_volume" in river_forcing_no_climatology.ds
94
- assert "river_tracer" in river_forcing_no_climatology.ds
95
- assert "river_time" in river_forcing_no_climatology.ds
96
- assert not river_forcing_no_climatology.climatology
97
- assert not hasattr(
98
- river_forcing_no_climatology.ds.river_time,
99
- "cycle_length",
115
+ return RiverForcing(
116
+ grid=iceland_test_grid,
117
+ start_time=start_time,
118
+ end_time=end_time,
119
+ indices=multi_cell_indices,
100
120
  )
101
- assert not hasattr(river_forcing_no_climatology.ds, "climatology")
102
121
 
103
122
 
104
- def test_reproducibility(river_forcing, river_forcing_climatology):
123
+ def compare_dictionaries(dict1, dict2):
124
+ assert dict1.keys() == dict2.keys()
125
+
126
+ for key in dict1.keys():
127
+ assert np.array_equal(dict1[key], dict2[key])
105
128
 
106
- xr.testing.assert_allclose(river_forcing.ds, river_forcing_climatology.ds)
107
129
 
108
- compare_dictionaries(
109
- river_forcing.original_indices, river_forcing_climatology.original_indices
130
+ class TestRiverForcingGeneral:
131
+ @pytest.mark.parametrize(
132
+ "river_forcing_fixture",
133
+ [
134
+ "river_forcing",
135
+ "river_forcing_climatology",
136
+ "river_forcing_with_bgc",
137
+ "river_forcing_for_grid_that_straddles_dateline",
138
+ "river_forcing_with_prescribed_single_cell_indices",
139
+ "river_forcing_with_prescribed_multi_cell_indices",
140
+ ],
110
141
  )
111
- compare_dictionaries(
112
- river_forcing.updated_indices, river_forcing_climatology.updated_indices
142
+ def test_successful_initialization(self, river_forcing_fixture, request):
143
+ river_forcing = request.getfixturevalue(river_forcing_fixture)
144
+ assert isinstance(river_forcing.ds, xr.Dataset)
145
+ assert len(river_forcing.ds.nriver) > 0
146
+ assert len(river_forcing.original_indices) > 0
147
+ assert len(river_forcing.indices) > 0
148
+ assert "river_volume" in river_forcing.ds
149
+ assert "river_tracer" in river_forcing.ds
150
+ assert "river_time" in river_forcing.ds
151
+
152
+ @pytest.mark.parametrize(
153
+ "river_forcing_fixture",
154
+ [
155
+ "river_forcing",
156
+ "river_forcing_climatology",
157
+ "river_forcing_with_bgc",
158
+ "river_forcing_with_prescribed_single_cell_indices",
159
+ "river_forcing_with_prescribed_multi_cell_indices",
160
+ ],
113
161
  )
162
+ def test_climatology_attributes(self, river_forcing_fixture, request):
163
+ river_forcing = request.getfixturevalue(river_forcing_fixture)
164
+ assert river_forcing.climatology
165
+ assert hasattr(
166
+ river_forcing.ds.river_time,
167
+ "cycle_length",
168
+ )
169
+ assert hasattr(river_forcing.ds, "climatology")
114
170
 
115
-
116
- def test_reproducibility_indices(river_forcing, river_forcing_no_climatology):
117
-
118
- compare_dictionaries(
119
- river_forcing.original_indices, river_forcing_no_climatology.original_indices
120
- )
121
- compare_dictionaries(
122
- river_forcing.updated_indices, river_forcing_no_climatology.updated_indices
171
+ def test_no_climatology_attributes(self, river_forcing_no_climatology, request):
172
+ assert not river_forcing_no_climatology.climatology
173
+ assert not hasattr(
174
+ river_forcing_no_climatology.ds.river_time,
175
+ "cycle_length",
176
+ )
177
+ assert not hasattr(river_forcing_no_climatology.ds, "climatology")
178
+
179
+ @pytest.mark.parametrize(
180
+ "river_forcing_fixture",
181
+ [
182
+ "river_forcing_climatology",
183
+ "river_forcing_no_climatology",
184
+ "river_forcing_with_bgc",
185
+ "river_forcing_with_prescribed_single_cell_indices",
186
+ "river_forcing_with_prescribed_multi_cell_indices",
187
+ ],
123
188
  )
189
+ def test_constant_tracers(self, river_forcing_fixture, request):
190
+ river_forcing = request.getfixturevalue(river_forcing_fixture)
191
+ np.testing.assert_allclose(
192
+ river_forcing.ds.river_tracer.isel(ntracers=0).values, 17.0, atol=0
193
+ )
194
+ np.testing.assert_allclose(
195
+ river_forcing.ds.river_tracer.isel(ntracers=1).values, 1.0, atol=0
196
+ )
197
+ np.testing.assert_allclose(
198
+ river_forcing.ds.river_tracer.isel(ntracers=slice(2, None)).values,
199
+ 0.0,
200
+ atol=0,
201
+ )
124
202
 
203
+ def test_reproducibility_same_grid(self, river_forcing):
125
204
 
126
- @pytest.mark.parametrize(
127
- "river_forcing_fixture",
128
- [
129
- "river_forcing_climatology",
130
- "river_forcing_no_climatology",
131
- "river_forcing_with_bgc",
132
- ],
133
- )
134
- def test_constant_tracers(river_forcing_fixture, request):
135
- river_forcing = request.getfixturevalue(river_forcing_fixture)
205
+ the_same_river_forcing = RiverForcing(
206
+ grid=river_forcing.grid,
207
+ start_time=datetime(1998, 1, 1),
208
+ end_time=datetime(1998, 3, 1),
209
+ )
136
210
 
137
- np.testing.assert_allclose(
138
- river_forcing.ds.river_tracer.isel(ntracers=0).values, 17.0, atol=0
211
+ assert river_forcing == the_same_river_forcing
212
+
213
+ @pytest.mark.parametrize(
214
+ "river_forcing_fixture",
215
+ [
216
+ "river_forcing_climatology",
217
+ "river_forcing_no_climatology",
218
+ "river_forcing_with_bgc",
219
+ "river_forcing_with_prescribed_single_cell_indices",
220
+ "river_forcing_with_prescribed_multi_cell_indices",
221
+ ],
139
222
  )
140
- np.testing.assert_allclose(
141
- river_forcing.ds.river_tracer.isel(ntracers=1).values, 1.0, atol=0
223
+ def test_river_locations_are_along_coast(self, river_forcing_fixture, request):
224
+ river_forcing = request.getfixturevalue(river_forcing_fixture)
225
+
226
+ mask = river_forcing.grid.ds.mask_rho
227
+ faces = (
228
+ mask.shift(eta_rho=1)
229
+ + mask.shift(eta_rho=-1)
230
+ + mask.shift(xi_rho=1)
231
+ + mask.shift(xi_rho=-1)
232
+ )
233
+ coast = (1 - mask) * (faces > 0)
234
+
235
+ indices = river_forcing.indices
236
+ for name in indices.keys():
237
+ for (eta_rho, xi_rho) in indices[name]:
238
+ assert coast[eta_rho, xi_rho]
239
+ assert river_forcing.ds["river_location"][eta_rho, xi_rho] > 0
240
+
241
+ def test_missing_source_name(self, iceland_test_grid):
242
+ with pytest.raises(ValueError, match="`source` must include a 'name'."):
243
+ RiverForcing(
244
+ grid=iceland_test_grid,
245
+ start_time=datetime(1998, 1, 1),
246
+ end_time=datetime(1998, 3, 1),
247
+ source={"path": "river_data.nc"},
248
+ )
249
+
250
+ def test_river_forcing_plot(self, river_forcing_with_bgc):
251
+ """Test plot method."""
252
+
253
+ river_forcing_with_bgc.plot_locations()
254
+ river_forcing_with_bgc.plot("river_volume")
255
+ river_forcing_with_bgc.plot("river_temp")
256
+ river_forcing_with_bgc.plot("river_salt")
257
+ river_forcing_with_bgc.plot("river_ALK")
258
+ river_forcing_with_bgc.plot("river_PO4")
259
+
260
+ @pytest.mark.parametrize(
261
+ "river_forcing_fixture",
262
+ [
263
+ "river_forcing_with_bgc",
264
+ "river_forcing_with_prescribed_multi_cell_indices",
265
+ ],
142
266
  )
143
- np.testing.assert_allclose(
144
- river_forcing.ds.river_tracer.isel(ntracers=slice(2, None)).values, 0.0, atol=0
267
+ def test_river_forcing_save(self, river_forcing_fixture, tmp_path, request):
268
+ """Test save method."""
269
+
270
+ river_forcing = request.getfixturevalue(river_forcing_fixture)
271
+ for file_str in ["test_rivers", "test_rivers.nc"]:
272
+ # Create a temporary filepath using the tmp_path fixture
273
+ for filepath in [tmp_path / file_str, str(tmp_path / file_str)]:
274
+
275
+ saved_filenames = river_forcing.save(filepath)
276
+ # Check if the .nc file was created
277
+ filepath = Path(filepath).with_suffix(".nc")
278
+ assert saved_filenames == [filepath]
279
+ assert filepath.exists()
280
+ # Clean up the .nc file
281
+ filepath.unlink()
282
+
283
+ @pytest.mark.parametrize(
284
+ "river_forcing_fixture",
285
+ [
286
+ "river_forcing_climatology",
287
+ "river_forcing_no_climatology",
288
+ "river_forcing_with_bgc",
289
+ "river_forcing_with_prescribed_single_cell_indices",
290
+ "river_forcing_with_prescribed_multi_cell_indices",
291
+ ],
145
292
  )
293
+ def test_roundtrip_yaml(self, river_forcing_fixture, request, tmp_path, caplog):
294
+ """Test that creating an RiverForcing object, saving its parameters to yaml
295
+ file, and re-opening yaml file creates the same object."""
146
296
 
297
+ river_forcing = request.getfixturevalue(river_forcing_fixture)
147
298
 
148
- @pytest.mark.parametrize(
149
- "river_forcing_fixture",
150
- [
151
- "river_forcing_climatology",
152
- "river_forcing_no_climatology",
153
- "river_forcing_with_bgc",
154
- ],
155
- )
156
- def test_river_locations_are_along_coast(river_forcing_fixture, request):
157
- river_forcing = request.getfixturevalue(river_forcing_fixture)
158
-
159
- mask = river_forcing.grid.ds.mask_rho
160
- faces = (
161
- mask.shift(eta_rho=1)
162
- + mask.shift(eta_rho=-1)
163
- + mask.shift(xi_rho=1)
164
- + mask.shift(xi_rho=-1)
165
- )
166
- coast = (1 - mask) * (faces > 0)
299
+ # Create a temporary filepath using the tmp_path fixture
300
+ file_str = "test_yaml"
301
+ for filepath in [
302
+ tmp_path / file_str,
303
+ str(tmp_path / file_str),
304
+ ]: # test for Path object and str
167
305
 
168
- indices = river_forcing.updated_indices
169
- for i in range(len(indices["station"])):
170
- eta_rho = indices["eta_rho"][i]
171
- xi_rho = indices["xi_rho"][i]
172
- assert coast[eta_rho, xi_rho]
173
- assert river_forcing.ds["river_location"][eta_rho, xi_rho] > 0
306
+ river_forcing.to_yaml(filepath)
174
307
 
308
+ # Clear caplog before running the test
309
+ caplog.clear()
175
310
 
176
- def test_missing_source_name():
311
+ with caplog.at_level(logging.INFO):
312
+ river_forcing_from_file = RiverForcing.from_yaml(filepath)
177
313
 
178
- grid = Grid(
179
- nx=2,
180
- ny=2,
181
- size_x=500,
182
- size_y=1000,
183
- center_lon=0,
184
- center_lat=55,
185
- rot=10,
186
- N=3, # number of vertical levels
314
+ assert "Use provided river indices." in caplog.text
315
+ assert river_forcing == river_forcing_from_file
316
+
317
+ filepath = Path(filepath)
318
+ filepath.unlink()
319
+
320
+ @pytest.mark.parametrize(
321
+ "river_forcing_fixture",
322
+ [
323
+ "river_forcing_climatology",
324
+ "river_forcing_no_climatology",
325
+ "river_forcing_with_bgc",
326
+ "river_forcing_with_prescribed_single_cell_indices",
327
+ "river_forcing_with_prescribed_multi_cell_indices",
328
+ ],
187
329
  )
330
+ def test_files_have_same_hash(self, river_forcing_fixture, request, tmp_path):
188
331
 
189
- with pytest.raises(ValueError, match="`source` must include a 'name'."):
190
- RiverForcing(
191
- grid=grid,
192
- start_time=datetime(1998, 1, 1),
193
- end_time=datetime(1998, 3, 1),
194
- source={"path": "river_data.nc"},
195
- )
332
+ river_forcing = request.getfixturevalue(river_forcing_fixture)
196
333
 
334
+ yaml_filepath = tmp_path / "test_yaml.yaml"
335
+ filepath1 = tmp_path / "test1.nc"
336
+ filepath2 = tmp_path / "test2.nc"
197
337
 
198
- def test_no_rivers_found():
338
+ river_forcing.to_yaml(yaml_filepath)
339
+ river_forcing.save(filepath1)
340
+ rf_from_file = RiverForcing.from_yaml(yaml_filepath)
341
+ rf_from_file.save(filepath2)
199
342
 
200
- # Create a grid over open ocean
201
- grid = Grid(
202
- nx=2, ny=2, size_x=50, size_y=50, center_lon=0, center_lat=55, rot=10, N=3
203
- )
204
- with pytest.raises(
205
- ValueError,
206
- match="No relevant rivers found.",
207
- ):
343
+ hash1 = calculate_file_hash(filepath1)
344
+ hash2 = calculate_file_hash(filepath2)
208
345
 
209
- RiverForcing(
210
- grid=grid,
211
- start_time=datetime(1998, 1, 1),
212
- end_time=datetime(1998, 3, 1),
346
+ assert hash1 == hash2, f"Hashes do not match: {hash1} != {hash2}"
347
+
348
+ yaml_filepath.unlink()
349
+ filepath1.unlink()
350
+ filepath2.unlink()
351
+
352
+ def test_from_yaml_missing_river_forcing(self, tmp_path):
353
+ yaml_content = textwrap.dedent(
354
+ """\
355
+ ---
356
+ roms_tools_version: 0.0.0
357
+ ---
358
+ Grid:
359
+ nx: 100
360
+ ny: 100
361
+ size_x: 1800
362
+ size_y: 2400
363
+ center_lon: -10
364
+ center_lat: 61
365
+ rot: -20
366
+ topography_source:
367
+ name: ETOPO5
368
+ hmin: 5.0
369
+ """
370
+ )
371
+
372
+ # Create a temporary filepath using the tmp_path fixture
373
+ file_str = "test_yaml"
374
+ for yaml_filepath in [
375
+ tmp_path / file_str,
376
+ str(tmp_path / file_str),
377
+ ]: # test for Path object and str
378
+
379
+ # Write YAML content to file
380
+ if isinstance(yaml_filepath, Path):
381
+ yaml_filepath.write_text(yaml_content)
382
+ else:
383
+ with open(yaml_filepath, "w") as f:
384
+ f.write(yaml_content)
385
+
386
+ with pytest.raises(
387
+ ValueError,
388
+ match="No RiverForcing configuration found in the YAML file.",
389
+ ):
390
+ RiverForcing.from_yaml(yaml_filepath)
391
+
392
+ yaml_filepath = Path(yaml_filepath)
393
+ yaml_filepath.unlink()
394
+
395
+
396
+ class TestRiverForcingWithoutPrescribedIndices:
397
+ start_time = datetime(1998, 1, 1)
398
+ end_time = datetime(1998, 3, 1)
399
+
400
+ def test_logging_message(self, iceland_test_grid, caplog):
401
+
402
+ with caplog.at_level(logging.INFO):
403
+ RiverForcing(
404
+ grid=iceland_test_grid,
405
+ start_time=self.start_time,
406
+ end_time=self.end_time,
407
+ )
408
+ # Verify the info message in the log
409
+ assert "No river indices provided." in caplog.text
410
+
411
+ def test_reproducibility(self, river_forcing, river_forcing_climatology):
412
+ """Verify that `river_forcing` and `river_forcing_climatology` produce identical
413
+ outputs.
414
+
415
+ `river_forcing` is initialized with `convert_to_climatology="if_any_missing"`, meaning
416
+ it fell back to climatology. This test ensures that the resulting datasets
417
+ and river index mappings are the same between the two cases.
418
+ """
419
+ xr.testing.assert_allclose(river_forcing.ds, river_forcing_climatology.ds)
420
+ compare_dictionaries(
421
+ river_forcing.original_indices, river_forcing_climatology.original_indices
213
422
  )
423
+ compare_dictionaries(river_forcing.indices, river_forcing_climatology.indices)
214
424
 
425
+ def test_no_rivers_found(self):
215
426
 
216
- def test_reproducibility_same_grid(river_forcing):
427
+ # Create a grid over open ocean
428
+ grid = Grid(
429
+ nx=2, ny=2, size_x=50, size_y=50, center_lon=0, center_lat=55, rot=10, N=3
430
+ )
217
431
 
218
- the_same_river_forcing = RiverForcing(
219
- grid=river_forcing.grid,
220
- start_time=datetime(1998, 1, 1),
221
- end_time=datetime(1998, 3, 1),
222
- )
432
+ with pytest.raises(ValueError, match="No relevant rivers found."):
433
+ RiverForcing(grid=grid, start_time=self.start_time, end_time=self.end_time)
223
434
 
224
- assert river_forcing == the_same_river_forcing
225
435
 
436
+ class TestRiverForcingWithPrescribedIndices:
437
+ start_time = datetime(1998, 1, 1)
438
+ end_time = datetime(1998, 3, 1)
226
439
 
227
- def test_update_river_location_variable_without_conflicts(river_forcing, tmp_path):
440
+ def test_logging_message(self, single_cell_indices, caplog, iceland_test_grid):
228
441
 
229
- fname = download_river_data("dai_trenberth_may2019.nc")
230
- ds = xr.open_dataset(fname, decode_times=False)
231
- # only keep the 300 biggest rivers, which will lower the total relevant river number
232
- ds = ds.isel(station=slice(None, 300))
233
- filepath = tmp_path / "test.nc"
234
- ds.to_netcdf(filepath)
442
+ with caplog.at_level(logging.INFO):
443
+ RiverForcing(
444
+ grid=iceland_test_grid,
445
+ start_time=self.start_time,
446
+ end_time=self.end_time,
447
+ indices=single_cell_indices,
448
+ )
449
+ # Verify the info message in the log
450
+ assert "Use provided river indices." in caplog.text
235
451
 
236
- another_river_forcing = RiverForcing(
237
- grid=river_forcing.grid,
238
- start_time=datetime(1998, 1, 1),
239
- end_time=datetime(1998, 3, 1),
240
- source={"name": "DAI", "path": filepath},
452
+ @pytest.mark.parametrize(
453
+ "indices_fixture", ["single_cell_indices", "multi_cell_indices"]
241
454
  )
455
+ def test_indices_stay_untouched(self, indices_fixture, request, iceland_test_grid):
456
+ indices = request.getfixturevalue(indices_fixture)
457
+
458
+ river_forcing = RiverForcing(
459
+ grid=iceland_test_grid,
460
+ start_time=self.start_time,
461
+ end_time=self.end_time,
462
+ indices=indices,
463
+ )
464
+ river_forcing.original_indices == indices
465
+ river_forcing.indices == indices
242
466
 
243
- assert isinstance(another_river_forcing.ds, xr.Dataset)
467
+ def test_fraction(
468
+ self,
469
+ river_forcing_with_prescribed_single_cell_indices,
470
+ river_forcing_with_prescribed_multi_cell_indices,
471
+ ):
472
+ def list_non_zero_values(data_array):
473
+ non_zero_values = data_array.values
474
+ return non_zero_values[non_zero_values != 0].tolist()
475
+
476
+ # check that all values are integers for single cell rivers
477
+ non_zero_values = river_forcing_with_prescribed_single_cell_indices.ds[
478
+ "river_location"
479
+ ]
480
+ is_integer = non_zero_values == np.floor(non_zero_values)
481
+ assert (is_integer).all()
482
+
483
+ # check that not all values are integers for multi cell rivers
484
+ non_zero_values = river_forcing_with_prescribed_multi_cell_indices.ds[
485
+ "river_location"
486
+ ]
487
+ is_integer = non_zero_values == np.floor(non_zero_values)
488
+ assert not (is_integer).all()
489
+
490
+ def test_reproducibility(
491
+ self, river_forcing, river_forcing_with_prescribed_single_cell_indices
492
+ ):
493
+ """river_forcing_with_prescribed_single_cell_indices was created with the
494
+ indices that were automatically inferred for river_forcing.
495
+
496
+ Test that these two are identical.
497
+ """
498
+ assert (
499
+ river_forcing.indices
500
+ == river_forcing_with_prescribed_single_cell_indices.indices
501
+ )
502
+ assert river_forcing.ds.identical(
503
+ river_forcing_with_prescribed_single_cell_indices.ds
504
+ )
505
+ assert river_forcing == river_forcing_with_prescribed_single_cell_indices
244
506
 
507
+ def test_reproducibility_with_flipped_dictionary_entries(
508
+ self, tmp_path, iceland_test_grid
509
+ ):
510
+ indices = {
511
+ "Hvita(Olfusa)": [(8, 6)],
512
+ "Thjorsa": [(8, 6)],
513
+ "JkulsFjll": [(11, 12)],
514
+ "Lagarfljot": [(9, 13), (10, 13)],
515
+ "Bruara": [(8, 6)],
516
+ "Svarta": [(12, 8), (12, 9), (12, 10)],
517
+ }
518
+
519
+ flipped_indices = {
520
+ "Thjorsa": [(8, 6)],
521
+ "Hvita(Olfusa)": [(8, 6)],
522
+ "JkulsFjll": [(11, 12)],
523
+ "Svarta": [(12, 10), (12, 9), (12, 8)], # also flip order of tuples here
524
+ "Lagarfljot": [(9, 13), (10, 13)],
525
+ "Bruara": [(8, 6)],
526
+ }
527
+
528
+ river_forcing = RiverForcing(
529
+ grid=iceland_test_grid,
530
+ start_time=self.start_time,
531
+ end_time=self.end_time,
532
+ indices=indices,
533
+ )
245
534
 
246
- def test_river_forcing_plot(river_forcing_with_bgc):
247
- """Test plot method."""
535
+ river_forcing_from_flipped_indices = RiverForcing(
536
+ grid=iceland_test_grid,
537
+ start_time=self.start_time,
538
+ end_time=self.end_time,
539
+ indices=flipped_indices,
540
+ )
248
541
 
249
- river_forcing_with_bgc.plot_locations()
250
- river_forcing_with_bgc.plot("river_volume")
251
- river_forcing_with_bgc.plot("river_temp")
252
- river_forcing_with_bgc.plot("river_salt")
253
- river_forcing_with_bgc.plot("river_ALK")
254
- river_forcing_with_bgc.plot("river_PO4")
542
+ # Create a temporary filepath using the tmp_path fixture
543
+ file1 = Path(tmp_path / "test1.nc")
544
+ file2 = Path(tmp_path / "test2.nc")
255
545
 
546
+ river_forcing.save(file1)
547
+ river_forcing_from_flipped_indices.save(file2)
256
548
 
257
- def test_river_forcing_save(river_forcing_with_bgc, tmp_path):
258
- """Test save method."""
549
+ hash1 = calculate_file_hash(file1)
550
+ hash2 = calculate_file_hash(file2)
259
551
 
260
- for file_str in ["test_rivers", "test_rivers.nc"]:
261
- # Create a temporary filepath using the tmp_path fixture
262
- for filepath in [tmp_path / file_str, str(tmp_path / file_str)]:
263
-
264
- saved_filenames = river_forcing_with_bgc.save(filepath)
265
- # Check if the .nc file was created
266
- filepath = Path(filepath).with_suffix(".nc")
267
- assert saved_filenames == [filepath]
268
- assert filepath.exists()
269
- # Clean up the .nc file
270
- filepath.unlink()
552
+ assert hash1 == hash2, f"Hashes do not match: {hash1} != {hash2}"
271
553
 
554
+ file1.unlink()
555
+ file2.unlink()
272
556
 
273
- @pytest.mark.parametrize(
274
- "river_forcing_fixture",
275
- [
276
- "river_forcing_climatology",
277
- "river_forcing_no_climatology",
278
- "river_forcing_with_bgc",
279
- ],
280
- )
281
- def test_roundtrip_yaml(river_forcing_fixture, request, tmp_path):
282
- """Test that creating an RiverForcing object, saving its parameters to yaml file,
283
- and re-opening yaml file creates the same object."""
284
-
285
- river_forcing = request.getfixturevalue(river_forcing_fixture)
286
-
287
- # Create a temporary filepath using the tmp_path fixture
288
- file_str = "test_yaml"
289
- for filepath in [
290
- tmp_path / file_str,
291
- str(tmp_path / file_str),
292
- ]: # test for Path object and str
293
-
294
- river_forcing.to_yaml(filepath)
295
-
296
- river_forcing_from_file = RiverForcing.from_yaml(filepath)
297
-
298
- assert river_forcing == river_forcing_from_file
299
-
300
- filepath = Path(filepath)
301
- filepath.unlink()
302
-
303
-
304
- @pytest.mark.parametrize(
305
- "river_forcing_fixture",
306
- [
307
- "river_forcing_climatology",
308
- "river_forcing_no_climatology",
309
- "river_forcing_with_bgc",
310
- ],
311
- )
312
- def test_files_have_same_hash(river_forcing_fixture, request, tmp_path):
313
-
314
- river_forcing = request.getfixturevalue(river_forcing_fixture)
315
-
316
- yaml_filepath = tmp_path / "test_yaml.yaml"
317
- filepath1 = tmp_path / "test1.nc"
318
- filepath2 = tmp_path / "test2.nc"
319
-
320
- river_forcing.to_yaml(yaml_filepath)
321
- river_forcing.save(filepath1)
322
- rf_from_file = RiverForcing.from_yaml(yaml_filepath)
323
- rf_from_file.save(filepath2)
324
-
325
- hash1 = calculate_file_hash(filepath1)
326
- hash2 = calculate_file_hash(filepath2)
327
-
328
- assert hash1 == hash2, f"Hashes do not match: {hash1} != {hash2}"
329
-
330
- yaml_filepath.unlink()
331
- filepath1.unlink()
332
- filepath2.unlink()
333
-
334
-
335
- def test_from_yaml_missing_river_forcing(tmp_path):
336
- yaml_content = textwrap.dedent(
337
- """\
338
- ---
339
- roms_tools_version: 0.0.0
340
- ---
341
- Grid:
342
- nx: 100
343
- ny: 100
344
- size_x: 1800
345
- size_y: 2400
346
- center_lon: -10
347
- center_lat: 61
348
- rot: -20
349
- topography_source:
350
- name: ETOPO5
351
- hmin: 5.0
352
- """
353
- )
557
+ def test_invalid_indices(self, iceland_test_grid):
558
+ invalid_single_cell_indices = {"Hvita(Olfusa)": [(0, 6)]}
559
+ invalid_multi_cell_indices = {"Hvita(Olfusa)": [(8, 6), (0, 6)]}
354
560
 
355
- # Create a temporary filepath using the tmp_path fixture
356
- file_str = "test_yaml"
357
- for yaml_filepath in [
358
- tmp_path / file_str,
359
- str(tmp_path / file_str),
360
- ]: # test for Path object and str
561
+ for indices in [invalid_single_cell_indices, invalid_multi_cell_indices]:
562
+ with pytest.raises(
563
+ ValueError, match="is not located on the coast at grid cell"
564
+ ):
565
+ RiverForcing(
566
+ grid=iceland_test_grid,
567
+ start_time=self.start_time,
568
+ end_time=self.end_time,
569
+ indices=indices,
570
+ )
361
571
 
362
- # Write YAML content to file
363
- if isinstance(yaml_filepath, Path):
364
- yaml_filepath.write_text(yaml_content)
365
- else:
366
- with open(yaml_filepath, "w") as f:
367
- f.write(yaml_content)
572
+ def test_raise_missing_rivers(self, iceland_test_grid):
573
+ fake_indices = {"Hvita(Olfusa)": [(8, 6)], "fake": [(11, 12)]}
368
574
 
575
+ with pytest.raises(
576
+ ValueError, match="The following rivers were not found in the dataset"
577
+ ):
578
+ RiverForcing(
579
+ grid=iceland_test_grid,
580
+ start_time=self.start_time,
581
+ end_time=self.end_time,
582
+ indices=fake_indices,
583
+ )
584
+
585
+ def test_indices_is_dict(self, iceland_test_grid):
586
+ with pytest.raises(ValueError, match="`indices` must be a dictionary."):
587
+ RiverForcing(
588
+ grid=iceland_test_grid,
589
+ start_time=self.start_time,
590
+ end_time=self.end_time,
591
+ indices="invalid",
592
+ )
593
+
594
+ def test_indices_empty(self, iceland_test_grid):
369
595
  with pytest.raises(
370
596
  ValueError,
371
- match="No RiverForcing configuration found in the YAML file.",
597
+ match="The provided 'indices' dictionary must contain at least one river.",
372
598
  ):
373
- RiverForcing.from_yaml(yaml_filepath)
374
-
375
- yaml_filepath = Path(yaml_filepath)
376
- yaml_filepath.unlink()
599
+ RiverForcing(
600
+ grid=iceland_test_grid,
601
+ start_time=self.start_time,
602
+ end_time=self.end_time,
603
+ indices={},
604
+ )
605
+
606
+ def test_invalid_river_name_type(self, iceland_test_grid):
607
+ indices = {123: [(8, 6)]} # Invalid river name (should be a string)
608
+ with pytest.raises(ValueError, match="River name `123` must be a string."):
609
+ RiverForcing(
610
+ grid=iceland_test_grid,
611
+ start_time=self.start_time,
612
+ end_time=self.end_time,
613
+ indices=indices,
614
+ )
615
+
616
+ def test_invalid_river_data_type(self, iceland_test_grid):
617
+ indices = {
618
+ "Hvita(Olfusa)": "8, 6" # Invalid river data (should be a list of tuples)
619
+ }
620
+ with pytest.raises(ValueError, match="must be a list of tuples."):
621
+ RiverForcing(
622
+ grid=iceland_test_grid,
623
+ start_time=self.start_time,
624
+ end_time=self.end_time,
625
+ indices=indices,
626
+ )
627
+
628
+ def test_invalid_tuple_length(self, iceland_test_grid):
629
+ indices = {
630
+ "Hvita(Olfusa)": [(8, 6, 7)] # Invalid tuple length (should be length 2)
631
+ }
632
+ with pytest.raises(ValueError, match="must be a tuple of length 2"):
633
+ RiverForcing(
634
+ grid=iceland_test_grid,
635
+ start_time=self.start_time,
636
+ end_time=self.end_time,
637
+ indices=indices,
638
+ )
639
+
640
+ def test_invalid_eta_rho_type(self, iceland_test_grid):
641
+ indices = {
642
+ "Hvita(Olfusa)": [("a", 6)] # Invalid eta_rho (should be an integer)
643
+ }
644
+ with pytest.raises(ValueError, match="First element of tuple for river"):
645
+ RiverForcing(
646
+ grid=iceland_test_grid,
647
+ start_time=self.start_time,
648
+ end_time=self.end_time,
649
+ indices=indices,
650
+ )
651
+
652
+ def test_invalid_xi_rho_type(self, iceland_test_grid):
653
+ indices = {"Hvita(Olfusa)": [(8, "b")]} # Invalid xi_rho (should be an integer)
654
+ with pytest.raises(ValueError, match="Second element of tuple for river"):
655
+ RiverForcing(
656
+ grid=iceland_test_grid,
657
+ start_time=self.start_time,
658
+ end_time=self.end_time,
659
+ indices=indices,
660
+ )
661
+
662
+ def test_eta_rho_out_of_range(self, iceland_test_grid):
663
+ indices = {"Hvita(Olfusa)": [(20, 6)]} # eta_rho out of valid range [0, 17]
664
+ with pytest.raises(ValueError, match="Value of eta_rho for river"):
665
+ RiverForcing(
666
+ grid=iceland_test_grid,
667
+ start_time=self.start_time,
668
+ end_time=self.end_time,
669
+ indices=indices,
670
+ )
671
+
672
+ def test_xi_rho_out_of_range(self, iceland_test_grid):
673
+ indices = {"Hvita(Olfusa)": [(8, 20)]} # xi_rho out of valid range [0, 17]
674
+ with pytest.raises(ValueError, match="Value of xi_rho for river"):
675
+ RiverForcing(
676
+ grid=iceland_test_grid,
677
+ start_time=self.start_time,
678
+ end_time=self.end_time,
679
+ indices=indices,
680
+ )
681
+
682
+ def test_duplicate_location(self, iceland_test_grid):
683
+ indices = {"Hvita(Olfusa)": [(8, 6), (8, 6)]} # Duplicate location
684
+ with pytest.raises(ValueError, match="Duplicate location"):
685
+ RiverForcing(
686
+ grid=iceland_test_grid,
687
+ start_time=self.start_time,
688
+ end_time=self.end_time,
689
+ indices=indices,
690
+ )