roms-tools 3.2.0__py3-none-any.whl → 3.4.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 (318) hide show
  1. roms_tools/__init__.py +1 -1
  2. roms_tools/analysis/cdr_ensemble.py +10 -13
  3. roms_tools/analysis/roms_output.py +5 -304
  4. roms_tools/{download.py → datasets/download.py} +1 -0
  5. roms_tools/{setup/datasets.py → datasets/lat_lon_datasets.py} +85 -854
  6. roms_tools/datasets/river_datasets.py +532 -0
  7. roms_tools/datasets/roms_dataset.py +767 -0
  8. roms_tools/datasets/utils.py +475 -0
  9. roms_tools/{setup/fill.py → fill.py} +110 -13
  10. roms_tools/plot.py +42 -34
  11. roms_tools/setup/boundary_forcing.py +52 -44
  12. roms_tools/setup/cdr_release.py +2 -4
  13. roms_tools/setup/grid.py +42 -17
  14. roms_tools/setup/initial_conditions.py +23 -23
  15. roms_tools/setup/nesting.py +270 -94
  16. roms_tools/setup/river_forcing.py +5 -5
  17. roms_tools/setup/surface_forcing.py +17 -12
  18. roms_tools/setup/tides.py +1 -1
  19. roms_tools/setup/topography.py +13 -7
  20. roms_tools/setup/utils.py +103 -294
  21. roms_tools/tests/test_analysis/test_cdr_ensemble.py +4 -6
  22. roms_tools/tests/test_analysis/test_roms_output.py +1 -220
  23. roms_tools/tests/{test_setup/test_datasets.py → test_datasets/test_lat_lon_datasets.py} +25 -64
  24. roms_tools/tests/test_datasets/test_river_datasets.py +48 -0
  25. roms_tools/tests/test_datasets/test_roms_dataset.py +539 -0
  26. roms_tools/tests/test_datasets/test_utils.py +527 -0
  27. roms_tools/tests/{test_setup/test_fill.py → test_fill.py} +72 -9
  28. roms_tools/tests/test_setup/test_boundary_forcing.py +57 -138
  29. roms_tools/tests/test_setup/test_cdr_release.py +4 -5
  30. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_east/c/0/0/0 +0 -0
  31. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_north/c/0/0/0 +0 -0
  32. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_east/c/0/0/0 +0 -0
  33. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_north/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_east/c/0/0/0 +0 -0
  37. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_north/c/0/0/0 +0 -0
  38. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_east/c/0/0/0 +0 -0
  39. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_north/c/0/0/0 +0 -0
  40. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_east/c/0/0/0 +0 -0
  41. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_north/c/0/0/0 +0 -0
  42. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_east/c/0/0/0 +0 -0
  43. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_north/c/0/0/0 +0 -0
  44. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_east/c/0/0/0 +0 -0
  45. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_north/c/0/0/0 +0 -0
  46. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_east/c/0/0/0 +0 -0
  47. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_north/c/0/0/0 +0 -0
  48. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_east/c/0/0/0 +0 -0
  49. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_north/c/0/0/0 +0 -0
  50. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_east/c/0/0/0 +0 -0
  51. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_north/c/0/0/0 +0 -0
  52. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_east/c/0/0/0 +0 -0
  53. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_north/c/0/0/0 +0 -0
  54. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_east/c/0/0/0 +0 -0
  55. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_north/c/0/0/0 +0 -0
  56. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_east/c/0/0/0 +0 -0
  57. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_north/c/0/0/0 +0 -0
  58. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_east/c/0/0/0 +0 -0
  59. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_north/c/0/0/0 +0 -0
  60. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_east/c/0/0/0 +0 -0
  61. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_north/c/0/0/0 +0 -0
  62. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_east/c/0/0/0 +0 -0
  63. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_north/c/0/0/0 +0 -0
  64. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_east/c/0/0/0 +0 -0
  65. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_north/c/0/0/0 +0 -0
  66. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_east/c/0/0/0 +0 -0
  67. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_north/c/0/0/0 +0 -0
  68. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_east/c/0/0/0 +0 -0
  69. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_north/c/0/0/0 +0 -0
  70. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_east/c/0/0/0 +0 -0
  71. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_north/c/0/0/0 +0 -0
  72. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_east/c/0/0/0 +0 -0
  73. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_north/c/0/0/0 +0 -0
  74. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_east/c/0/0/0 +0 -0
  75. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_north/c/0/0/0 +0 -0
  76. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_east/c/0/0/0 +0 -0
  77. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_north/c/0/0/0 +0 -0
  78. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_east/c/0/0/0 +0 -0
  79. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_north/c/0/0/0 +0 -0
  80. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_east/c/0/0/0 +0 -0
  81. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_north/c/0/0/0 +0 -0
  82. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_east/c/0/0/0 +0 -0
  83. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_north/c/0/0/0 +0 -0
  84. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_east/c/0/0/0 +0 -0
  85. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_north/c/0/0/0 +0 -0
  86. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_east/c/0/0/0 +0 -0
  87. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_north/c/0/0/0 +0 -0
  88. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_east/c/0/0/0 +0 -0
  89. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_north/c/0/0/0 +0 -0
  90. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_east/c/0/0/0 +0 -0
  91. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_north/c/0/0/0 +0 -0
  92. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zarr.json +289 -2017
  93. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_east/c/0/0/0 +0 -0
  94. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_north/c/0/0/0 +0 -0
  95. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/zarr.json +294 -2022
  96. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_east/c/0/0/0 +0 -0
  97. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_north/c/0/0/0 +0 -0
  98. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_south/c/0/0/0 +0 -0
  99. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_west/c/0/0/0 +0 -0
  100. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_east/c/0/0/0 +0 -0
  101. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_north/c/0/0/0 +0 -0
  102. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_south/c/0/0/0 +0 -0
  103. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_west/c/0/0/0 +0 -0
  104. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_east/c/0/0/0 +0 -0
  105. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_north/c/0/0/0 +0 -0
  106. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_south/c/0/0/0 +0 -0
  107. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_west/c/0/0/0 +0 -0
  108. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_east/c/0/0 +0 -0
  109. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_north/c/0/0 +0 -0
  110. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_south/c/0/0 +0 -0
  111. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_west/c/0/0 +0 -0
  112. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_east/c/0/0/0 +0 -0
  113. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_north/c/0/0/0 +0 -0
  114. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_south/c/0/0/0 +0 -0
  115. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_west/c/0/0/0 +0 -0
  116. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_east/c/0/0 +0 -0
  117. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_north/c/0/0 +0 -0
  118. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_south/c/0/0 +0 -0
  119. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_west/c/0/0 +0 -0
  120. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zarr.json +182 -182
  121. roms_tools/tests/test_setup/test_data/grid.zarr/h/c/0/0 +0 -0
  122. roms_tools/tests/test_setup/test_data/grid.zarr/zarr.json +191 -191
  123. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/c/0/0 +0 -0
  124. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/zarr.json +210 -210
  125. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK/c/0/0/0/0 +0 -0
  126. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 +0 -0
  127. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC/c/0/0/0/0 +0 -0
  128. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 +0 -0
  129. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOC/c/0/0/0/0 +0 -0
  130. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOCr/c/0/0/0/0 +0 -0
  131. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DON/c/0/0/0/0 +0 -0
  132. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DONr/c/0/0/0/0 +0 -0
  133. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOP/c/0/0/0/0 +0 -0
  134. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOPr/c/0/0/0/0 +0 -0
  135. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Fe/c/0/0/0/0 +0 -0
  136. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Lig/c/0/0/0/0 +0 -0
  137. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NH4/c/0/0/0/0 +0 -0
  138. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NO3/c/0/0/0/0 +0 -0
  139. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/O2/c/0/0/0/0 +0 -0
  140. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/PO4/c/0/0/0/0 +0 -0
  141. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 +0 -0
  142. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatC/c/0/0/0/0 +0 -0
  143. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatChl/c/0/0/0/0 +0 -0
  144. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatFe/c/0/0/0/0 +0 -0
  145. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatP/c/0/0/0/0 +0 -0
  146. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatSi/c/0/0/0/0 +0 -0
  147. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazC/c/0/0/0/0 +0 -0
  148. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazChl/c/0/0/0/0 +0 -0
  149. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazFe/c/0/0/0/0 +0 -0
  150. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazP/c/0/0/0/0 +0 -0
  151. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/salt/c/0/0/0/0 +0 -0
  152. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spC/c/0/0/0/0 +0 -0
  153. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spCaCO3/c/0/0/0/0 +0 -0
  154. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spChl/c/0/0/0/0 +0 -0
  155. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spFe/c/0/0/0/0 +0 -0
  156. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spP/c/0/0/0/0 +0 -0
  157. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/temp/c/0/0/0/0 +0 -0
  158. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/u/c/0/0/0/0 +0 -0
  159. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ubar/c/0/0/0 +0 -0
  160. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/v/c/0/0/0/0 +0 -0
  161. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/vbar/c/0/0/0 +0 -0
  162. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zarr.json +182 -182
  163. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zooC/c/0/0/0/0 +0 -0
  164. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/salt/c/0/0/0/0 +0 -0
  165. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/temp/c/0/0/0/0 +0 -0
  166. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/u/c/0/0/0/0 +0 -0
  167. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ubar/c/0/0/0 +0 -0
  168. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/v/c/0/0/0/0 +0 -0
  169. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/vbar/c/0/0/0 +0 -0
  170. roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/zarr.json +187 -187
  171. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Im/c/0/0/0 +0 -0
  172. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Re/c/0/0/0 +0 -0
  173. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Im/c/0/0/0 +0 -0
  174. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Re/c/0/0/0 +0 -0
  175. roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/zarr.json +66 -66
  176. roms_tools/tests/test_setup/test_grid.py +56 -1
  177. roms_tools/tests/test_setup/test_initial_conditions.py +3 -94
  178. roms_tools/tests/test_setup/test_nesting.py +119 -30
  179. roms_tools/tests/test_setup/test_surface_forcing.py +2 -2
  180. roms_tools/tests/test_setup/test_tides.py +1 -1
  181. roms_tools/tests/test_setup/test_utils.py +100 -15
  182. roms_tools/tests/test_tiling/test_partition.py +63 -15
  183. roms_tools/tests/test_utils.py +78 -0
  184. roms_tools/tiling/partition.py +81 -211
  185. roms_tools/utils.py +193 -0
  186. {roms_tools-3.2.0.dist-info → roms_tools-3.4.0.dist-info}/METADATA +1 -1
  187. {roms_tools-3.2.0.dist-info → roms_tools-3.4.0.dist-info}/RECORD +190 -312
  188. {roms_tools-3.2.0.dist-info → roms_tools-3.4.0.dist-info}/WHEEL +1 -1
  189. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/c/0/0/0 +0 -0
  190. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/zarr.json +0 -54
  191. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/c/0/0/0 +0 -0
  192. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/zarr.json +0 -54
  193. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/c/0/0/0 +0 -0
  194. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/zarr.json +0 -54
  195. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/c/0/0/0 +0 -0
  196. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/zarr.json +0 -54
  197. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/c/0/0/0 +0 -0
  198. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/zarr.json +0 -54
  199. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/c/0/0/0 +0 -0
  200. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/zarr.json +0 -54
  201. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/c/0/0/0 +0 -0
  202. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/zarr.json +0 -54
  203. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/c/0/0/0 +0 -0
  204. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/zarr.json +0 -54
  205. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/c/0/0/0 +0 -0
  206. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/zarr.json +0 -54
  207. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/c/0/0/0 +0 -0
  208. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/zarr.json +0 -54
  209. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/c/0/0/0 +0 -0
  210. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/zarr.json +0 -54
  211. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/c/0/0/0 +0 -0
  212. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/zarr.json +0 -54
  213. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/c/0/0/0 +0 -0
  214. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/zarr.json +0 -54
  215. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/c/0/0/0 +0 -0
  216. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/zarr.json +0 -54
  217. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/c/0/0/0 +0 -0
  218. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/zarr.json +0 -54
  219. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/c/0/0/0 +0 -0
  220. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/zarr.json +0 -54
  221. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/c/0/0/0 +0 -0
  222. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/zarr.json +0 -54
  223. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/c/0/0/0 +0 -0
  224. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/zarr.json +0 -54
  225. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/c/0/0/0 +0 -0
  226. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/zarr.json +0 -54
  227. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/c/0/0/0 +0 -0
  228. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/zarr.json +0 -54
  229. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/c/0/0/0 +0 -0
  230. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/zarr.json +0 -54
  231. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/c/0/0/0 +0 -0
  232. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/zarr.json +0 -54
  233. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/c/0/0/0 +0 -0
  234. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/zarr.json +0 -54
  235. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/c/0/0/0 +0 -0
  236. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/zarr.json +0 -54
  237. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/c/0/0/0 +0 -0
  238. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/zarr.json +0 -54
  239. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/c/0/0/0 +0 -0
  240. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/zarr.json +0 -54
  241. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/c/0/0/0 +0 -0
  242. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/zarr.json +0 -54
  243. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/c/0/0/0 +0 -0
  244. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/zarr.json +0 -54
  245. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/c/0/0/0 +0 -0
  246. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/zarr.json +0 -54
  247. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/c/0/0/0 +0 -0
  248. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/zarr.json +0 -54
  249. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/c/0/0/0 +0 -0
  250. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/zarr.json +0 -54
  251. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/c/0/0/0 +0 -0
  252. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/zarr.json +0 -54
  253. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_ALT_CO2_west/c/0/0/0 +0 -0
  254. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_ALT_CO2_west/zarr.json +0 -54
  255. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_west/c/0/0/0 +0 -0
  256. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_west/zarr.json +0 -54
  257. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_ALT_CO2_west/c/0/0/0 +0 -0
  258. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_ALT_CO2_west/zarr.json +0 -54
  259. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_west/c/0/0/0 +0 -0
  260. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_west/zarr.json +0 -54
  261. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOC_west/c/0/0/0 +0 -0
  262. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOC_west/zarr.json +0 -54
  263. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOCr_west/c/0/0/0 +0 -0
  264. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOCr_west/zarr.json +0 -54
  265. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DON_west/c/0/0/0 +0 -0
  266. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DON_west/zarr.json +0 -54
  267. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DONr_west/c/0/0/0 +0 -0
  268. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DONr_west/zarr.json +0 -54
  269. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOP_west/c/0/0/0 +0 -0
  270. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOP_west/zarr.json +0 -54
  271. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOPr_west/c/0/0/0 +0 -0
  272. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOPr_west/zarr.json +0 -54
  273. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Fe_west/c/0/0/0 +0 -0
  274. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Fe_west/zarr.json +0 -54
  275. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Lig_west/c/0/0/0 +0 -0
  276. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Lig_west/zarr.json +0 -54
  277. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NH4_west/c/0/0/0 +0 -0
  278. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NH4_west/zarr.json +0 -54
  279. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NO3_west/c/0/0/0 +0 -0
  280. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NO3_west/zarr.json +0 -54
  281. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/O2_west/c/0/0/0 +0 -0
  282. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/O2_west/zarr.json +0 -54
  283. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/PO4_west/c/0/0/0 +0 -0
  284. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/PO4_west/zarr.json +0 -54
  285. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/SiO3_west/c/0/0/0 +0 -0
  286. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/SiO3_west/zarr.json +0 -54
  287. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatC_west/c/0/0/0 +0 -0
  288. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatC_west/zarr.json +0 -54
  289. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatChl_west/c/0/0/0 +0 -0
  290. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatChl_west/zarr.json +0 -54
  291. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatFe_west/c/0/0/0 +0 -0
  292. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatFe_west/zarr.json +0 -54
  293. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatP_west/c/0/0/0 +0 -0
  294. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatP_west/zarr.json +0 -54
  295. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatSi_west/c/0/0/0 +0 -0
  296. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatSi_west/zarr.json +0 -54
  297. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazC_west/c/0/0/0 +0 -0
  298. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazC_west/zarr.json +0 -54
  299. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazChl_west/c/0/0/0 +0 -0
  300. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazChl_west/zarr.json +0 -54
  301. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazFe_west/c/0/0/0 +0 -0
  302. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazFe_west/zarr.json +0 -54
  303. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazP_west/c/0/0/0 +0 -0
  304. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazP_west/zarr.json +0 -54
  305. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spC_west/c/0/0/0 +0 -0
  306. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spC_west/zarr.json +0 -54
  307. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spCaCO3_west/c/0/0/0 +0 -0
  308. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spCaCO3_west/zarr.json +0 -54
  309. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spChl_west/c/0/0/0 +0 -0
  310. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spChl_west/zarr.json +0 -54
  311. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spFe_west/c/0/0/0 +0 -0
  312. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spFe_west/zarr.json +0 -54
  313. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spP_west/c/0/0/0 +0 -0
  314. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spP_west/zarr.json +0 -54
  315. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/zooC_west/c/0/0/0 +0 -0
  316. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/zooC_west/zarr.json +0 -54
  317. {roms_tools-3.2.0.dist-info → roms_tools-3.4.0.dist-info}/licenses/LICENSE +0 -0
  318. {roms_tools-3.2.0.dist-info → roms_tools-3.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,767 @@
1
+ import logging
2
+ import re
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime, timedelta
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+ import xarray as xr
10
+
11
+ from roms_tools import Grid
12
+ from roms_tools.datasets.utils import (
13
+ check_dataset,
14
+ convert_to_float64,
15
+ extrapolate_deepest_to_bottom,
16
+ select_relevant_fields,
17
+ select_relevant_times,
18
+ validate_start_end_time,
19
+ )
20
+ from roms_tools.fill import LateralFill
21
+ from roms_tools.utils import load_data, wrap_longitudes
22
+ from roms_tools.vertical_coordinate import (
23
+ compute_depth_coordinates,
24
+ )
25
+
26
+ DEFAULT_NR_BUFFER_POINTS = (
27
+ 20 # Default number of buffer points for subdomain selection.
28
+ )
29
+ # Balances performance and accuracy:
30
+ # - Too many points → more expensive computations
31
+ # - Too few points → potential boundary artifacts when lateral refill is performed
32
+ # See discussion: https://github.com/CWorthy-ocean/roms-tools/issues/153
33
+ # This default will be applied consistently across all datasets requiring lateral fill.
34
+
35
+
36
+ @dataclass(kw_only=True)
37
+ class ROMSDataset:
38
+ """Represents ROMS model output.
39
+
40
+ Parameters
41
+ ----------
42
+ path: str | Path | list[str | Path]
43
+ Filename, or list of filenames with model output.
44
+ grid : Grid
45
+ Object representing the grid information.
46
+ start_time : Optional[datetime], optional
47
+ Start time for selecting relevant data. If not provided, no time-based filtering is applied.
48
+ end_time : Optional[datetime], optional
49
+ End time for selecting relevant data. If not provided, the dataset selects the time entry
50
+ closest to `start_time` within the range `[start_time, start_time + 24 hours)`.
51
+ If `start_time` is also not provided, no time-based filtering is applied.
52
+ allow_flex_time: bool, optional
53
+ Controls how strictly the dataset selects a time entry when `end_time` is not provided (relevant for initial conditions):
54
+
55
+ - If False (default): requires an exact match to `start_time`. Raises a ValueError if no match exists.
56
+ - If True: allows a +24h search window after `start_time` and selects the closest available
57
+ time entry within that window. Raises a ValueError if none are found.
58
+
59
+ Only used when `end_time` is None. Has no effect otherwise.
60
+ dim_names: dict[str, str], optional
61
+ Dictionary specifying the names of dimensions in the dataset.
62
+ var_names: dict[str, str], optional
63
+ Dictionary of variable names that are required in the dataset.
64
+ opt_var_names: dict[str, str], optional
65
+ Dictionary of variable names that are optional in the dataset.
66
+ Defaults to an empty dictionary.
67
+ model_reference_date : datetime, optional
68
+ Reference date of ROMS simulation.
69
+ If not specified, this is inferred from metadata of the model output
70
+ If specified and does not coincide with metadata, a warning is raised.
71
+ adjust_depth_for_sea_surface_height : bool, optional
72
+ Whether to account for sea surface height variations when computing depth coordinates.
73
+ Defaults to `False`.
74
+ use_dask: bool, optional
75
+ Indicates whether to use dask for processing. If True, data is processed with dask; if False, data is processed eagerly. Defaults to False.
76
+ """
77
+
78
+ path: str | Path | list[str | Path]
79
+ """Filename, or list of filenames with model output."""
80
+ grid: Grid
81
+ """Object representing the grid information."""
82
+ start_time: datetime | None = None
83
+ """Start time for selecting relevant data."""
84
+ end_time: datetime | None = None
85
+ """End time for selecting relevant data."""
86
+ allow_flex_time: bool = False
87
+ """Controls how strictly the dataset selects a time entry when `end_time` is not provided."""
88
+ dim_names: dict[str, str] = field(
89
+ default_factory=lambda: {
90
+ "eta_rho": "eta_rho",
91
+ "xi_rho": "xi_rho",
92
+ "time": "time",
93
+ }
94
+ )
95
+ """Dictionary specifying the names of dimensions in the dataset."""
96
+ var_names: dict[str, str] | None = None
97
+ """Dictionary of variable names that are required in the dataset."""
98
+ opt_var_names: dict[str, str] = field(default_factory=dict)
99
+ """Dictionary of variable names that are optional in the dataset."""
100
+ use_dask: bool = False
101
+ """Whether to use dask for processing."""
102
+ model_reference_date: datetime | None = None
103
+ """Reference date of ROMS simulation."""
104
+ adjust_depth_for_sea_surface_height: bool | None = False
105
+ """Whether to account for sea surface height variations when computing depth
106
+ coordinates."""
107
+
108
+ ds: xr.Dataset = field(init=False, repr=False)
109
+ """An xarray Dataset containing the ROMS output."""
110
+
111
+ def __post_init__(self):
112
+ validate_start_end_time(self.start_time, self.end_time)
113
+ ds = self.load_data()
114
+ self._check_consistency_data_grid(ds)
115
+
116
+ self._set_default_var_names(ds)
117
+
118
+ check_dataset(ds, self.dim_names, self.var_names)
119
+ self._check_vertical_coordinate(ds)
120
+ self._infer_model_reference_date_from_metadata(ds)
121
+ ds = self._add_absolute_time(ds)
122
+
123
+ ds = self.select_relevant_fields(ds)
124
+ if self.start_time is not None:
125
+ ds = self.select_relevant_times(ds)
126
+
127
+ ds = self._add_lat_lon_coords_and_masks(ds)
128
+ self.ds = ds
129
+
130
+ # Dataset for depth coordinates
131
+ self.ds_depth_coords = xr.Dataset()
132
+
133
+ def _check_consistency_data_grid(self, ds: xr.Dataset) -> None:
134
+ """
135
+ Ensure that the input dataset `ds` is consistent with the grid dataset.
136
+
137
+ Specifically, checks that the dimensions of the dataset match the grid's
138
+ `eta_rho` and `xi_rho` dimensions.
139
+
140
+ Parameters
141
+ ----------
142
+ ds : xr.Dataset
143
+ The dataset to check against the grid.
144
+
145
+ Raises
146
+ ------
147
+ ValueError
148
+ If the `eta_rho` or `xi_rho` dimensions of `ds` do not match those of `self.grid.ds`.
149
+ """
150
+ eta = self.dim_names["eta_rho"]
151
+ xi = self.dim_names["xi_rho"]
152
+ grid_eta = self.grid.ds.sizes.get(eta)
153
+ grid_xi = self.grid.ds.sizes.get(xi)
154
+ ds_eta = ds.sizes.get(eta)
155
+ ds_xi = ds.sizes.get(xi)
156
+
157
+ if grid_eta != ds_eta or grid_xi != ds_xi:
158
+ raise ValueError(
159
+ f"Inconsistent dataset dimensions: "
160
+ f"grid ({eta}={grid_eta}, {xi}={grid_xi}), "
161
+ f"dataset ({eta}={ds_eta}, {xi}={ds_xi})."
162
+ )
163
+
164
+ def _set_default_var_names(self, ds: xr.Dataset) -> None:
165
+ """
166
+ Ensure ``self.var_names`` is a valid mapping.
167
+
168
+ If ``self.var_names`` is ``None``, it is initialized as an identity
169
+ mapping for all variables in ``ds``.
170
+
171
+ Parameters
172
+ ----------
173
+ ds : xr.Dataset
174
+ Dataset whose variable names are used when creating defaults.
175
+ """
176
+ if self.var_names is None:
177
+ self.var_names = {name: name for name in ds.data_vars}
178
+
179
+ def _get_depth_coordinates(self, depth_type="layer", locations=["rho"]):
180
+ """Ensure depth coordinates are stored for a given location and depth type.
181
+
182
+ Calculates vertical depth coordinates (layer or interface) for specified locations (e.g., rho, u, v points)
183
+ and updates them in the dataset (`self.ds`).
184
+
185
+ Parameters
186
+ ----------
187
+ depth_type : str
188
+ The type of depth coordinate to compute. Valid options:
189
+ - "layer": Compute layer depth coordinates.
190
+ - "interface": Compute interface depth coordinates.
191
+ locations : list[str], optional
192
+ Locations for which to compute depth coordinates. Default is ["rho", "u", "v"].
193
+ Valid options include:
194
+ - "rho": Depth coordinates at rho points.
195
+ - "u": Depth coordinates at u points.
196
+ - "v": Depth coordinates at v points.
197
+
198
+ Updates
199
+ -------
200
+ self.ds_depth_coords : xarray.Dataset
201
+
202
+ Raises
203
+ ------
204
+ ValueError
205
+ If `adjust_depth_for_sea_surface_height` is enabled but `zeta` is missing from `self.ds`.
206
+
207
+ Notes
208
+ -----
209
+ - This method relies on the `compute_depth_coordinates` function to perform calculations.
210
+ - If `adjust_depth_for_sea_surface_height` is `True`, the method accounts for variations
211
+ in sea surface height (`zeta`).
212
+ """
213
+ if self.adjust_depth_for_sea_surface_height:
214
+ if "zeta" not in self.ds:
215
+ raise ValueError(
216
+ "`zeta` is required in provided ROMS output when `adjust_depth_for_sea_surface_height` is enabled."
217
+ )
218
+ zeta = self.ds.zeta
219
+ else:
220
+ zeta = 0
221
+
222
+ for location in locations:
223
+ var_name = f"{depth_type}_depth_{location}"
224
+ if var_name not in self.ds_depth_coords:
225
+ depth_da = compute_depth_coordinates(
226
+ self.grid.ds, zeta, depth_type, location
227
+ )
228
+ depth_da = depth_da.assign_coords(
229
+ {
230
+ f"lat_{location}": self.grid.ds[f"lat_{location}"],
231
+ f"lon_{location}": self.grid.ds[f"lon_{location}"],
232
+ }
233
+ )
234
+ self.ds_depth_coords[var_name] = depth_da
235
+
236
+ def load_data(self) -> xr.Dataset:
237
+ """Load the ROMS data."""
238
+ ds = load_data(
239
+ filename=self.path,
240
+ dim_names={"time": "time"},
241
+ use_dask=self.use_dask,
242
+ decode_times=False,
243
+ decode_timedelta=False,
244
+ time_chunking=True,
245
+ force_combine_nested=True,
246
+ )
247
+
248
+ return ds
249
+
250
+ def select_relevant_fields(self, ds: xr.Dataset) -> xr.Dataset:
251
+ """
252
+ Return a subset of the dataset containing only the required and optional
253
+ variables defined for this object.
254
+
255
+ Variables retained are those listed in ``self.var_names`` and
256
+ ``self.opt_var_names``. Any other data variables are removed, except for
257
+ the special variable ``"mask"``, which is always preserved if present.
258
+
259
+ Parameters
260
+ ----------
261
+ ds : xr.Dataset
262
+ The input dataset from which relevant variables will be selected.
263
+
264
+ Returns
265
+ -------
266
+ xr.Dataset
267
+ A new dataset containing only the required variables specified in
268
+ ``self.var_names`` and the optional variables specified in
269
+ ``self.opt_var_names``, along with ``"mask"`` if present.
270
+ """
271
+ return select_relevant_fields(
272
+ ds,
273
+ [*self.var_names.values(), *self.opt_var_names.values()], # type: ignore
274
+ )
275
+
276
+ def select_relevant_times(self, ds: xr.Dataset) -> xr.Dataset:
277
+ """Select a subset of the dataset based on the specified time range.
278
+
279
+ This method filters the dataset to include all records between `start_time` and `end_time`.
280
+ Additionally, it ensures that one record at or before `start_time` and one record at or
281
+ after `end_time` are included, even if they fall outside the strict time range.
282
+
283
+ If no `end_time` is specified, the method will select the time range of
284
+ [start_time, start_time + 24 hours) and return the closest time entry to `start_time` within that range.
285
+
286
+ Parameters
287
+ ----------
288
+ ds : xr.Dataset
289
+ The input dataset to be filtered. Must contain a time dimension.
290
+
291
+ Returns
292
+ -------
293
+ xr.Dataset
294
+ A dataset filtered to the specified time range, including the closest entries
295
+ at or before `start_time` and at or after `end_time` if applicable.
296
+
297
+ Raises
298
+ ------
299
+ ValueError
300
+ If no matching times are found between `start_time` and `start_time + 24 hours`.
301
+
302
+ Warns
303
+ -----
304
+ UserWarning
305
+ If the dataset contains exactly 12 time steps but the climatology flag is not set.
306
+ This may indicate that the dataset represents climatology data.
307
+
308
+ UserWarning
309
+ If no records at or before `start_time` or no records at or after `end_time` are found.
310
+
311
+ UserWarning
312
+ If the dataset does not contain any time dimension or the time dimension is incorrectly named.
313
+
314
+ Notes
315
+ -----
316
+ - If the `climatology` flag is set and `end_time` is not provided, the method will
317
+ interpolate initial conditions from climatology data.
318
+ - If the dataset uses `cftime` datetime objects, these will be converted to standard
319
+ `np.datetime64` objects before filtering.
320
+ """
321
+ time_dim = self.dim_names["time"]
322
+
323
+ # Ensure start_time is not None for type safety
324
+ if self.start_time is None:
325
+ raise ValueError("select_relevant_times called but start_time is None.")
326
+
327
+ ds = select_relevant_times(
328
+ ds=ds,
329
+ time_dim=time_dim,
330
+ time_coord="time",
331
+ start_time=self.start_time,
332
+ end_time=self.end_time,
333
+ allow_flex_time=self.allow_flex_time,
334
+ )
335
+
336
+ return ds
337
+
338
+ def _infer_model_reference_date_from_metadata(self, ds: xr.Dataset) -> None:
339
+ """Infer and validate the model reference date from `ocean_time` metadata.
340
+
341
+ Parameters
342
+ ----------
343
+ ds : xr.Dataset
344
+ Dataset with an `ocean_time` variable and a `long_name` attribute
345
+ in the format `Time since YYYY/MM/DD`.
346
+
347
+ Raises
348
+ ------
349
+ ValueError
350
+ If `self.model_reference_date` is not set and the reference date cannot
351
+ be inferred, or if the inferred date does not match `self.model_reference_date`.
352
+
353
+ Warns
354
+ -----
355
+ UserWarning
356
+ If `self.model_reference_date` is set but the reference date cannot be inferred.
357
+ """
358
+ # Check if 'long_name' exists in the attributes of 'ocean_time'
359
+ if "long_name" in ds.ocean_time.attrs:
360
+ input_string = ds.ocean_time.attrs["long_name"]
361
+ match = re.search(r"(\d{4})/(\d{2})/(\d{2})", input_string)
362
+
363
+ if match:
364
+ # If a match is found, extract year, month, day and create the inferred date
365
+ year, month, day = map(int, match.groups())
366
+ inferred_date = datetime(year, month, day)
367
+
368
+ if hasattr(self, "model_reference_date") and self.model_reference_date:
369
+ # Check if the inferred date matches the provided model reference date
370
+ if self.model_reference_date != inferred_date:
371
+ raise ValueError(
372
+ f"Mismatch between `self.model_reference_date` ({self.model_reference_date}) "
373
+ f"and inferred reference date ({inferred_date})."
374
+ )
375
+ else:
376
+ # Set the model reference date if not already set
377
+ self.model_reference_date = inferred_date
378
+ else:
379
+ # Handle case where no match is found
380
+ if hasattr(self, "model_reference_date") and self.model_reference_date:
381
+ logging.warning(
382
+ "Could not infer the model reference date from the metadata. "
383
+ "`self.model_reference_date` will be used.",
384
+ )
385
+ else:
386
+ raise ValueError(
387
+ "Model reference date could not be inferred from the metadata, "
388
+ "and `self.model_reference_date` is not set."
389
+ )
390
+ else:
391
+ # Handle case where 'long_name' attribute doesn't exist
392
+ if hasattr(self, "model_reference_date") and self.model_reference_date:
393
+ logging.warning(
394
+ "`long_name` attribute not found in ocean_time. "
395
+ "`self.model_reference_date` will be used instead.",
396
+ )
397
+ else:
398
+ raise ValueError(
399
+ "Model reference date could not be inferred from the metadata, "
400
+ "and `self.model_reference_date` is not set."
401
+ )
402
+
403
+ def _check_vertical_coordinate(self, ds: xr.Dataset) -> None:
404
+ """Check that the vertical coordinate parameters in the dataset are consistent
405
+ with the model grid.
406
+
407
+ This method compares the vertical coordinate parameters (`theta_s`, `theta_b`, `hc`, `Cs_r`, `Cs_w`) in
408
+ the provided dataset (`ds`) with those in the model grid (`self.grid`). The first three parameters are
409
+ checked for exact equality, while the last two are checked for numerical closeness.
410
+
411
+ Parameters
412
+ ----------
413
+ ds : xarray.Dataset
414
+ The dataset containing vertical coordinate parameters in its attributes, such as `theta_s`, `theta_b`,
415
+ `hc`, `Cs_r`, and `Cs_w`.
416
+
417
+ Raises
418
+ ------
419
+ ValueError
420
+ If the vertical coordinate parameters do not match the expected values (based on exact or approximate equality).
421
+
422
+ Notes
423
+ -----
424
+ - Missing attributes trigger a warning instead of an exception.
425
+ - `theta_s`, `theta_b`, and `hc` are checked for exact equality using `np.array_equal`.
426
+ - `Cs_r` and `Cs_w` are checked for numerical closeness using `np.allclose`.
427
+ """
428
+ required_exact = ["theta_s", "theta_b", "hc"]
429
+ required_close = ["Cs_r", "Cs_w"]
430
+
431
+ # Check exact equality
432
+ for param in required_exact:
433
+ value = ds.attrs.get(param, None)
434
+ if value is None:
435
+ logging.warning(
436
+ f"Dataset is missing attribute '{param}'. Skipping this check."
437
+ )
438
+ continue
439
+ if not np.array_equal(getattr(self.grid, param), value):
440
+ raise ValueError(
441
+ f"{param} from grid ({getattr(self.grid, param)}) does not match dataset ({value})."
442
+ )
443
+
444
+ # Check numerical closeness
445
+ for param in required_close:
446
+ value = ds.attrs.get(param, None)
447
+ if value is None:
448
+ logging.warning(
449
+ f"Dataset is missing attribute '{param}'. Skipping this check."
450
+ )
451
+ continue
452
+ grid_value = getattr(self.grid.ds, param)
453
+ if not np.allclose(grid_value, value):
454
+ raise ValueError(
455
+ f"{param} from grid ({grid_value}) is not close to dataset ({value})."
456
+ )
457
+
458
+ def _add_absolute_time(self, ds: xr.Dataset) -> xr.Dataset:
459
+ """Add absolute time as a coordinate to the dataset.
460
+
461
+ Parameters
462
+ ----------
463
+ ds : xarray.Dataset
464
+ Dataset containing "ocean_time" in seconds since the model reference date.
465
+
466
+ Returns
467
+ -------
468
+ xarray.Dataset
469
+ Dataset with absolute time added.
470
+ """
471
+ if self.model_reference_date is None:
472
+ raise ValueError(
473
+ "`model_reference_date` must be set before computing absolute time."
474
+ )
475
+
476
+ ocean_time_seconds = ds["ocean_time"].values
477
+
478
+ abs_time = np.array(
479
+ [
480
+ self.model_reference_date + timedelta(seconds=seconds)
481
+ for seconds in ocean_time_seconds
482
+ ]
483
+ )
484
+
485
+ abs_time = xr.DataArray(
486
+ abs_time, dims=["time"], coords={"time": ds["ocean_time"]}
487
+ )
488
+ abs_time.attrs["long_name"] = "absolute time"
489
+ ds = ds.assign_coords({"abs_time": abs_time})
490
+ ds = ds.drop_vars("time")
491
+ ds = ds.set_index(time="abs_time")
492
+
493
+ return ds
494
+
495
+ def _add_lat_lon_coords_and_masks(self, ds: xr.Dataset) -> xr.Dataset:
496
+ """
497
+ Attach horizontal coordinate fields (lat/lon) and grid masks to a dataset.
498
+
499
+ This method augments the input dataset with the appropriate geographic
500
+ coordinates taken from the grid object. It *always* adds `lat_rho` and
501
+ `lon_rho`. If the dataset contains staggered horizontal dimensions
502
+ (`xi_u` or `eta_v`), the corresponding `u`- or `v`-point coordinates are
503
+ added as well (`lat_u`, `lon_u`, `lat_v`, `lon_v`).
504
+
505
+ In addition, the grid masks (`mask_rho`, `mask_u`, `mask_v`) are copied
506
+ into the dataset for later use in operations such as lateral filling.
507
+
508
+ Parameters
509
+ ----------
510
+ ds : xarray.Dataset
511
+ Dataset to be augmented with horizontal coordinates and masks.
512
+
513
+ Returns
514
+ -------
515
+ xarray.Dataset
516
+ A new dataset with the appropriate latitude/longitude coordinates
517
+ and grid masks assigned based on the dataset's horizontal staggering.
518
+
519
+ Notes
520
+ -----
521
+ This routine does not modify the input dataset in place; a new dataset
522
+ with added coordinates and mask variables is returned.
523
+ """
524
+ coords_to_add = {
525
+ "lat_rho": self.grid.ds["lat_rho"],
526
+ "lon_rho": self.grid.ds["lon_rho"],
527
+ }
528
+ vars_to_add = {"mask_rho": self.grid.ds["mask_rho"]}
529
+
530
+ if "xi_u" in ds.dims:
531
+ coords_to_add.update(
532
+ {"lat_u": self.grid.ds["lat_u"], "lon_u": self.grid.ds["lon_u"]}
533
+ )
534
+ vars_to_add.update({"mask_u": self.grid.ds["mask_u"]})
535
+ if "eta_v" in ds.dims:
536
+ coords_to_add.update(
537
+ {"lat_v": self.grid.ds["lat_v"], "lon_v": self.grid.ds["lon_v"]}
538
+ )
539
+ vars_to_add.update({"mask_v": self.grid.ds["mask_v"]})
540
+
541
+ ds = ds.assign_coords(coords_to_add)
542
+ for mask_name, mask_data in vars_to_add.items():
543
+ ds[mask_name] = mask_data
544
+
545
+ return ds
546
+
547
+ def choose_subdomain(
548
+ self,
549
+ target_coords: dict[str, Any],
550
+ buffer_points: int = DEFAULT_NR_BUFFER_POINTS,
551
+ ) -> None:
552
+ """Selects a subdomain from the xarray Dataset based on specified target
553
+ coordinates, extending the selection by a defined buffer. Adjusts longitude
554
+ ranges as necessary to accommodate the dataset's expected range and handles
555
+ potential discontinuities.
556
+
557
+ Parameters
558
+ ----------
559
+ target_coords : dict
560
+ A dictionary containing the target latitude and longitude coordinates, typically
561
+ with keys "lat", "lon", and "straddle".
562
+ buffer_points : int
563
+ The number of grid points to extend beyond the specified latitude and longitude
564
+ ranges when selecting the subdomain. Defaults to 20.
565
+
566
+ Returns
567
+ -------
568
+ None
569
+ The subdomain of the xarray Dataset is assigned to `self.ds`.
570
+
571
+ Raises
572
+ ------
573
+ ValueError
574
+ If the selected latitude or longitude range does not intersect with the dataset.
575
+ """
576
+ subdomain = choose_subdomain(
577
+ self.ds, self.grid.ds, target_coords, buffer_points
578
+ )
579
+ self.ds = subdomain
580
+
581
+ def convert_to_float64(self) -> None:
582
+ """Convert all data variables in the dataset to float64.
583
+
584
+ This method updates the dataset by converting all of its data variables to the
585
+ `float64` data type, ensuring consistency for numerical operations that require
586
+ high precision. The dataset is modified in place.
587
+
588
+ Parameters
589
+ ----------
590
+ None
591
+
592
+ Returns
593
+ -------
594
+ None
595
+ This method modifies the dataset in place and does not return anything.
596
+ """
597
+ ds = convert_to_float64(self.ds)
598
+ self.ds = ds
599
+
600
+ return None
601
+
602
+ def extrapolate_deepest_to_bottom(self):
603
+ """Extrapolate deepest non-NaN values to fill bottom NaNs along the s (depth)
604
+ dimension.
605
+
606
+ For each variable with a depth dimension, fills missing values at the bottom by
607
+ propagating the deepest available data downward.
608
+ """
609
+ self.ds = extrapolate_deepest_to_bottom(self.ds, "s_rho")
610
+
611
+ def apply_lateral_fill(self) -> None:
612
+ """Apply lateral fill to variables using available masks and grid dimensions.
613
+
614
+ Lateral fill is applied only when:
615
+ - A corresponding mask exists in the dataset, and
616
+ - At least one variable is defined on the associated horizontal grid.
617
+
618
+ Raises
619
+ ------
620
+ ValueError
621
+ If variables exist on a horizontal grid (rho, u, or v) but the
622
+ corresponding mask is missing.
623
+ """
624
+ # Mapping of horizontal dims to required mask name
625
+ dim_to_mask: dict[tuple[str, str], str] = {
626
+ ("eta_rho", "xi_rho"): "mask_rho",
627
+ ("eta_rho", "xi_u"): "mask_u",
628
+ ("eta_v", "xi_rho"): "mask_v",
629
+ }
630
+ horiz_dim_order = ("eta_rho", "eta_v", "xi_rho", "xi_u")
631
+
632
+ # Identify which horizontal dim sets are actually used
633
+ used_dim_sets: set[tuple[str, str]] = set()
634
+
635
+ for var in self.ds.data_vars.values():
636
+ horiz_dims = tuple(d for d in horiz_dim_order if d in var.dims)
637
+ if len(horiz_dims) == 2:
638
+ used_dim_sets.add(horiz_dims)
639
+
640
+ # Enforce required masks for all grids (rho, u, v)
641
+ for dims, mask_name in dim_to_mask.items():
642
+ if dims in used_dim_sets and mask_name not in self.ds:
643
+ raise ValueError(
644
+ f"Variable(s) found on grid {tuple(dims)}, but required mask "
645
+ f"'{mask_name}' is missing from the dataset."
646
+ )
647
+
648
+ # Build lateral fillers
649
+ lateral_fillers: dict[tuple[str, str], LateralFill] = {
650
+ dims: LateralFill(xr.where(self.ds[mask_name] == 1, True, False), dims)
651
+ for dims, mask_name in dim_to_mask.items()
652
+ if dims in used_dim_sets
653
+ }
654
+
655
+ # Apply lateral fill
656
+ for var_name, var in self.ds.data_vars.items():
657
+ if var_name.startswith("mask"):
658
+ continue
659
+
660
+ # Keep dims in canonical order
661
+ var_horiz_dims = tuple(d for d in horiz_dim_order if d in var.dims)
662
+ if len(var_horiz_dims) == 2:
663
+ filler = lateral_fillers.get(var_horiz_dims)
664
+ if filler is not None:
665
+ self.ds[var_name] = filler.apply(var)
666
+
667
+
668
+ def choose_subdomain(
669
+ ds: xr.Dataset,
670
+ ds_grid: xr.Dataset,
671
+ target_coords: dict[str, Any],
672
+ buffer_points: int = DEFAULT_NR_BUFFER_POINTS,
673
+ ):
674
+ """Selects a subdomain from the xarray Dataset based on specified target
675
+ coordinates, extending the selection by a defined buffer. Adjusts longitude
676
+ ranges as necessary to accommodate the dataset's expected range and handles
677
+ potential discontinuities.
678
+
679
+ Parameters
680
+ ----------
681
+ ds : xr.Dataset
682
+ The full ROMS xarray Dataset to subset.
683
+ ds_grid: xr.Dataset
684
+ Dataset containing the grid coordinates, in particular `pm` and `pn`.
685
+ target_coords : dict
686
+ A dictionary containing the target latitude and longitude coordinates, typically
687
+ with keys "lat", "lon", and "straddle".
688
+ buffer_points : int
689
+ The number of grid points to extend beyond the specified latitude and longitude
690
+ ranges when selecting the subdomain. Defaults to 20.
691
+
692
+ Returns
693
+ -------
694
+ xr.Dataset
695
+ Returns the subset of the original dataset.
696
+
697
+ Raises
698
+ ------
699
+ ValueError
700
+ If the selected latitude or longitude range does not intersect with the dataset.
701
+ """
702
+ # Adjust longitude range if needed to match the expected range
703
+ ds = wrap_longitudes(ds, target_coords["straddle"])
704
+
705
+ lat_min = target_coords["lat"].min().values
706
+ lat_max = target_coords["lat"].max().values
707
+ lon_min = target_coords["lon"].min().values
708
+ lon_max = target_coords["lon"].max().values
709
+
710
+ # Extract grid spacing (in meters)
711
+ dx = 0.5 * ((1 / ds_grid.pm).mean() + (1 / ds_grid.pn).mean()) # meters
712
+ buffer = dx * buffer_points # buffer distance in meters
713
+
714
+ lat = np.deg2rad(0.5 * (lat_min + lat_max))
715
+
716
+ deg_per_meter_lat = 1 / 111_320.0
717
+ margin_lat = buffer * deg_per_meter_lat
718
+
719
+ deg_per_meter_lon = 1 / (111_320.0 * np.cos(lat))
720
+ margin_lon = buffer * deg_per_meter_lon
721
+
722
+ mapping = {
723
+ "rho": ("eta_rho", "xi_rho"),
724
+ "u": ("eta_rho", "xi_u"),
725
+ "v": ("eta_v", "xi_rho"),
726
+ }
727
+
728
+ for location in ["rho", "u", "v"]:
729
+ eta_dim, xi_dim = mapping[location]
730
+
731
+ if eta_dim in ds.dims and xi_dim in ds.dims:
732
+ # Check that lat/lon coordinates exist
733
+ lat_coord = f"lat_{location}"
734
+ lon_coord = f"lon_{location}"
735
+ if lat_coord not in ds.coords or lon_coord not in ds.coords:
736
+ raise ValueError(
737
+ f"Dataset is missing expected coordinates for location '{location}': "
738
+ f"expected '{lat_coord}' and '{lon_coord}'"
739
+ )
740
+
741
+ # Build subset mask
742
+ subset_mask = (
743
+ (ds[lat_coord] > lat_min - margin_lat)
744
+ & (ds[lat_coord] < lat_max + margin_lat)
745
+ & (ds[lon_coord] > lon_min - margin_lon)
746
+ & (ds[lon_coord] < lon_max + margin_lon)
747
+ )
748
+
749
+ # Reduce along xi_dim
750
+ eta_mask = subset_mask.any(dim=xi_dim)
751
+ eta_indices = np.where(eta_mask)[0]
752
+ first_eta, last_eta = eta_indices[0], eta_indices[-1]
753
+
754
+ # Reduce along eta_dim
755
+ xi_mask = subset_mask.any(dim=eta_dim)
756
+ xi_indices = np.where(xi_mask)[0]
757
+ first_xi, last_xi = xi_indices[0], xi_indices[-1]
758
+
759
+ # Subset the dataset
760
+ ds = ds.isel(
761
+ **{
762
+ eta_dim: slice(first_eta, last_eta + 1),
763
+ xi_dim: slice(first_xi, last_xi + 1),
764
+ }
765
+ )
766
+
767
+ return ds