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
@@ -0,0 +1,241 @@
1
+ from collections.abc import Callable
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+ import xarray as xr
6
+
7
+ from roms_tools import Grid
8
+ from roms_tools.tiling.join import (
9
+ _find_common_dims,
10
+ _find_transitions,
11
+ _infer_partition_layout_from_datasets,
12
+ join_netcdf,
13
+ open_partitions,
14
+ )
15
+ from roms_tools.tiling.partition import partition_netcdf
16
+
17
+
18
+ @pytest.fixture
19
+ def partitioned_grid_factory(
20
+ tmp_path, large_grid
21
+ ) -> Callable[[int, int], tuple[Grid, list[Path]]]:
22
+ """
23
+ A fixture factory that returns a function to generate partitioned files
24
+ with a configurable layout.
25
+ """
26
+
27
+ def _partitioned_files(np_xi: int, np_eta: int) -> tuple[Grid, list[Path]]:
28
+ partable_grid = large_grid
29
+ partable_grid.save(tmp_path / "test_grid.nc")
30
+ parted_files = partition_netcdf(
31
+ tmp_path / "test_grid.nc", np_xi=np_xi, np_eta=np_eta
32
+ )
33
+ return partable_grid, parted_files
34
+
35
+ return _partitioned_files
36
+
37
+
38
+ @pytest.fixture
39
+ def partitioned_ic_factory(
40
+ tmp_path, initial_conditions_on_large_grid
41
+ ) -> Callable[[int, int], tuple[Path, list[Path]]]:
42
+ def _partitioned_files(np_xi: int, np_eta: int) -> tuple[Path, list[Path]]:
43
+ whole_ics = initial_conditions_on_large_grid
44
+ whole_path = whole_ics.save(tmp_path / "test_ic.nc")[0]
45
+ parted_paths = partition_netcdf(whole_path, np_xi=np_xi, np_eta=np_eta)
46
+ return whole_path, parted_paths
47
+
48
+ return _partitioned_files
49
+
50
+
51
+ class TestHelperFunctions:
52
+ def test_find_common_dims(self):
53
+ """Test _find_common_dims with different datasets and directions.
54
+
55
+ Tests for a valid common dimension, a valid dimension that is not common,
56
+ an invalid direction, and a ValueError when no common dimension is found.
57
+ """
58
+ # Create mock xarray.Dataset objects for testing
59
+ ds1 = xr.Dataset(coords={"xi_rho": [0], "xi_u": [0]})
60
+ ds2 = xr.Dataset(coords={"xi_rho": [0], "xi_u": [0]})
61
+ ds3 = xr.Dataset(coords={"xi_rho": [0], "xi_v": [0]})
62
+ datasets_common = [ds1, ds2]
63
+ datasets_not_common = [ds1, ds3]
64
+
65
+ # Test case with a common dimension ("xi_rho" and "xi_u")
66
+ # The function should find 'xi_rho' and 'xi_u' and return them in a list.
67
+ assert _find_common_dims("xi", datasets_common) == ["xi_rho", "xi_u"]
68
+
69
+ # Test case where a dimension is not common to all datasets
70
+ # The function should find only "xi_rho" as "xi_u" is not in ds3.
71
+ assert _find_common_dims("xi", datasets_not_common) == ["xi_rho"]
72
+
73
+ # Test case for an invalid direction, should raise a ValueError
74
+ with pytest.raises(ValueError, match="'direction' must be 'xi' or 'eta'"):
75
+ _find_common_dims("zeta", datasets_common)
76
+
77
+ # Test case where no common dimensions exist
78
+ ds_no_common1 = xr.Dataset(coords={"xi_rho": [0]})
79
+ ds_no_common2 = xr.Dataset(coords={"xi_v": [0]})
80
+ with pytest.raises(
81
+ ValueError, match="No common point found along direction xi"
82
+ ):
83
+ _find_common_dims("xi", [ds_no_common1, ds_no_common2])
84
+
85
+ def test_find_transitions(self):
86
+ """Test _find_transitions with various input lists.
87
+
88
+ Test cases include lists with no transitions, a single transition,
89
+ multiple transitions, and edge cases like empty and single-element lists.
90
+ """
91
+ # Test case with no transitions
92
+ assert _find_transitions([10, 10, 10, 10]) == []
93
+
94
+ # Test case with a single transition
95
+ assert _find_transitions([10, 10, 12, 12]) == [2]
96
+
97
+ # Test case with multiple transitions
98
+ assert _find_transitions([10, 12, 12, 14, 14, 14]) == [1, 3]
99
+
100
+ # Test case with transitions on every element
101
+ assert _find_transitions([10, 12, 14, 16]) == [1, 2, 3]
102
+
103
+ # Edge case: empty list
104
+ assert _find_transitions([]) == []
105
+
106
+ # Edge case: single-element list
107
+ assert _find_transitions([10]) == []
108
+
109
+ def test_infer_partition_layout_from_datasets(self):
110
+ """Test _infer_partition_layout_from_datasets with various layouts.
111
+
112
+ Tests include a single dataset, a 2x2 grid, a 4x1 grid (single row),
113
+ and a 1x4 grid (single column).
114
+ """
115
+ # Test case 1: Single dataset (1x1 partition)
116
+ ds1 = xr.Dataset(coords={"eta_rho": [0], "xi_rho": [0]})
117
+ assert _infer_partition_layout_from_datasets([ds1]) == (1, 1)
118
+
119
+ # Test case 2: 2x2 grid partition.
120
+ # The eta dimension will transition after the second dataset (np_xi=2).
121
+ ds_2x2_1 = xr.Dataset(coords={"eta_rho": [0] * 20, "xi_rho": [0] * 10})
122
+ ds_2x2_2 = xr.Dataset(coords={"eta_rho": [0] * 20, "xi_rho": [0] * 10})
123
+ ds_2x2_3 = xr.Dataset(coords={"eta_rho": [0] * 10, "xi_rho": [0] * 10})
124
+ ds_2x2_4 = xr.Dataset(coords={"eta_rho": [0] * 10, "xi_rho": [0] * 10})
125
+ datasets_2x2 = [ds_2x2_1, ds_2x2_2, ds_2x2_3, ds_2x2_4]
126
+ assert _infer_partition_layout_from_datasets(datasets_2x2) == (2, 2)
127
+
128
+ # Test case 3: 4x1 grid partition (single row).
129
+ # The eta dimension sizes are all the same, so no transition is detected.
130
+ # The function falls back to returning nd, 1.
131
+ ds_4x1_1 = xr.Dataset(coords={"eta_rho": [0] * 10, "xi_rho": [0] * 5})
132
+ ds_4x1_2 = xr.Dataset(coords={"eta_rho": [0] * 10, "xi_rho": [0] * 5})
133
+ ds_4x1_3 = xr.Dataset(coords={"eta_rho": [0] * 10, "xi_rho": [0] * 5})
134
+ ds_4x1_4 = xr.Dataset(coords={"eta_rho": [0] * 10, "xi_rho": [0] * 5})
135
+ datasets_4x1 = [ds_4x1_1, ds_4x1_2, ds_4x1_3, ds_4x1_4]
136
+ assert _infer_partition_layout_from_datasets(datasets_4x1) == (4, 1)
137
+
138
+ # Test case 4: 1x4 grid partition (single column).
139
+ # The xi dimension is partitioned, so the eta dimensions must change at every step.
140
+ ds_1x4_1 = xr.Dataset(coords={"eta_rho": [0] * 10, "xi_rho": [0] * 20})
141
+ ds_1x4_2 = xr.Dataset(coords={"eta_rho": [0] * 12, "xi_rho": [0] * 20})
142
+ ds_1x4_3 = xr.Dataset(coords={"eta_rho": [0] * 14, "xi_rho": [0] * 20})
143
+ ds_1x4_4 = xr.Dataset(coords={"eta_rho": [0] * 16, "xi_rho": [0] * 20})
144
+ datasets_1x4 = [ds_1x4_1, ds_1x4_2, ds_1x4_3, ds_1x4_4]
145
+ # In this case, `_find_transitions` for eta will find a transition at index 1, so np_xi=1.
146
+ # This will correctly return (1, 4).
147
+ assert _infer_partition_layout_from_datasets(datasets_1x4) == (1, 4)
148
+
149
+
150
+ class TestJoinROMSData:
151
+ @pytest.mark.parametrize(
152
+ "np_xi, np_eta",
153
+ [
154
+ (1, 1),
155
+ (1, 6),
156
+ (6, 1),
157
+ (2, 2),
158
+ (3, 3),
159
+ (3, 4),
160
+ (4, 3), # (12,24)
161
+ # # All possible:
162
+ # # Single-partition grid
163
+ # (1,1)
164
+ # # Single-row grids
165
+ # (2, 1), (3, 1), (4, 1), (6, 1), (12, 1),
166
+ # # Single-column grids
167
+ # (1, 2), (1, 3), (1, 4), (1, 6), (1, 8), (1, 12), (1, 24),
168
+ # # Multi-row, multi-column grids
169
+ # (2, 2), (2, 3), (2, 4), (2, 6), (2, 8), (2, 12), (2, 24),
170
+ # (3, 2), (3, 3), (3, 4), (3, 6), (3, 8), (3, 12), (3, 24),
171
+ # (4, 2), (4, 3), (4, 4), (4, 6), (4, 8), (4, 12), (4, 24),
172
+ # (6, 2), (6, 3), (6, 4), (6, 6), (6, 8), (6, 12), (6, 24),
173
+ # (12, 2), (12, 3), (12, 4), (12, 6), (12, 8), (12, 12), (12, 24)
174
+ ],
175
+ )
176
+ def test_open_grid_partitions(self, partitioned_grid_factory, np_xi, np_eta):
177
+ grid, partitions = partitioned_grid_factory(np_xi=np_xi, np_eta=np_eta)
178
+ joined_grid = open_partitions(partitions)
179
+
180
+ for v in grid.ds.variables:
181
+ assert (grid.ds[v].values == joined_grid[v].values).all(), (
182
+ f"{v} does not match in joined dataset"
183
+ )
184
+ assert grid.ds.attrs == joined_grid.attrs
185
+
186
+ def test_join_grid_netcdf(self, partitioned_grid_factory):
187
+ grid, partitions = partitioned_grid_factory(np_xi=3, np_eta=4)
188
+ joined_netcdf = join_netcdf(
189
+ partitions, output_path=partitions[0].parent / "joined_grid.nc"
190
+ )
191
+ assert joined_netcdf.exists()
192
+ joined_grid = xr.open_dataset(joined_netcdf)
193
+
194
+ for v in grid.ds.variables:
195
+ assert (grid.ds[v].values == joined_grid[v].values).all(), (
196
+ f"{v} does not match in joined dataset"
197
+ )
198
+ assert grid.ds.attrs == joined_grid.attrs
199
+
200
+ @pytest.mark.parametrize(
201
+ "np_xi, np_eta",
202
+ [
203
+ (1, 1),
204
+ (1, 6),
205
+ (6, 1),
206
+ (2, 2),
207
+ (3, 3),
208
+ (3, 4),
209
+ (4, 3), # (12,24)
210
+ ],
211
+ )
212
+ def test_open_initial_condition_partitions(
213
+ self, partitioned_ic_factory, np_xi, np_eta
214
+ ):
215
+ whole_file, partitioned_files = partitioned_ic_factory(
216
+ np_xi=np_xi, np_eta=np_eta
217
+ )
218
+ joined_ics = open_partitions(partitioned_files)
219
+ whole_ics = xr.open_dataset(whole_file, decode_timedelta=True)
220
+
221
+ for v in whole_ics.variables:
222
+ assert (whole_ics[v].values == joined_ics[v].values).all(), (
223
+ f"{v} does not match in joined dataset: {joined_ics[v].values} vs {whole_ics[v].values}"
224
+ )
225
+ assert whole_ics.attrs == joined_ics.attrs
226
+
227
+ def test_join_initial_condition_netcdf(self, tmp_path, partitioned_ic_factory):
228
+ whole_file, partitioned_files = partitioned_ic_factory(np_xi=3, np_eta=4)
229
+ whole_ics = xr.open_dataset(whole_file, decode_timedelta=True)
230
+
231
+ joined_netcdf = join_netcdf(
232
+ partitioned_files, output_path=partitioned_files[0].parent / "joined_ics.nc"
233
+ )
234
+ assert joined_netcdf.exists()
235
+ joined_ics = xr.open_dataset(joined_netcdf, decode_timedelta=True)
236
+
237
+ for v in whole_ics.variables:
238
+ assert (whole_ics[v].values == joined_ics[v].values).all(), (
239
+ f"{v} does not match in joined dataset: {joined_ics[v].values} vs {whole_ics[v].values}"
240
+ )
241
+ assert whole_ics.attrs == joined_ics.attrs
@@ -7,11 +7,13 @@ import pytest
7
7
  import xarray as xr
8
8
 
9
9
  from roms_tools.utils import (
10
- _generate_focused_coordinate_range,
11
- _has_copernicus,
12
- _has_dask,
13
- _has_gcsfs,
14
- _load_data,
10
+ _path_list_from_input,
11
+ generate_focused_coordinate_range,
12
+ get_dask_chunks,
13
+ has_copernicus,
14
+ has_dask,
15
+ has_gcsfs,
16
+ load_data,
15
17
  )
16
18
 
17
19
 
@@ -25,66 +27,132 @@ from roms_tools.utils import (
25
27
  ],
26
28
  )
27
29
  def test_coordinate_range_monotonicity(min_val, max_val, center, sc, N):
28
- centers, faces = _generate_focused_coordinate_range(
30
+ centers, faces = generate_focused_coordinate_range(
29
31
  min_val=min_val, max_val=max_val, center=center, sc=sc, N=N
30
32
  )
31
33
  assert np.all(np.diff(faces) > 0), "faces is not strictly increasing"
32
34
  assert np.all(np.diff(centers) > 0), "centers is not strictly increasing"
33
35
 
34
36
 
37
+ class TestPathListFromInput:
38
+ """A collection of tests for the _path_list_from_input function."""
39
+
40
+ # Test cases that don't require I/O
41
+ def test_list_of_strings(self):
42
+ """Test with a list of file paths as strings."""
43
+ files_list = ["path/to/file1.txt", "path/to/file2.txt"]
44
+ result = _path_list_from_input(files_list)
45
+ assert len(result) == 2
46
+ assert result[0] == Path("path/to/file1.txt")
47
+ assert result[1] == Path("path/to/file2.txt")
48
+
49
+ def test_list_of_path_objects(self):
50
+ """Test with a list of pathlib.Path objects."""
51
+ files_list = [Path("file_a.txt"), Path("file_b.txt")]
52
+ result = _path_list_from_input(files_list)
53
+ assert len(result) == 2
54
+ assert result[0] == Path("file_a.txt")
55
+ assert result[1] == Path("file_b.txt")
56
+
57
+ def test_single_path_object(self):
58
+ """Test with a single pathlib.Path object."""
59
+ file_path = Path("a_single_file.csv")
60
+ result = _path_list_from_input(file_path)
61
+ assert len(result) == 1
62
+ assert result[0] == file_path
63
+
64
+ def test_invalid_input_type_raises(self):
65
+ """Test that an invalid input type raises a TypeError."""
66
+ with pytest.raises(TypeError, match="'files' should be str, Path, or List"):
67
+ _path_list_from_input(123)
68
+
69
+ # Test cases that require I/O and `tmp_path`
70
+ def test_single_file_as_str(self, tmp_path):
71
+ """Test with a single file given as a string, requiring a file to exist."""
72
+ p = tmp_path / "test_file.txt"
73
+ p.touch()
74
+ result = _path_list_from_input(str(p))
75
+ assert len(result) == 1
76
+ assert result[0] == p
77
+
78
+ def test_wildcard_pattern(self, tmp_path, monkeypatch):
79
+ """Test with a wildcard pattern, requiring files to exist, using monkeypatch."""
80
+ # Setup
81
+ d = tmp_path / "data"
82
+ d.mkdir()
83
+ (d / "file1.csv").touch()
84
+ (d / "file2.csv").touch()
85
+ (d / "other_file.txt").touch()
86
+
87
+ # Action: Temporarily change the current working directory
88
+ monkeypatch.chdir(tmp_path)
89
+
90
+ result = _path_list_from_input("data/*.csv")
91
+
92
+ # Assertion
93
+ assert len(result) == 2
94
+ assert result[0].name == "file1.csv"
95
+ assert result[1].name == "file2.csv"
96
+
97
+ def test_non_matching_pattern_raises(self, tmp_path):
98
+ """Test that a non-matching pattern raises a FileNotFoundError."""
99
+ with pytest.raises(FileNotFoundError, match="No files matched"):
100
+ _path_list_from_input(str(tmp_path / "non_existent_file_*.txt"))
101
+
102
+
35
103
  def test_has_dask() -> None:
36
104
  """Verify that dask existence is correctly reported when found."""
37
105
  with mock.patch("roms_tools.utils.find_spec", return_value=mock.MagicMock):
38
- assert _has_dask()
106
+ assert has_dask()
39
107
 
40
108
 
41
109
  def test_has_dask_error_when_missing() -> None:
42
110
  """Verify that dask existence is correctly reported when not found."""
43
111
  with mock.patch("roms_tools.utils.find_spec", return_value=None):
44
- assert not _has_dask()
112
+ assert not has_dask()
45
113
 
46
114
 
47
115
  def test_has_gcfs() -> None:
48
116
  """Verify that GCFS existence is correctly reported when found."""
49
117
  with mock.patch("roms_tools.utils.find_spec", return_value=mock.MagicMock):
50
- assert _has_gcsfs()
118
+ assert has_gcsfs()
51
119
 
52
120
 
53
121
  def test_has_gcfs_error_when_missing() -> None:
54
122
  """Verify that GCFS existence is correctly reported when not found."""
55
123
  with mock.patch("roms_tools.utils.find_spec", return_value=None):
56
- assert not _has_gcsfs()
124
+ assert not has_gcsfs()
57
125
 
58
126
 
59
127
  def test_has_copernicus() -> None:
60
128
  """Verify that copernicus existence is correctly reported when found."""
61
129
  with mock.patch("roms_tools.utils.find_spec", return_value=mock.MagicMock):
62
- assert _has_copernicus()
130
+ assert has_copernicus()
63
131
 
64
132
 
65
133
  def test_has_copernicus_error_when_missing() -> None:
66
134
  """Verify that copernicus existence is correctly reported when not found."""
67
135
  with mock.patch("roms_tools.utils.find_spec", return_value=None):
68
- assert not _has_copernicus()
136
+ assert not has_copernicus()
69
137
 
70
138
 
71
139
  def test_load_data_dask_not_found() -> None:
72
140
  """Verify that load data raises an exception when dask is requested and missing."""
73
141
  with (
74
- mock.patch("roms_tools.utils._has_dask", return_value=False),
142
+ mock.patch("roms_tools.utils.has_dask", return_value=False),
75
143
  pytest.raises(RuntimeError),
76
144
  ):
77
- _load_data("foo.zarr", {"a": "a"}, use_dask=True)
145
+ load_data("foo.zarr", {"a": "a"}, use_dask=True)
78
146
 
79
147
 
80
148
  def test_load_data_open_zarr_without_dask() -> None:
81
149
  """Verify that load data raises an exception when zarr is requested without dask."""
82
150
  with (
83
- mock.patch("roms_tools.utils._has_dask", return_value=False),
151
+ mock.patch("roms_tools.utils.has_dask", return_value=False),
84
152
  pytest.raises(ValueError),
85
153
  ):
86
154
  # read_zarr should require use_dask to be True
87
- _load_data("foo.zarr", {"a": ""}, use_dask=False, read_zarr=True)
155
+ load_data("foo.zarr", {"a": ""}, use_dask=False, read_zarr=True)
88
156
 
89
157
 
90
158
  @pytest.mark.parametrize(
@@ -111,7 +179,7 @@ def test_load_data_open_dataset(
111
179
  "roms_tools.utils.xr.open_dataset",
112
180
  wraps=xr.open_dataset,
113
181
  ) as fn_od:
114
- ds = _load_data(
182
+ ds = load_data(
115
183
  ds_path,
116
184
  {"latitude": "latitude"},
117
185
  use_dask=False,
@@ -119,3 +187,57 @@ def test_load_data_open_dataset(
119
187
  assert fn_od.called
120
188
 
121
189
  assert expected_dim in ds.dims
190
+
191
+
192
+ # test get_dask_chunks
193
+
194
+
195
+ def test_latlon_default_chunks():
196
+ dim_names = {"latitude": "lat", "longitude": "lon"}
197
+ expected = {"lat": -1, "lon": -1}
198
+ result = get_dask_chunks(dim_names)
199
+ assert result == expected
200
+
201
+
202
+ def test_latlon_with_depth_and_time():
203
+ dim_names = {"latitude": "lat", "longitude": "lon", "depth": "z", "time": "t"}
204
+ expected = {"lat": -1, "lon": -1, "z": -1, "t": 1}
205
+ result = get_dask_chunks(dim_names)
206
+ assert result == expected
207
+
208
+
209
+ def test_latlon_with_time_chunking_false():
210
+ dim_names = {"latitude": "lat", "longitude": "lon", "time": "t"}
211
+ expected = {"lat": -1, "lon": -1}
212
+ result = get_dask_chunks(dim_names, time_chunking=False)
213
+ assert result == expected
214
+
215
+
216
+ def test_roms_default_chunks():
217
+ dim_names = {}
218
+ expected_keys = {"eta_rho", "eta_v", "xi_rho", "xi_u", "s_rho"}
219
+ result = get_dask_chunks(dim_names)
220
+ assert set(result.keys()) == expected_keys
221
+ assert all(v == -1 for v in result.values())
222
+
223
+
224
+ def test_roms_with_depth_and_time():
225
+ dim_names = {"depth": "s_rho", "time": "ocean_time"}
226
+ result = get_dask_chunks(dim_names)
227
+ # ROMS default keys + depth + time
228
+ expected_keys = {"eta_rho", "eta_v", "xi_rho", "xi_u", "s_rho", "ocean_time"}
229
+ assert set(result.keys()) == expected_keys
230
+ assert result["ocean_time"] == 1
231
+ assert result["s_rho"] == -1
232
+
233
+
234
+ def test_roms_with_ntides():
235
+ dim_names = {"ntides": "nt"}
236
+ result = get_dask_chunks(dim_names)
237
+ assert result["nt"] == 1
238
+
239
+
240
+ def test_time_chunking_false_roms():
241
+ dim_names = {"time": "ocean_time"}
242
+ result = get_dask_chunks(dim_names, time_chunking=False)
243
+ assert "ocean_time" not in result
@@ -0,0 +1,189 @@
1
+ from collections.abc import Sequence
2
+ from pathlib import Path
3
+ from typing import Literal, cast
4
+
5
+ import xarray as xr
6
+
7
+ from roms_tools.utils import FilePaths, _path_list_from_input
8
+
9
+
10
+ def open_partitions(files: FilePaths) -> xr.Dataset:
11
+ """
12
+ Open partitioned ROMS netCDF files as a single dataset.
13
+
14
+ Parameters
15
+ ----------
16
+ files: str | List[str | Path]
17
+ List or wildcard pattern describing files to join,
18
+ e.g. "roms_rst.20121209133435.*.nc"
19
+
20
+ Returns
21
+ -------
22
+ xarray.Dataset
23
+ Dataset containing unified partitioned datasets
24
+ """
25
+ filepaths = _path_list_from_input(files)
26
+ datasets = [xr.open_dataset(p, decode_timedelta=True) for p in sorted(filepaths)]
27
+ joined = join_datasets(datasets)
28
+ return joined
29
+
30
+
31
+ def join_netcdf(files: FilePaths, output_path: Path | None = None) -> Path:
32
+ """
33
+ Join partitioned NetCDFs into a single dataset.
34
+
35
+ Parameters
36
+ ----------
37
+ files : str | List[str | Path]
38
+ List or wildcard pattern describing files to join,
39
+ e.g. "roms_rst.20121209133435.*.nc"
40
+
41
+ output_path : Path, optional
42
+ If provided, the joined dataset will be saved to this path.
43
+ Otherwise, the common base of pattern (e.g. roms_rst.20121209133435.nc)
44
+ will be used.
45
+
46
+ Returns
47
+ -------
48
+ Path
49
+ The path of the saved file
50
+ """
51
+ filepaths = _path_list_from_input(files)
52
+ # Determine output path if not provided
53
+ if output_path is None:
54
+ # e.g. roms_rst.20120101120000.023.nc -> roms_rst.20120101120000.nc
55
+ output_path = filepaths[0].with_suffix("").with_suffix(".nc")
56
+
57
+ joined = open_partitions(cast(FilePaths, filepaths))
58
+ joined.to_netcdf(output_path)
59
+ print(f"Saved joined dataset to: {output_path}")
60
+
61
+ return output_path
62
+
63
+
64
+ def _find_transitions(dim_sizes: list[int]) -> list[int]:
65
+ """Finds the indices of all transitions in a list of dimension sizes.
66
+
67
+ A transition is a point where the dimension size changes from the previous one.
68
+ This function is used to determine the number of partitions (e.g., np_eta or np_xi).
69
+
70
+ Parameters
71
+ ----------
72
+ dim_sizes : list[int]
73
+ A list of integer sizes for a given dimension across multiple datasets.
74
+
75
+ Returns
76
+ -------
77
+ List[int]
78
+ A list of indices where a transition was detected.
79
+ """
80
+ transitions: list[int] = []
81
+ if len(dim_sizes) < 2:
82
+ return transitions
83
+
84
+ for i in range(1, len(dim_sizes)):
85
+ if dim_sizes[i] != dim_sizes[i - 1]:
86
+ transitions.append(i)
87
+ return transitions
88
+
89
+
90
+ def _find_common_dims(
91
+ direction: Literal["xi", "eta"], datasets: Sequence[xr.Dataset]
92
+ ) -> list[str]:
93
+ """Finds all common dimensions along the xi or eta direction amongst a list of Datasets.
94
+
95
+ Parameters
96
+ ----------
97
+ direction: str ("xi" or "eta")
98
+ The direction in which to seek a common dimension
99
+ datasets: Sequence[xr.Dataset]:
100
+ The datasets in which to look
101
+
102
+ Returns
103
+ -------
104
+ common_dim: list[str]
105
+ The dimensions common to all specified datasets along 'direction'
106
+ """
107
+ if direction not in ["xi", "eta"]:
108
+ raise ValueError("'direction' must be 'xi' or 'eta'")
109
+ dims = []
110
+ for point in ["rho", "u", "v"]:
111
+ if all(f"{direction}_{point}" in d.dims for d in datasets):
112
+ dims.append(f"{direction}_{point}")
113
+ if not dims:
114
+ raise ValueError(f"No common point found along direction {direction}")
115
+ return dims
116
+
117
+
118
+ def _infer_partition_layout_from_datasets(
119
+ datasets: Sequence[xr.Dataset],
120
+ ) -> tuple[int, int]:
121
+ """Infer np_eta, np_xi from datasets."""
122
+ nd = len(datasets)
123
+ if nd == 1:
124
+ return 1, 1
125
+
126
+ eta_dims = _find_common_dims("eta", datasets)
127
+ first_eta_transition = nd
128
+
129
+ for eta_dim in eta_dims:
130
+ dim_sizes = [ds.sizes.get(eta_dim, 0) for ds in datasets]
131
+ eta_transitions = _find_transitions(dim_sizes)
132
+ if eta_transitions and (min(eta_transitions) < first_eta_transition):
133
+ first_eta_transition = min(eta_transitions)
134
+ if first_eta_transition < nd:
135
+ np_xi = first_eta_transition
136
+ np_eta = nd // np_xi
137
+ return np_xi, np_eta
138
+ # If we did not successfully find np_xi,np_eta using eta points
139
+ # then we have a single-column grid:
140
+
141
+ return nd, 1
142
+
143
+
144
+ def join_datasets(datasets: Sequence[xr.Dataset]) -> xr.Dataset:
145
+ """Take a sequence of partitioned Datasets and return a joined Dataset."""
146
+ np_xi, np_eta = _infer_partition_layout_from_datasets(datasets)
147
+
148
+ # Arrange into grid
149
+ grid = [[datasets[j + i * np_xi] for j in range(np_xi)] for i in range(np_eta)]
150
+
151
+ # Join each row (along xi_*)
152
+ rows_joined = []
153
+ for row in grid:
154
+ all_vars = set().union(*(ds.data_vars for ds in row))
155
+ row_dataset = xr.Dataset()
156
+
157
+ for varname in all_vars:
158
+ var_slices = [ds[varname] for ds in row if varname in ds]
159
+ xi_dims = [dim for dim in var_slices[0].dims if dim.startswith("xi_")]
160
+
161
+ if not xi_dims:
162
+ row_dataset[varname] = var_slices[0]
163
+ else:
164
+ xi_dim = xi_dims[0]
165
+ row_dataset[varname] = xr.concat(
166
+ var_slices, dim=xi_dim, combine_attrs="override"
167
+ )
168
+
169
+ rows_joined.append(row_dataset)
170
+
171
+ # Join all rows (along eta_*)
172
+ final_dataset = xr.Dataset()
173
+ all_vars = set().union(*(ds.data_vars for ds in rows_joined))
174
+
175
+ for varname in all_vars:
176
+ var_slices = [ds[varname] for ds in rows_joined if varname in ds]
177
+ eta_dims = [dim for dim in var_slices[0].dims if dim.startswith("eta_")]
178
+
179
+ if not eta_dims:
180
+ final_dataset[varname] = var_slices[0]
181
+ else:
182
+ eta_dim = eta_dims[0]
183
+ final_dataset[varname] = xr.concat(
184
+ var_slices, dim=eta_dim, combine_attrs="override"
185
+ )
186
+ # Copy attributes from first dataset
187
+ final_dataset.attrs = datasets[0].attrs
188
+
189
+ return final_dataset