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.
Files changed (221) hide show
  1. roms_tools/__init__.py +3 -0
  2. roms_tools/analysis/cdr_analysis.py +203 -0
  3. roms_tools/analysis/cdr_ensemble.py +198 -0
  4. roms_tools/analysis/roms_output.py +80 -46
  5. roms_tools/data/grids/GLORYS_global_grid.nc +0 -0
  6. roms_tools/download.py +4 -0
  7. roms_tools/plot.py +113 -51
  8. roms_tools/setup/boundary_forcing.py +45 -20
  9. roms_tools/setup/cdr_forcing.py +122 -8
  10. roms_tools/setup/cdr_release.py +161 -8
  11. roms_tools/setup/grid.py +150 -141
  12. roms_tools/setup/initial_conditions.py +113 -48
  13. roms_tools/setup/{datasets.py → lat_lon_datasets.py} +443 -938
  14. roms_tools/setup/mask.py +63 -7
  15. roms_tools/setup/nesting.py +314 -117
  16. roms_tools/setup/river_datasets.py +527 -0
  17. roms_tools/setup/river_forcing.py +46 -20
  18. roms_tools/setup/surface_forcing.py +7 -9
  19. roms_tools/setup/tides.py +2 -3
  20. roms_tools/setup/topography.py +8 -10
  21. roms_tools/setup/utils.py +396 -23
  22. roms_tools/tests/test_analysis/test_cdr_analysis.py +144 -0
  23. roms_tools/tests/test_analysis/test_cdr_ensemble.py +202 -0
  24. roms_tools/tests/test_analysis/test_roms_output.py +61 -3
  25. roms_tools/tests/test_setup/test_boundary_forcing.py +54 -52
  26. roms_tools/tests/test_setup/test_cdr_forcing.py +54 -0
  27. roms_tools/tests/test_setup/test_cdr_release.py +118 -1
  28. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_east/c/0/0/0 +0 -0
  29. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_north/c/0/0/0 +0 -0
  30. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/c/0/0/0 +0 -0
  31. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_east/c/0/0/0 +0 -0
  32. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_north/c/0/0/0 +0 -0
  33. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/c/0/0/0 +0 -0
  34. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_east/c/0/0/0 +0 -0
  35. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_north/c/0/0/0 +0 -0
  36. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/c/0/0/0 +0 -0
  37. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_east/c/0/0/0 +0 -0
  38. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_north/c/0/0/0 +0 -0
  39. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/c/0/0/0 +0 -0
  40. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_east/c/0/0/0 +0 -0
  41. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_north/c/0/0/0 +0 -0
  42. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/c/0/0/0 +0 -0
  43. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_east/c/0/0/0 +0 -0
  44. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_north/c/0/0/0 +0 -0
  45. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/c/0/0/0 +0 -0
  46. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_east/c/0/0/0 +0 -0
  47. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_north/c/0/0/0 +0 -0
  48. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/c/0/0/0 +0 -0
  49. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_east/c/0/0/0 +0 -0
  50. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_north/c/0/0/0 +0 -0
  51. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/c/0/0/0 +0 -0
  52. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_east/c/0/0/0 +0 -0
  53. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_north/c/0/0/0 +0 -0
  54. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/c/0/0/0 +0 -0
  55. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_east/c/0/0/0 +0 -0
  56. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_north/c/0/0/0 +0 -0
  57. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/c/0/0/0 +0 -0
  58. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_east/c/0/0/0 +0 -0
  59. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_north/c/0/0/0 +0 -0
  60. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/c/0/0/0 +0 -0
  61. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_east/c/0/0/0 +0 -0
  62. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_north/c/0/0/0 +0 -0
  63. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/c/0/0/0 +0 -0
  64. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_east/c/0/0/0 +0 -0
  65. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_north/c/0/0/0 +0 -0
  66. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/c/0/0/0 +0 -0
  67. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_east/c/0/0/0 +0 -0
  68. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_north/c/0/0/0 +0 -0
  69. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/c/0/0/0 +0 -0
  70. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_east/c/0/0/0 +0 -0
  71. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_north/c/0/0/0 +0 -0
  72. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/c/0/0/0 +0 -0
  73. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_east/c/0/0/0 +0 -0
  74. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_north/c/0/0/0 +0 -0
  75. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/c/0/0/0 +0 -0
  76. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_east/c/0/0/0 +0 -0
  77. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_north/c/0/0/0 +0 -0
  78. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/c/0/0/0 +0 -0
  79. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_east/c/0/0/0 +0 -0
  80. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_north/c/0/0/0 +0 -0
  81. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/c/0/0/0 +0 -0
  82. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_east/c/0/0/0 +0 -0
  83. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_north/c/0/0/0 +0 -0
  84. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/c/0/0/0 +0 -0
  85. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_east/c/0/0/0 +0 -0
  86. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_north/c/0/0/0 +0 -0
  87. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/c/0/0/0 +0 -0
  88. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_east/c/0/0/0 +0 -0
  89. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_north/c/0/0/0 +0 -0
  90. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/c/0/0/0 +0 -0
  91. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_east/c/0/0/0 +0 -0
  92. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_north/c/0/0/0 +0 -0
  93. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/c/0/0/0 +0 -0
  94. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_east/c/0/0/0 +0 -0
  95. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_north/c/0/0/0 +0 -0
  96. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/c/0/0/0 +0 -0
  97. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_east/c/0/0/0 +0 -0
  98. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_north/c/0/0/0 +0 -0
  99. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/c/0/0/0 +0 -0
  100. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_east/c/0/0/0 +0 -0
  101. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_north/c/0/0/0 +0 -0
  102. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/c/0/0/0 +0 -0
  103. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_east/c/0/0/0 +0 -0
  104. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_north/c/0/0/0 +0 -0
  105. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/c/0/0/0 +0 -0
  106. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_east/c/0/0/0 +0 -0
  107. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_north/c/0/0/0 +0 -0
  108. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/c/0/0/0 +0 -0
  109. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_east/c/0/0/0 +0 -0
  110. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_north/c/0/0/0 +0 -0
  111. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/c/0/0/0 +0 -0
  112. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_east/c/0/0/0 +0 -0
  113. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_north/c/0/0/0 +0 -0
  114. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/c/0/0/0 +0 -0
  115. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_east/c/0/0/0 +0 -0
  116. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_north/c/0/0/0 +0 -0
  117. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/c/0/0/0 +0 -0
  118. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_east/c/0/0/0 +0 -0
  119. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_north/c/0/0/0 +0 -0
  120. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/c/0/0/0 +0 -0
  121. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zarr.json +406 -406
  122. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_east/c/0/0/0 +0 -0
  123. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_north/c/0/0/0 +0 -0
  124. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/c/0/0/0 +0 -0
  125. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_east/c/0/0/0 +0 -0
  126. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_north/c/0/0/0 +0 -0
  127. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_south/c/0/0/0 +0 -0
  128. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_west/c/0/0/0 +0 -0
  129. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_east/c/0/0/0 +0 -0
  130. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_north/c/0/0/0 +0 -0
  131. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_south/c/0/0/0 +0 -0
  132. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_west/c/0/0/0 +0 -0
  133. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_east/c/0/0/0 +0 -0
  134. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_north/c/0/0/0 +0 -0
  135. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_south/c/0/0/0 +0 -0
  136. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_west/c/0/0/0 +0 -0
  137. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_east/c/0/0 +0 -0
  138. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_north/c/0/0 +0 -0
  139. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_south/c/0/0 +0 -0
  140. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_west/c/0/0 +0 -0
  141. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_east/c/0/0/0 +0 -0
  142. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_north/c/0/0/0 +0 -0
  143. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_south/c/0/0/0 +0 -0
  144. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_west/c/0/0/0 +0 -0
  145. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_east/c/0/0 +0 -0
  146. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_north/c/0/0 +0 -0
  147. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_south/c/0/0 +0 -0
  148. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_west/c/0/0 +0 -0
  149. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zarr.json +182 -182
  150. roms_tools/tests/test_setup/test_data/grid.zarr/h/c/0/0 +0 -0
  151. roms_tools/tests/test_setup/test_data/grid.zarr/zarr.json +191 -191
  152. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/c/0/0 +0 -0
  153. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/zarr.json +210 -210
  154. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK/c/0/0/0/0 +0 -0
  155. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 +0 -0
  156. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC/c/0/0/0/0 +0 -0
  157. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 +0 -0
  158. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOC/c/0/0/0/0 +0 -0
  159. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOCr/c/0/0/0/0 +0 -0
  160. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DON/c/0/0/0/0 +0 -0
  161. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DONr/c/0/0/0/0 +0 -0
  162. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOP/c/0/0/0/0 +0 -0
  163. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOPr/c/0/0/0/0 +0 -0
  164. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Fe/c/0/0/0/0 +0 -0
  165. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Lig/c/0/0/0/0 +0 -0
  166. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NH4/c/0/0/0/0 +0 -0
  167. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NO3/c/0/0/0/0 +0 -0
  168. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/O2/c/0/0/0/0 +0 -0
  169. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/PO4/c/0/0/0/0 +0 -0
  170. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 +0 -0
  171. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatC/c/0/0/0/0 +0 -0
  172. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatChl/c/0/0/0/0 +0 -0
  173. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatFe/c/0/0/0/0 +0 -0
  174. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatP/c/0/0/0/0 +0 -0
  175. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatSi/c/0/0/0/0 +0 -0
  176. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazC/c/0/0/0/0 +0 -0
  177. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazChl/c/0/0/0/0 +0 -0
  178. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazFe/c/0/0/0/0 +0 -0
  179. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazP/c/0/0/0/0 +0 -0
  180. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/salt/c/0/0/0/0 +0 -0
  181. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spC/c/0/0/0/0 +0 -0
  182. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spCaCO3/c/0/0/0/0 +0 -0
  183. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spChl/c/0/0/0/0 +0 -0
  184. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spFe/c/0/0/0/0 +0 -0
  185. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spP/c/0/0/0/0 +0 -0
  186. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/temp/c/0/0/0/0 +0 -0
  187. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/u/c/0/0/0/0 +0 -0
  188. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ubar/c/0/0/0 +0 -0
  189. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/v/c/0/0/0/0 +0 -0
  190. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/vbar/c/0/0/0 +0 -0
  191. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zarr.json +182 -182
  192. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zooC/c/0/0/0/0 +0 -0
  193. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/salt/c/0/0/0/0 +0 -0
  194. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/temp/c/0/0/0/0 +0 -0
  195. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/u/c/0/0/0/0 +0 -0
  196. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ubar/c/0/0/0 +0 -0
  197. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/v/c/0/0/0/0 +0 -0
  198. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/vbar/c/0/0/0 +0 -0
  199. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/zarr.json +187 -187
  200. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Im/c/0/0/0 +0 -0
  201. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Re/c/0/0/0 +0 -0
  202. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Im/c/0/0/0 +0 -0
  203. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Re/c/0/0/0 +0 -0
  204. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/zarr.json +66 -66
  205. roms_tools/tests/test_setup/test_grid.py +236 -115
  206. roms_tools/tests/test_setup/test_initial_conditions.py +94 -41
  207. roms_tools/tests/test_setup/{test_datasets.py → test_lat_lon_datasets.py} +409 -100
  208. roms_tools/tests/test_setup/test_nesting.py +119 -31
  209. roms_tools/tests/test_setup/test_river_datasets.py +48 -0
  210. roms_tools/tests/test_setup/test_surface_forcing.py +2 -1
  211. roms_tools/tests/test_setup/test_utils.py +92 -2
  212. roms_tools/tests/test_setup/utils.py +71 -0
  213. roms_tools/tests/test_tiling/test_join.py +241 -0
  214. roms_tools/tests/test_utils.py +139 -17
  215. roms_tools/tiling/join.py +189 -0
  216. roms_tools/utils.py +131 -99
  217. {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/METADATA +12 -2
  218. {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/RECORD +221 -211
  219. {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/WHEEL +0 -0
  220. {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/licenses/LICENSE +0 -0
  221. {roms_tools-3.1.2.dist-info → roms_tools-3.3.0.dist-info}/top_level.txt +0 -0
roms_tools/setup/utils.py CHANGED
@@ -1,11 +1,13 @@
1
1
  import importlib.metadata
2
2
  import logging
3
+ import time
4
+ import typing
3
5
  from collections.abc import Sequence
4
6
  from dataclasses import asdict, fields, is_dataclass
5
- from datetime import datetime
7
+ from datetime import datetime, timedelta
6
8
  from enum import StrEnum
7
9
  from pathlib import Path
8
- from typing import Any
10
+ from typing import Any, Literal, TypeAlias
9
11
 
10
12
  import cftime
11
13
  import numba as nb
@@ -18,11 +20,55 @@ from pydantic import BaseModel
18
20
  from roms_tools.constants import R_EARTH
19
21
  from roms_tools.utils import interpolate_from_rho_to_u, interpolate_from_rho_to_v
20
22
 
23
+ if typing.TYPE_CHECKING:
24
+ from roms_tools.setup.grid import Grid
25
+
21
26
  yaml.SafeDumper.add_multi_representer(
22
27
  StrEnum,
23
28
  yaml.representer.SafeRepresenter.represent_str,
24
29
  )
25
30
 
31
+ HEADER_SZ = 96
32
+ HEADER_CHAR = "="
33
+
34
+ RawDataSource: TypeAlias = dict[str, str | Path | list[str | Path] | bool]
35
+
36
+
37
+ def log_the_separator() -> None:
38
+ """Log a separator line using HEADER_CHAR repeated HEADER_SZ times."""
39
+ logging.info(HEADER_CHAR * HEADER_SZ)
40
+
41
+
42
+ class Timed:
43
+ """Context manager to time a block and log messages."""
44
+
45
+ def __init__(self, message: str = "", verbose: bool = True) -> None:
46
+ """
47
+ Initialize the context manager.
48
+
49
+ Parameters
50
+ ----------
51
+ message : str, optional
52
+ A log message printed at the start of the block (default: "").
53
+ verbose : bool, optional
54
+ Whether to log timing information (default: True).
55
+ """
56
+ self.message = message
57
+ self.verbose = verbose
58
+ self.start: float | None = None
59
+
60
+ def __enter__(self) -> "Timed":
61
+ if self.verbose:
62
+ self.start = time.time()
63
+ if self.message:
64
+ logging.info(self.message)
65
+ return self
66
+
67
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
68
+ if self.verbose and self.start is not None:
69
+ logging.info(f"Total time: {time.time() - self.start:.3f} seconds")
70
+ log_the_separator()
71
+
26
72
 
27
73
  def nan_check(field, mask, error_message=None) -> None:
28
74
  """Checks for NaN values at wet points in the field.
@@ -437,157 +483,193 @@ def get_variable_metadata():
437
483
  "long_name": "dissolved inorganic phosphate",
438
484
  "units": "mmol/m^3",
439
485
  "flux_units": "mmol/s",
486
+ "integrated_units": "mmol",
440
487
  },
441
488
  "NO3": {
442
489
  "long_name": "dissolved inorganic nitrate",
443
490
  "units": "mmol/m^3",
444
491
  "flux_units": "mmol/s",
492
+ "integrated_units": "mmol",
445
493
  },
446
494
  "SiO3": {
447
495
  "long_name": "dissolved inorganic silicate",
448
496
  "units": "mmol/m^3",
449
497
  "flux_units": "mmol/s",
498
+ "integrated_units": "mmol",
450
499
  },
451
500
  "NH4": {
452
501
  "long_name": "dissolved ammonia",
453
502
  "units": "mmol/m^3",
454
503
  "flux_units": "mmol/s",
504
+ "integrated_units": "mmol",
455
505
  },
456
506
  "Fe": {
457
507
  "long_name": "dissolved inorganic iron",
458
508
  "units": "mmol/m^3",
459
509
  "flux_units": "mmol/s",
510
+ "integrated_units": "mmol",
460
511
  },
461
512
  "Lig": {
462
513
  "long_name": "iron binding ligand",
463
514
  "units": "mmol/m^3",
464
515
  "flux_units": "mmol/s",
516
+ "integrated_units": "mmol",
465
517
  },
466
518
  "O2": {
467
519
  "long_name": "dissolved oxygen",
468
520
  "units": "mmol/m^3",
469
521
  "flux_units": "mmol/s",
522
+ "integrated_units": "mmol",
470
523
  },
471
524
  "DIC": {
472
525
  "long_name": "dissolved inorganic carbon",
473
526
  "units": "mmol/m^3",
474
527
  "flux_units": "mmol/s",
528
+ "integrated_units": "mmol",
475
529
  },
476
530
  "DIC_ALT_CO2": {
477
531
  "long_name": "dissolved inorganic carbon, alternative CO2",
478
532
  "units": "mmol/m^3",
479
533
  "flux_units": "meq/s",
534
+ "integrated_units": "meq",
535
+ },
536
+ "ALK": {
537
+ "long_name": "alkalinity",
538
+ "units": "meq/m^3",
539
+ "flux_units": "meq/s",
540
+ "integrated_units": "meq",
480
541
  },
481
- "ALK": {"long_name": "alkalinity", "units": "meq/m^3", "flux_units": "meq/s"},
482
542
  "ALK_ALT_CO2": {
483
543
  "long_name": "alkalinity, alternative CO2",
484
544
  "units": "meq/m^3",
485
545
  "flux_units": "meq/s",
546
+ "integrated_units": "meq",
486
547
  },
487
548
  "DOC": {
488
549
  "long_name": "dissolved organic carbon",
489
550
  "units": "mmol/m^3",
490
551
  "flux_units": "mmol/s",
552
+ "integrated_units": "mmol",
491
553
  },
492
554
  "DON": {
493
555
  "long_name": "dissolved organic nitrogen",
494
556
  "units": "mmol/m^3",
495
557
  "flux_units": "mmol/s",
558
+ "integrated_units": "mmol",
496
559
  },
497
560
  "DOP": {
498
561
  "long_name": "dissolved organic phosphorus",
499
562
  "units": "mmol/m^3",
500
563
  "flux_units": "mmol/s",
564
+ "integrated_units": "mmol",
501
565
  },
502
566
  "DOCr": {
503
567
  "long_name": "refractory dissolved organic carbon",
504
568
  "units": "mmol/m^3",
505
569
  "flux_units": "mmol/s",
570
+ "integrated_units": "mmol",
506
571
  },
507
572
  "DONr": {
508
573
  "long_name": "refractory dissolved organic nitrogen",
509
574
  "units": "mmol/m^3",
510
575
  "flux_units": "mmol/s",
576
+ "integrated_units": "mmol",
511
577
  },
512
578
  "DOPr": {
513
579
  "long_name": "refractory dissolved organic phosphorus",
514
580
  "units": "mmol/m^3",
515
581
  "flux_units": "mmol/s",
582
+ "integrated_units": "mmol",
516
583
  },
517
584
  "zooC": {
518
585
  "long_name": "zooplankton carbon",
519
586
  "units": "mmol/m^3",
520
587
  "flux_units": "mmol/s",
588
+ "integrated_units": "mmol",
521
589
  },
522
590
  "spChl": {
523
591
  "long_name": "small phytoplankton chlorophyll",
524
592
  "units": "mg/m^3",
525
593
  "flux_units": "mg/s",
594
+ "integrated_units": "mg",
526
595
  },
527
596
  "spC": {
528
597
  "long_name": "small phytoplankton carbon",
529
598
  "units": "mmol/m^3",
530
599
  "flux_units": "mmol/s",
600
+ "integrated_units": "mmol",
531
601
  },
532
602
  "spP": {
533
603
  "long_name": "small phytoplankton phosphorous",
534
604
  "units": "mmol/m^3",
535
605
  "flux_units": "mmol/s",
606
+ "integrated_units": "mmol",
536
607
  },
537
608
  "spFe": {
538
609
  "long_name": "small phytoplankton iron",
539
610
  "units": "mmol/m^3",
540
611
  "flux_units": "mmol/s",
612
+ "integrated_units": "mmol",
541
613
  },
542
614
  "spCaCO3": {
543
615
  "long_name": "small phytoplankton CaCO3",
544
616
  "units": "mmol/m^3",
545
617
  "flux_units": "mmol/s",
618
+ "integrated_units": "mmol",
546
619
  },
547
620
  "diatChl": {
548
621
  "long_name": "diatom chloropyll",
549
622
  "units": "mg/m^3",
550
623
  "flux_units": "mg/s",
624
+ "integrated_units": "mg",
551
625
  },
552
626
  "diatC": {
553
627
  "long_name": "diatom carbon",
554
628
  "units": "mmol/m^3",
555
629
  "flux_units": "mmol/s",
630
+ "integrated_units": "mmol",
556
631
  },
557
632
  "diatP": {
558
633
  "long_name": "diatom phosphorus",
559
634
  "units": "mmol/m^3",
560
635
  "flux_units": "mmol/s",
636
+ "integrated_units": "mmol",
561
637
  },
562
638
  "diatFe": {
563
639
  "long_name": "diatom iron",
564
640
  "units": "mmol/m^3",
565
641
  "flux_units": "mmol/s",
642
+ "integrated_units": "mmol",
566
643
  },
567
644
  "diatSi": {
568
645
  "long_name": "diatom silicate",
569
646
  "units": "mmol/m^3",
570
647
  "flux_units": "mmol/s",
648
+ "integrated_units": "mmol",
571
649
  },
572
650
  "diazChl": {
573
651
  "long_name": "diazotroph chloropyll",
574
652
  "units": "mg/m^3",
575
653
  "flux_units": "mg/s",
654
+ "integrated_units": "mg",
576
655
  },
577
656
  "diazC": {
578
657
  "long_name": "diazotroph carbon",
579
658
  "units": "mmol/m^3",
580
659
  "flux_units": "mmol/s",
660
+ "integrated_units": "mmol",
581
661
  },
582
662
  "diazP": {
583
663
  "long_name": "diazotroph phosphorus",
584
664
  "units": "mmol/m^3",
585
665
  "flux_units": "mmol/s",
666
+ "integrated_units": "mmol",
586
667
  },
587
668
  "diazFe": {
588
669
  "long_name": "diazotroph iron",
589
670
  "units": "mmol/m^3",
590
671
  "flux_units": "mmol/s",
672
+ "integrated_units": "mmol",
591
673
  },
592
674
  "pco2_air": {"long_name": "atmospheric pCO2", "units": "ppmv"},
593
675
  "pco2_air_alt": {
@@ -720,7 +802,10 @@ def compute_missing_surface_bgc_variables(bgc_data):
720
802
  return bgc_data
721
803
 
722
804
 
723
- def get_tracer_metadata_dict(include_bgc=True, with_flux_units=False):
805
+ def get_tracer_metadata_dict(
806
+ include_bgc: bool = True,
807
+ unit_type: Literal["concentration", "flux", "integrated"] = "concentration",
808
+ ):
724
809
  """Generate a dictionary containing metadata for model tracers.
725
810
 
726
811
  The returned dictionary maps tracer names to their associated units and long names.
@@ -732,9 +817,8 @@ def get_tracer_metadata_dict(include_bgc=True, with_flux_units=False):
732
817
  If True (default), includes biogeochemical tracers in the output.
733
818
  If False, returns only physical tracers (e.g., temperature, salinity).
734
819
 
735
- with_flux_units : bool, optional
736
- If True, uses units appropriate for tracer fluxes (e.g., mmol/s).
737
- If False (default), uses units appropriate for tracer concentrations (e.g., mmol/m³).
820
+ unit_type : str
821
+ One of "concentration" (default), "flux", or "integrated".
738
822
 
739
823
  Returns
740
824
  -------
@@ -784,15 +868,19 @@ def get_tracer_metadata_dict(include_bgc=True, with_flux_units=False):
784
868
 
785
869
  metadata = get_variable_metadata()
786
870
 
787
- tracer_dict = {
788
- tracer: {
789
- "units": metadata[tracer]["flux_units"]
790
- if with_flux_units
791
- else metadata[tracer]["units"],
871
+ tracer_dict = {}
872
+ for tracer in tracer_names:
873
+ if unit_type == "flux":
874
+ unit = metadata[tracer]["flux_units"]
875
+ elif unit_type == "integrated":
876
+ unit = metadata[tracer].get("integrated_units", None)
877
+ else: # default to concentration units
878
+ unit = metadata[tracer]["units"]
879
+
880
+ tracer_dict[tracer] = {
881
+ "units": unit,
792
882
  "long_name": metadata[tracer]["long_name"],
793
883
  }
794
- for tracer in tracer_names
795
- }
796
884
 
797
885
  return tracer_dict
798
886
 
@@ -819,7 +907,8 @@ def add_tracer_metadata_to_ds(ds, include_bgc=True, with_flux_units=False):
819
907
  xarray.Dataset
820
908
  The dataset with added tracer metadata.
821
909
  """
822
- tracer_dict = get_tracer_metadata_dict(include_bgc, with_flux_units)
910
+ unit_type = "flux" if with_flux_units else "concentration"
911
+ tracer_dict = get_tracer_metadata_dict(include_bgc, unit_type=unit_type)
823
912
 
824
913
  tracer_names = list(tracer_dict.keys())
825
914
  tracer_units = [tracer_dict[tracer]["units"] for tracer in tracer_names]
@@ -1045,22 +1134,47 @@ def group_by_year(ds, filepath):
1045
1134
  return dataset_list, output_filenames
1046
1135
 
1047
1136
 
1048
- def get_target_coords(grid, use_coarse_grid=False):
1049
- """Retrieves longitude and latitude coordinates from the grid, adjusting them based
1050
- on longitude range.
1137
+ def get_target_coords(
1138
+ grid: "Grid", use_coarse_grid: bool = False
1139
+ ) -> dict[str, xr.DataArray | bool | None]:
1140
+ """
1141
+ Retrieve longitude, latitude, and auxiliary grid coordinates, adjusting for
1142
+ longitude ranges and coarse grid usage.
1051
1143
 
1052
1144
  Parameters
1053
1145
  ----------
1054
1146
  grid : Grid
1055
- Object representing the grid information used for the model.
1147
+ Grid object.
1056
1148
  use_coarse_grid : bool, optional
1057
- Use coarse grid data if True. Defaults to False.
1149
+ If True, use the coarse grid variables (`lat_coarse`, `lon_coarse`, etc.)
1150
+ instead of the native grid. Defaults to False.
1058
1151
 
1059
1152
  Returns
1060
1153
  -------
1061
- dict
1062
- Dictionary containing the longitude, latitude, angle and mask arrays, along with a boolean indicating
1063
- if the grid straddles the meridian.
1154
+ dict[str, xr.DataArray | bool | None]
1155
+ Dictionary containing the following keys:
1156
+
1157
+ - `"lat"` : xr.DataArray
1158
+ Latitude at rho points.
1159
+ - `"lon"` : xr.DataArray
1160
+ Longitude at rho points, adjusted to -180 to 180 or 0 to 360 range.
1161
+ - `"lat_psi"` : xr.DataArray | None
1162
+ Latitude at psi points, if available.
1163
+ - `"lon_psi"` : xr.DataArray | None
1164
+ Longitude at psi points, if available.
1165
+ - `"angle"` : xr.DataArray
1166
+ Grid rotation angle.
1167
+ - `"mask"` : xr.DataArray | None
1168
+ Land/sea mask at rho points.
1169
+ - `"straddle"` : bool
1170
+ True if the grid crosses the Greenwich meridian, False otherwise.
1171
+
1172
+ Notes
1173
+ -----
1174
+ - If `grid.straddle` is False and the ROMS domain lies more than 5° from
1175
+ the Greenwich meridian, longitudes are adjusted to 0-360 range.
1176
+ - Renaming of coarse grid dimensions is applied to match the rho-point
1177
+ naming convention (`eta_rho`, `xi_rho`) for compatibility with ROMS-Tools.
1064
1178
  """
1065
1179
  # Select grid variables based on whether the coarse grid is used
1066
1180
  if use_coarse_grid:
@@ -1851,3 +1965,262 @@ def validate_names(
1851
1965
  names = names[:max_to_plot]
1852
1966
 
1853
1967
  return names
1968
+
1969
+
1970
+ def check_dataset(
1971
+ ds: xr.Dataset,
1972
+ dim_names: dict[str, str],
1973
+ var_names: dict[str, str],
1974
+ opt_var_names: dict[str, str] | None = None,
1975
+ ) -> None:
1976
+ """Check if the dataset contains the specified variables and dimensions.
1977
+
1978
+ Parameters
1979
+ ----------
1980
+ ds : xr.Dataset
1981
+ The xarray Dataset to check.
1982
+ dim_names: Dict[str, str], optional
1983
+ Dictionary specifying the names of dimensions in the dataset.
1984
+ var_names: Dict[str, str]
1985
+ Dictionary of variable names that are required in the dataset.
1986
+ opt_var_names : Optional[Dict[str, str]], optional
1987
+ Dictionary of optional variable names.
1988
+ These variables are not strictly required, and the function will not raise an error if they are missing.
1989
+ Default is None, meaning no optional variables are considered.
1990
+
1991
+
1992
+ Raises
1993
+ ------
1994
+ ValueError
1995
+ If the dataset does not contain the specified variables or dimensions.
1996
+ """
1997
+ missing_dims = [dim for dim in dim_names.values() if dim not in ds.dims]
1998
+ if missing_dims:
1999
+ raise ValueError(
2000
+ f"Dataset does not contain all required dimensions. The following dimensions are missing: {missing_dims}"
2001
+ )
2002
+
2003
+ missing_vars = [var for var in var_names.values() if var not in ds.data_vars]
2004
+ if missing_vars:
2005
+ raise ValueError(
2006
+ f"Dataset does not contain all required variables. The following variables are missing: {missing_vars}"
2007
+ )
2008
+
2009
+ if opt_var_names:
2010
+ missing_optional_vars = [
2011
+ var for var in opt_var_names.values() if var not in ds.data_vars
2012
+ ]
2013
+ if missing_optional_vars:
2014
+ logging.warning(
2015
+ f"Optional variables missing (but not critical): {missing_optional_vars}"
2016
+ )
2017
+
2018
+
2019
+ def select_relevant_times(
2020
+ ds: xr.Dataset,
2021
+ time_dim: str,
2022
+ start_time: datetime,
2023
+ end_time: datetime | None = None,
2024
+ climatology: bool = False,
2025
+ allow_flex_time: bool = False,
2026
+ ) -> xr.Dataset:
2027
+ """
2028
+ Select a subset of the dataset based on time constraints.
2029
+
2030
+ This function supports two main use cases:
2031
+
2032
+ 1. **Time range selection (start_time + end_time provided):**
2033
+ - Returns all records strictly between `start_time` and `end_time`.
2034
+ - Ensures at least one record at or before `start_time` and one record at or
2035
+ after `end_time` are included, even if they fall outside the strict range.
2036
+
2037
+ 2. **Initial condition selection (start_time provided, end_time=None):**
2038
+ - Delegates to `_select_initial_time`, which reduces the dataset to exactly one
2039
+ time entry.
2040
+ - If `allow_flex_time=True`, a +24-hour buffer around `start_time` is allowed,
2041
+ and the closest timestamp is chosen.
2042
+ - If `allow_flex_time=False`, requires an exact timestamp match.
2043
+
2044
+ Additional behavior:
2045
+ - If `climatology=True`, the dataset must contain exactly 12 time steps. If valid,
2046
+ the climatology dataset is returned without further filtering.
2047
+ - If the dataset uses `cftime` datetime objects, these are converted to
2048
+ `np.datetime64` before filtering.
2049
+
2050
+ Parameters
2051
+ ----------
2052
+ ds : xr.Dataset
2053
+ The dataset to filter. Must contain a valid time dimension.
2054
+ time_dim : str
2055
+ Name of the time dimension in `ds`.
2056
+ start_time : datetime
2057
+ Start time for filtering.
2058
+ end_time : datetime or None
2059
+ End time for filtering. If `None`, the function assumes an initial condition
2060
+ use case and selects exactly one timestamp.
2061
+ climatology : bool, optional
2062
+ If True, requires exactly 12 time steps and bypasses normal filtering.
2063
+ Defaults to False.
2064
+ allow_flex_time : bool, optional
2065
+ Whether to allow a +24h search window after `start_time` when `end_time`
2066
+ is None. If False (default), requires an exact match.
2067
+
2068
+ Returns
2069
+ -------
2070
+ xr.Dataset
2071
+ A filtered dataset containing only the selected time entries.
2072
+
2073
+ Raises
2074
+ ------
2075
+ ValueError
2076
+ - If `climatology=True` but the dataset does not contain exactly 12 time steps.
2077
+ - If `climatology=False` and the dataset contains integer time values.
2078
+ - If no valid records are found within the requested range or window.
2079
+
2080
+ Warns
2081
+ -----
2082
+ UserWarning
2083
+ - If no records exist at or before `start_time` or at or after `end_time`.
2084
+ - If the specified time dimension does not exist in the dataset.
2085
+
2086
+ Notes
2087
+ -----
2088
+ - For initial conditions (end_time=None), see `_select_initial_time` for details
2089
+ on strict vs. flexible selection behavior.
2090
+ - Logs warnings instead of failing hard when boundary records are missing, and
2091
+ defaults to using the earliest or latest available time in such cases.
2092
+ """
2093
+ if time_dim not in ds.variables:
2094
+ logging.warning(
2095
+ f"Dataset does not contain time dimension '{time_dim}'. "
2096
+ "Please check variable naming or dataset structure."
2097
+ )
2098
+ return ds
2099
+
2100
+ time_type = get_time_type(ds[time_dim])
2101
+
2102
+ if climatology:
2103
+ if len(ds[time_dim]) != 12:
2104
+ raise ValueError(
2105
+ f"The dataset contains {len(ds[time_dim])} time steps, but the climatology flag is set to True, which requires exactly 12 time steps."
2106
+ )
2107
+ else:
2108
+ if time_type == "int":
2109
+ raise ValueError(
2110
+ "The dataset contains integer time values, which are only supported when the climatology flag is set to True. However, your climatology flag is set to False."
2111
+ )
2112
+ if time_type == "cftime":
2113
+ ds = ds.assign_coords({time_dim: convert_cftime_to_datetime(ds[time_dim])})
2114
+
2115
+ if not end_time:
2116
+ # Assume we are looking for exactly one time record for initial conditions
2117
+ return _select_initial_time(
2118
+ ds, time_dim, start_time, climatology, allow_flex_time
2119
+ )
2120
+
2121
+ if climatology:
2122
+ return ds
2123
+
2124
+ # Identify records before or at start_time
2125
+ before_start = ds[time_dim] <= np.datetime64(start_time)
2126
+ if before_start.any():
2127
+ closest_before_start = ds[time_dim].where(before_start, drop=True)[-1]
2128
+ else:
2129
+ logging.warning(f"No records found at or before the start_time: {start_time}.")
2130
+ closest_before_start = ds[time_dim][0]
2131
+
2132
+ # Identify records after or at end_time
2133
+ after_end = ds[time_dim] >= np.datetime64(end_time)
2134
+ if after_end.any():
2135
+ closest_after_end = ds[time_dim].where(after_end, drop=True).min()
2136
+ else:
2137
+ logging.warning(f"No records found at or after the end_time: {end_time}.")
2138
+ closest_after_end = ds[time_dim].max()
2139
+
2140
+ # Select records within the time range and add the closest before/after
2141
+ within_range = (ds[time_dim] > np.datetime64(start_time)) & (
2142
+ ds[time_dim] < np.datetime64(end_time)
2143
+ )
2144
+ selected_times = ds[time_dim].where(
2145
+ within_range
2146
+ | (ds[time_dim] == closest_before_start)
2147
+ | (ds[time_dim] == closest_after_end),
2148
+ drop=True,
2149
+ )
2150
+ ds = ds.sel({time_dim: selected_times})
2151
+
2152
+ return ds
2153
+
2154
+
2155
+ def _select_initial_time(
2156
+ ds: xr.Dataset,
2157
+ time_dim: str,
2158
+ ini_time: datetime,
2159
+ climatology: bool,
2160
+ allow_flex_time: bool = False,
2161
+ ) -> xr.Dataset:
2162
+ """Select exactly one initial time from dataset.
2163
+
2164
+ Parameters
2165
+ ----------
2166
+ ds : xr.Dataset
2167
+ The input dataset with a time dimension.
2168
+ time_dim : str
2169
+ Name of the time dimension.
2170
+ ini_time : datetime
2171
+ The desired initial time.
2172
+ allow_flex_time : bool
2173
+ - If True: allow a +24h window and pick the closest available timestamp.
2174
+ - If False (default): require an exact match, otherwise raise ValueError.
2175
+
2176
+ Returns
2177
+ -------
2178
+ xr.Dataset
2179
+ Dataset reduced to exactly one timestamp.
2180
+
2181
+ Raises
2182
+ ------
2183
+ ValueError
2184
+ If no matching time is found (when `allow_flex_time=False`), or no entries are
2185
+ available within the +24h window (when `allow_flex_time=True`).
2186
+ """
2187
+ if climatology:
2188
+ # Convert from timedelta64[ns] to fractional days
2189
+ ds["time"] = ds["time"] / np.timedelta64(1, "D")
2190
+ # Interpolate from climatology for initial conditions
2191
+ return interpolate_from_climatology(ds, time_dim, ini_time)
2192
+
2193
+ if allow_flex_time:
2194
+ # Look in time range [ini_time, ini_time + 24h)
2195
+ end_time = ini_time + timedelta(days=1)
2196
+ times = (np.datetime64(ini_time) <= ds[time_dim]) & (
2197
+ ds[time_dim] < np.datetime64(end_time)
2198
+ )
2199
+
2200
+ if np.all(~times):
2201
+ raise ValueError(
2202
+ f"No time entries found between {ini_time} and {end_time}."
2203
+ )
2204
+
2205
+ ds = ds.where(times, drop=True)
2206
+ if ds.sizes[time_dim] > 1:
2207
+ # Pick the time closest to start_time
2208
+ ds = ds.isel({time_dim: 0})
2209
+
2210
+ logging.warning(
2211
+ f"Selected time entry closest to the specified start_time in +24 hour range: {ds[time_dim].values}"
2212
+ )
2213
+
2214
+ else:
2215
+ # Strict match required
2216
+ if not (ds[time_dim].values == np.datetime64(ini_time)).any():
2217
+ raise ValueError(
2218
+ f"No exact match found for initial time {ini_time}. Consider setting allow_flex_time to True."
2219
+ )
2220
+
2221
+ ds = ds.sel({time_dim: np.datetime64(ini_time)})
2222
+
2223
+ if time_dim not in ds.dims:
2224
+ ds = ds.expand_dims(time_dim)
2225
+
2226
+ return ds