roms-tools 3.3.0__py3-none-any.whl → 3.5.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 (246) 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}/lat_lon_datasets.py +88 -64
  6. roms_tools/{setup → datasets}/river_datasets.py +9 -4
  7. roms_tools/datasets/roms_dataset.py +854 -0
  8. roms_tools/datasets/utils.py +487 -0
  9. roms_tools/{setup/fill.py → fill.py} +110 -13
  10. roms_tools/plot.py +4 -4
  11. roms_tools/regrid.py +76 -0
  12. roms_tools/setup/boundary_forcing.py +53 -45
  13. roms_tools/setup/cdr_release.py +2 -4
  14. roms_tools/setup/grid.py +46 -15
  15. roms_tools/setup/initial_conditions.py +330 -71
  16. roms_tools/setup/mask.py +2 -5
  17. roms_tools/setup/nesting.py +13 -6
  18. roms_tools/setup/river_forcing.py +4 -4
  19. roms_tools/setup/surface_forcing.py +15 -11
  20. roms_tools/setup/tides.py +7 -6
  21. roms_tools/setup/topography.py +10 -2
  22. roms_tools/setup/utils.py +292 -666
  23. roms_tools/tests/test_analysis/test_cdr_ensemble.py +4 -6
  24. roms_tools/tests/test_analysis/test_roms_output.py +1 -220
  25. roms_tools/tests/{test_setup → test_datasets}/test_lat_lon_datasets.py +4 -4
  26. roms_tools/tests/{test_setup → test_datasets}/test_river_datasets.py +1 -1
  27. roms_tools/tests/test_datasets/test_roms_dataset.py +743 -0
  28. roms_tools/tests/test_datasets/test_utils.py +527 -0
  29. roms_tools/tests/{test_setup/test_fill.py → test_fill.py} +72 -9
  30. roms_tools/tests/test_regrid.py +120 -1
  31. roms_tools/tests/test_setup/test_boundary_forcing.py +57 -138
  32. roms_tools/tests/test_setup/test_cdr_release.py +4 -5
  33. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zarr.json +293 -2021
  34. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/zarr.json +294 -2022
  35. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK/c/0/0/0/0 +0 -0
  36. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/ALK_west → initial_conditions_from_roms.zarr/ALK}/zarr.json +11 -8
  37. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK_ALT_CO2/c/0/0/0/0 +0 -0
  38. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west → initial_conditions_from_roms.zarr/ALK_ALT_CO2}/zarr.json +11 -8
  39. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_r/c/0 +0 -0
  40. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_unified_climatology.zarr/diatFe_west → initial_conditions_from_roms.zarr/Cs_r}/zarr.json +5 -12
  41. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_w/c/0 +0 -0
  42. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/diatFe_west → initial_conditions_from_roms.zarr/Cs_w}/zarr.json +3 -10
  43. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC/c/0/0/0/0 +0 -0
  44. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/DOCr_west → initial_conditions_from_roms.zarr/DIC}/zarr.json +11 -8
  45. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC_ALT_CO2/c/0/0/0/0 +0 -0
  46. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC_ALT_CO2/zarr.json +57 -0
  47. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOC/c/0/0/0/0 +0 -0
  48. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOC/zarr.json +57 -0
  49. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOCr/c/0/0/0/0 +0 -0
  50. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west → initial_conditions_from_roms.zarr/DOCr}/zarr.json +11 -8
  51. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DON/c/0/0/0/0 +0 -0
  52. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DON/zarr.json +57 -0
  53. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DONr/c/0/0/0/0 +0 -0
  54. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DONr/zarr.json +57 -0
  55. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOP/c/0/0/0/0 +0 -0
  56. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOP/zarr.json +57 -0
  57. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOPr/c/0/0/0/0 +0 -0
  58. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOPr/zarr.json +57 -0
  59. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Fe/c/0/0/0/0 +0 -0
  60. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Fe/zarr.json +57 -0
  61. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Lig/c/0/0/0/0 +0 -0
  62. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/DOP_west → initial_conditions_from_roms.zarr/Lig}/zarr.json +11 -8
  63. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NH4/c/0/0/0/0 +0 -0
  64. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/DON_west → initial_conditions_from_roms.zarr/NH4}/zarr.json +11 -8
  65. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NO3/c/0/0/0/0 +0 -0
  66. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NO3/zarr.json +57 -0
  67. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/O2/c/0/0/0/0 +0 -0
  68. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/Lig_west → initial_conditions_from_roms.zarr/O2}/zarr.json +11 -8
  69. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/PO4/c/0/0/0/0 +0 -0
  70. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/PO4/zarr.json +57 -0
  71. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/SiO3/c/0/0/0/0 +0 -0
  72. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/SiO3/zarr.json +57 -0
  73. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/abs_time/zarr.json +47 -0
  74. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatC/c/0/0/0/0 +0 -0
  75. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/diatC_west → initial_conditions_from_roms.zarr/diatC}/zarr.json +11 -8
  76. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatChl/c/0/0/0/0 +0 -0
  77. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/diatChl_west → initial_conditions_from_roms.zarr/diatChl}/zarr.json +11 -8
  78. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatFe/c/0/0/0/0 +0 -0
  79. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/O2_west → initial_conditions_from_roms.zarr/diatFe}/zarr.json +11 -8
  80. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatP/c/0/0/0/0 +0 -0
  81. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/DIC_west → initial_conditions_from_roms.zarr/diatP}/zarr.json +11 -8
  82. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatSi/c/0/0/0/0 +0 -0
  83. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/DOC_west → initial_conditions_from_roms.zarr/diatSi}/zarr.json +11 -8
  84. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazC/c/0/0/0/0 +0 -0
  85. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazC/zarr.json +57 -0
  86. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazChl/c/0/0/0/0 +0 -0
  87. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/diazChl_west → initial_conditions_from_roms.zarr/diazChl}/zarr.json +11 -8
  88. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazFe/c/0/0/0/0 +0 -0
  89. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/Fe_west → initial_conditions_from_roms.zarr/diazFe}/zarr.json +11 -8
  90. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazP/c/0/0/0/0 +0 -0
  91. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazP/zarr.json +57 -0
  92. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ocean_time/c/0 +0 -0
  93. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ocean_time/zarr.json +47 -0
  94. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/salt/c/0/0/0/0 +0 -0
  95. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_unified_climatology.zarr/ALK_west → initial_conditions_from_roms.zarr/salt}/zarr.json +12 -9
  96. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spC/c/0/0/0/0 +0 -0
  97. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spC/zarr.json +57 -0
  98. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spCaCO3/c/0/0/0/0 +0 -0
  99. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spCaCO3/zarr.json +57 -0
  100. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spChl/c/0/0/0/0 +0 -0
  101. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/spChl_west → initial_conditions_from_roms.zarr/spChl}/zarr.json +11 -8
  102. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spFe/c/0/0/0/0 +0 -0
  103. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spFe/zarr.json +57 -0
  104. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spP/c/0/0/0/0 +0 -0
  105. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spP/zarr.json +57 -0
  106. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/temp/c/0/0/0/0 +0 -0
  107. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/temp/zarr.json +57 -0
  108. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/u/c/0/0/0/0 +0 -0
  109. roms_tools/tests/test_setup/test_data/{bgc_boundary_forcing_from_climatology.zarr/NH4_west → initial_conditions_from_roms.zarr/u}/zarr.json +12 -9
  110. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ubar/c/0/0/0 +0 -0
  111. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ubar/zarr.json +54 -0
  112. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/v/c/0/0/0/0 +0 -0
  113. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/v/zarr.json +57 -0
  114. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/vbar/c/0/0/0 +0 -0
  115. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/vbar/zarr.json +54 -0
  116. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/w/zarr.json +57 -0
  117. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zarr.json +2481 -0
  118. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zeta/c/0/0/0 +0 -0
  119. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zeta/zarr.json +54 -0
  120. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zooC/c/0/0/0/0 +0 -0
  121. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zooC/zarr.json +57 -0
  122. roms_tools/tests/test_setup/test_grid.py +66 -1
  123. roms_tools/tests/test_setup/test_initial_conditions.py +130 -104
  124. roms_tools/tests/test_setup/test_nesting.py +2 -1
  125. roms_tools/tests/test_setup/test_surface_forcing.py +1 -1
  126. roms_tools/tests/test_setup/test_tides.py +1 -1
  127. roms_tools/tests/test_setup/test_utils.py +100 -15
  128. roms_tools/tests/test_setup/test_validation.py +15 -0
  129. roms_tools/tests/test_tiling/test_partition.py +63 -15
  130. roms_tools/tests/test_utils.py +365 -0
  131. roms_tools/tiling/partition.py +81 -211
  132. roms_tools/utils.py +360 -62
  133. {roms_tools-3.3.0.dist-info → roms_tools-3.5.0.dist-info}/METADATA +2 -3
  134. {roms_tools-3.3.0.dist-info → roms_tools-3.5.0.dist-info}/RECORD +137 -174
  135. {roms_tools-3.3.0.dist-info → roms_tools-3.5.0.dist-info}/WHEEL +1 -1
  136. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/c/0/0/0 +0 -0
  137. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/c/0/0/0 +0 -0
  138. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/c/0/0/0 +0 -0
  139. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/c/0/0/0 +0 -0
  140. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/c/0/0/0 +0 -0
  141. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/c/0/0/0 +0 -0
  142. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/c/0/0/0 +0 -0
  143. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/c/0/0/0 +0 -0
  144. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/zarr.json +0 -54
  145. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/c/0/0/0 +0 -0
  146. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/c/0/0/0 +0 -0
  147. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/zarr.json +0 -54
  148. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/c/0/0/0 +0 -0
  149. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/c/0/0/0 +0 -0
  150. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/c/0/0/0 +0 -0
  151. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/c/0/0/0 +0 -0
  152. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/zarr.json +0 -54
  153. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/c/0/0/0 +0 -0
  154. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/c/0/0/0 +0 -0
  155. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/zarr.json +0 -54
  156. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/c/0/0/0 +0 -0
  157. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/zarr.json +0 -54
  158. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/c/0/0/0 +0 -0
  159. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/c/0/0/0 +0 -0
  160. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/c/0/0/0 +0 -0
  161. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/c/0/0/0 +0 -0
  162. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/zarr.json +0 -54
  163. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/c/0/0/0 +0 -0
  164. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/zarr.json +0 -54
  165. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/c/0/0/0 +0 -0
  166. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/zarr.json +0 -54
  167. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/c/0/0/0 +0 -0
  168. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/c/0/0/0 +0 -0
  169. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/zarr.json +0 -54
  170. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/c/0/0/0 +0 -0
  171. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/zarr.json +0 -54
  172. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/c/0/0/0 +0 -0
  173. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/zarr.json +0 -54
  174. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/c/0/0/0 +0 -0
  175. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/zarr.json +0 -54
  176. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/c/0/0/0 +0 -0
  177. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/c/0/0/0 +0 -0
  178. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/zarr.json +0 -54
  179. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/c/0/0/0 +0 -0
  180. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/zarr.json +0 -54
  181. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/c/0/0/0 +0 -0
  182. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/zarr.json +0 -54
  183. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_ALT_CO2_west/c/0/0/0 +0 -0
  184. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_ALT_CO2_west/zarr.json +0 -54
  185. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_west/c/0/0/0 +0 -0
  186. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_ALT_CO2_west/c/0/0/0 +0 -0
  187. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_ALT_CO2_west/zarr.json +0 -54
  188. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_west/c/0/0/0 +0 -0
  189. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_west/zarr.json +0 -54
  190. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOC_west/c/0/0/0 +0 -0
  191. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOC_west/zarr.json +0 -54
  192. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOCr_west/c/0/0/0 +0 -0
  193. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOCr_west/zarr.json +0 -54
  194. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DON_west/c/0/0/0 +0 -0
  195. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DON_west/zarr.json +0 -54
  196. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DONr_west/c/0/0/0 +0 -0
  197. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DONr_west/zarr.json +0 -54
  198. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOP_west/c/0/0/0 +0 -0
  199. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOP_west/zarr.json +0 -54
  200. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOPr_west/c/0/0/0 +0 -0
  201. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOPr_west/zarr.json +0 -54
  202. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Fe_west/c/0/0/0 +0 -0
  203. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Fe_west/zarr.json +0 -54
  204. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Lig_west/c/0/0/0 +0 -0
  205. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Lig_west/zarr.json +0 -54
  206. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NH4_west/c/0/0/0 +0 -0
  207. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NH4_west/zarr.json +0 -54
  208. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NO3_west/c/0/0/0 +0 -0
  209. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NO3_west/zarr.json +0 -54
  210. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/O2_west/c/0/0/0 +0 -0
  211. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/O2_west/zarr.json +0 -54
  212. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/PO4_west/c/0/0/0 +0 -0
  213. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/PO4_west/zarr.json +0 -54
  214. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/SiO3_west/c/0/0/0 +0 -0
  215. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/SiO3_west/zarr.json +0 -54
  216. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatC_west/c/0/0/0 +0 -0
  217. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatC_west/zarr.json +0 -54
  218. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatChl_west/c/0/0/0 +0 -0
  219. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatChl_west/zarr.json +0 -54
  220. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatFe_west/c/0/0/0 +0 -0
  221. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatP_west/c/0/0/0 +0 -0
  222. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatP_west/zarr.json +0 -54
  223. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatSi_west/c/0/0/0 +0 -0
  224. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatSi_west/zarr.json +0 -54
  225. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazC_west/c/0/0/0 +0 -0
  226. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazC_west/zarr.json +0 -54
  227. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazChl_west/c/0/0/0 +0 -0
  228. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazChl_west/zarr.json +0 -54
  229. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazFe_west/c/0/0/0 +0 -0
  230. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazFe_west/zarr.json +0 -54
  231. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazP_west/c/0/0/0 +0 -0
  232. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazP_west/zarr.json +0 -54
  233. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spC_west/c/0/0/0 +0 -0
  234. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spC_west/zarr.json +0 -54
  235. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spCaCO3_west/c/0/0/0 +0 -0
  236. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spCaCO3_west/zarr.json +0 -54
  237. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spChl_west/c/0/0/0 +0 -0
  238. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spChl_west/zarr.json +0 -54
  239. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spFe_west/c/0/0/0 +0 -0
  240. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spFe_west/zarr.json +0 -54
  241. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spP_west/c/0/0/0 +0 -0
  242. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spP_west/zarr.json +0 -54
  243. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/zooC_west/c/0/0/0 +0 -0
  244. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/zooC_west/zarr.json +0 -54
  245. {roms_tools-3.3.0.dist-info → roms_tools-3.5.0.dist-info}/licenses/LICENSE +0 -0
  246. {roms_tools-3.3.0.dist-info → roms_tools-3.5.0.dist-info}/top_level.txt +0 -0
@@ -3,17 +3,29 @@ from pathlib import Path
3
3
  from unittest import mock
4
4
 
5
5
  import numpy as np
6
+ import pandas as pd
6
7
  import pytest
7
8
  import xarray as xr
8
9
 
10
+ from roms_tools.datasets.download import download_test_data
11
+ from roms_tools.datasets.lat_lon_datasets import ERA5Correction
9
12
  from roms_tools.utils import (
13
+ _interpolate_generic,
10
14
  _path_list_from_input,
11
15
  generate_focused_coordinate_range,
12
16
  get_dask_chunks,
13
17
  has_copernicus,
14
18
  has_dask,
15
19
  has_gcsfs,
20
+ interpolate_cyclic_time,
21
+ interpolate_from_climatology,
22
+ interpolate_from_rho_to_u,
23
+ interpolate_from_rho_to_v,
24
+ interpolate_from_u_to_rho,
25
+ interpolate_from_v_to_rho,
16
26
  load_data,
27
+ rotate_velocities,
28
+ wrap_longitudes,
17
29
  )
18
30
 
19
31
 
@@ -241,3 +253,356 @@ def test_time_chunking_false_roms():
241
253
  dim_names = {"time": "ocean_time"}
242
254
  result = get_dask_chunks(dim_names, time_chunking=False)
243
255
  assert "ocean_time" not in result
256
+
257
+
258
+ # test interpolate_from_climatology
259
+
260
+
261
+ @pytest.fixture
262
+ def climatology_data():
263
+ """Create a simple annual cycle dataset with 12 time points."""
264
+ time_coord = np.arange(1, 13) # months as day_of_year approximation
265
+ da = xr.DataArray(np.arange(12), dims=("time",), coords={"time": time_coord})
266
+ ds = xr.Dataset({"var1": da, "var2": da * 2})
267
+ return da, ds, "time", "time"
268
+
269
+
270
+ def test_interpolate_dataarray_single_time(climatology_data):
271
+ da, _, time_dim, time_coord = climatology_data
272
+ target_time = pd.Timestamp("2000-03-15") # day_of_year ~ 75
273
+ interpolated = interpolate_from_climatology(da, time_dim, time_coord, target_time)
274
+ assert isinstance(interpolated, xr.DataArray)
275
+ assert interpolated.sizes[time_dim] == 1
276
+
277
+
278
+ def test_interpolate_dataset_multiple_times(climatology_data):
279
+ _, ds, time_dim, time_coord = climatology_data
280
+ target_times = pd.date_range("2000-01-01", periods=3, freq="ME")
281
+ interpolated = interpolate_from_climatology(ds, time_dim, time_coord, target_times)
282
+ assert isinstance(interpolated, xr.Dataset)
283
+ assert all(interpolated[var].sizes[time_dim] == 3 for var in interpolated.data_vars)
284
+
285
+
286
+ def test_interpolate_dataarray_time_dim_not_equal_time_coord():
287
+ time_values = np.arange(1, 13)
288
+ da = xr.DataArray(
289
+ np.arange(12),
290
+ dims=("time_dim",),
291
+ coords={"time_coord": ("time_dim", time_values)},
292
+ )
293
+ target_time = pd.Timestamp("2000-06-15")
294
+ interpolated = interpolate_from_climatology(
295
+ da, time_dim="time_dim", time_coord="time_coord", time=target_time
296
+ )
297
+ assert interpolated.sizes["time_dim"] == 1
298
+ assert np.issubdtype(interpolated.dtype, np.number)
299
+
300
+
301
+ def test_interpolate_cyclic_time_basic():
302
+ time_values = np.arange(1, 13)
303
+ da = xr.DataArray(np.arange(12), dims=("time",), coords={"time": time_values})
304
+ target_days = [0.5, 6.5, 12.5] # fractional days, include cyclic behavior
305
+ interpolated = interpolate_cyclic_time(
306
+ da, time_dim="time", time_coord="time", day_of_year=target_days
307
+ )
308
+ assert isinstance(interpolated, xr.DataArray)
309
+ assert interpolated.sizes["time"] == len(target_days)
310
+
311
+
312
+ def test_interpolate_from_climatology_invalid_input():
313
+ with pytest.raises(TypeError):
314
+ interpolate_from_climatology(
315
+ "not a dataset", "time", "time", pd.Timestamp("2000-01-01")
316
+ )
317
+
318
+
319
+ def test_interpolate_from_real_climatology(use_dask):
320
+ fname = download_test_data("ERA5_regional_test_data.nc")
321
+ era5_times = xr.open_dataset(fname).time
322
+
323
+ climatology = ERA5Correction(use_dask=use_dask)
324
+ field = climatology.ds["ssr_corr"]
325
+ field["time"] = field["time"].dt.days
326
+
327
+ interpolated_field = interpolate_from_climatology(field, "time", "time", era5_times)
328
+ assert len(interpolated_field.time) == len(era5_times)
329
+
330
+
331
+ def test_wrap_longitudes_staggered():
332
+ # Dimensions
333
+ eta_rho, xi_rho, xi_u = 3, 4, 5
334
+ eta_v = 2
335
+
336
+ # Create 2D coordinates
337
+ lon_rho = xr.DataArray(
338
+ np.linspace(0, 360, eta_rho * xi_rho).reshape(eta_rho, xi_rho),
339
+ dims=("eta_rho", "xi_rho"),
340
+ attrs={"units": "degrees_east"},
341
+ )
342
+ lon_u = xr.DataArray(
343
+ np.linspace(-190, 190, eta_rho * xi_u).reshape(eta_rho, xi_u),
344
+ dims=("eta_rho", "xi_u"),
345
+ attrs={"units": "degrees_east"},
346
+ )
347
+ lon_v = xr.DataArray(
348
+ np.linspace(-180, 180, eta_v * xi_rho).reshape(eta_v, xi_rho),
349
+ dims=("eta_v", "xi_rho"),
350
+ attrs={"units": "degrees_east"},
351
+ )
352
+
353
+ # Dummy variables
354
+ ds = xr.Dataset(
355
+ {
356
+ "dummy_rho": (("eta_rho", "xi_rho"), np.zeros((eta_rho, xi_rho))),
357
+ "dummy_u": (("eta_rho", "xi_u"), np.zeros((eta_rho, xi_u))),
358
+ "dummy_v": (("eta_v", "xi_rho"), np.zeros((eta_v, xi_rho))),
359
+ },
360
+ coords={"lon_rho": lon_rho, "lon_u": lon_u, "lon_v": lon_v},
361
+ )
362
+
363
+ # Wrap to [-180, 180]
364
+ ds_wrapped = wrap_longitudes(ds, straddle=True)
365
+
366
+ # Check values: all >180 should be shifted
367
+ assert ds_wrapped.lon_rho.max().values <= 180
368
+ assert ds_wrapped.lon_u.max().values <= 180
369
+ assert ds_wrapped.lon_v.max().values <= 180
370
+
371
+ # Wrap to [0, 360]
372
+ ds_wrapped2 = wrap_longitudes(ds, straddle=False)
373
+ assert ds_wrapped2.lon_rho.min().values >= 0
374
+ assert ds_wrapped2.lon_u.min().values >= 0
375
+ assert ds_wrapped2.lon_v.min().values >= 0
376
+
377
+ # Check attributes preserved
378
+ for name in ["lon_rho", "lon_u", "lon_v"]:
379
+ assert ds.coords[name].attrs == ds_wrapped.coords[name].attrs
380
+
381
+
382
+ # test _interpolate_generic and its wrappers
383
+
384
+ # -------------------------
385
+ # Fixtures
386
+ # -------------------------
387
+
388
+
389
+ @pytest.fixture
390
+ def sample_rho_field() -> xr.DataArray:
391
+ """Create a simple rho-point field for testing."""
392
+ data = np.arange(12, dtype=float).reshape(3, 4)
393
+ eta = np.arange(3)
394
+ xi = np.arange(4)
395
+
396
+ return xr.DataArray(
397
+ data,
398
+ dims=("eta_rho", "xi_rho"),
399
+ coords={
400
+ "lat_rho": (("eta_rho", "xi_rho"), eta[:, None] * np.ones((1, 4))),
401
+ "lon_rho": (("eta_rho", "xi_rho"), np.ones((3, 1)) * xi[None, :]),
402
+ },
403
+ )
404
+
405
+
406
+ @pytest.fixture
407
+ def sample_u_field() -> xr.DataArray:
408
+ """Create a simple u-point field for testing."""
409
+ data = np.arange(9, dtype=float).reshape(3, 3)
410
+ eta = np.arange(3)
411
+ xi = np.arange(3)
412
+
413
+ return xr.DataArray(
414
+ data,
415
+ dims=("eta_rho", "xi_u"),
416
+ coords={
417
+ "lat_u": (("eta_rho", "xi_u"), eta[:, None] * np.ones((1, 3))),
418
+ "lon_u": (("eta_rho", "xi_u"), np.ones((3, 1)) * xi[None, :]),
419
+ },
420
+ )
421
+
422
+
423
+ @pytest.fixture
424
+ def sample_v_field() -> xr.DataArray:
425
+ """Create a simple v-point field for testing."""
426
+ data = np.arange(8, dtype=float).reshape(2, 4)
427
+ eta = np.arange(2)
428
+ xi = np.arange(4)
429
+
430
+ return xr.DataArray(
431
+ data,
432
+ dims=("eta_v", "xi_rho"),
433
+ coords={
434
+ "lat_v": (("eta_v", "xi_rho"), eta[:, None] * np.ones((1, 4))),
435
+ "lon_v": (("eta_v", "xi_rho"), np.ones((2, 1)) * xi[None, :]),
436
+ },
437
+ )
438
+
439
+
440
+ # -------------------------
441
+ # Generic interpolation tests
442
+ # -------------------------
443
+
444
+
445
+ def test_interpolate_from_rho_to_u_additive(sample_rho_field: xr.DataArray):
446
+ result = _interpolate_generic(
447
+ sample_rho_field, dim_in="xi_rho", dim_out="xi_u", method="additive"
448
+ )
449
+
450
+ # One fewer point along xi
451
+ assert result.shape[1] == sample_rho_field.shape[1] - 1
452
+
453
+ expected = 0.5 * (sample_rho_field.values[:, 1:] + sample_rho_field.values[:, :-1])
454
+ np.testing.assert_allclose(result.values, expected)
455
+
456
+
457
+ def test_interpolate_from_rho_to_u_multiplicative(sample_rho_field: xr.DataArray):
458
+ result = _interpolate_generic(
459
+ sample_rho_field, dim_in="xi_rho", dim_out="xi_u", method="multiplicative"
460
+ )
461
+
462
+ expected = sample_rho_field.values[:, 1:] * sample_rho_field.values[:, :-1]
463
+ np.testing.assert_allclose(result.values, expected)
464
+
465
+
466
+ # -------------------------
467
+ # Wrapper tests
468
+ # -------------------------
469
+
470
+
471
+ def test_rho_to_u_wrapper_additive(sample_rho_field: xr.DataArray):
472
+ result = interpolate_from_rho_to_u(sample_rho_field, method="additive")
473
+
474
+ # Dimension swap
475
+ assert "xi_u" in result.dims
476
+ assert "xi_rho" not in result.dims
477
+
478
+ # Coordinates dropped
479
+ for coord in ("lat_rho", "lon_rho"):
480
+ assert coord not in result.coords
481
+
482
+ # Shape check
483
+ assert result.sizes["xi_u"] == sample_rho_field.sizes["xi_rho"] - 1
484
+
485
+
486
+ def test_rho_to_v_wrapper_additive(sample_rho_field: xr.DataArray):
487
+ result = interpolate_from_rho_to_v(sample_rho_field, method="additive")
488
+
489
+ # Dimension swap
490
+ assert "eta_v" in result.dims
491
+ assert "eta_rho" not in result.dims
492
+
493
+ # Coordinates dropped
494
+ for coord in ("lat_rho", "lon_rho"):
495
+ assert coord not in result.coords
496
+
497
+ # Shape check
498
+ assert result.sizes["eta_v"] == sample_rho_field.sizes["eta_rho"] - 1
499
+
500
+
501
+ def test_u_to_rho_wrapper_additive(sample_u_field: xr.DataArray):
502
+ result = interpolate_from_u_to_rho(sample_u_field, method="additive")
503
+
504
+ # Dimension swap
505
+ assert "xi_rho" in result.dims
506
+ assert "xi_u" not in result.dims
507
+
508
+ # Coordinates dropped
509
+ for coord in ("lat_u", "lon_u"):
510
+ assert coord not in result.coords
511
+
512
+ # Shape: one more along xi due to padding
513
+ assert result.sizes["xi_rho"] == sample_u_field.sizes["xi_u"] + 1
514
+
515
+
516
+ def test_v_to_rho_wrapper_additive(sample_v_field: xr.DataArray):
517
+ result = interpolate_from_v_to_rho(sample_v_field, method="additive")
518
+
519
+ # Dimension swap
520
+ assert "eta_rho" in result.dims
521
+ assert "eta_v" not in result.dims
522
+
523
+ # Coordinates dropped
524
+ for coord in ("lat_v", "lon_v"):
525
+ assert coord not in result.coords
526
+
527
+ # Shape: one more along eta due to padding
528
+ assert result.sizes["eta_rho"] == sample_v_field.sizes["eta_v"] + 1
529
+
530
+
531
+ # -------------------------
532
+ # Error handling
533
+ # -------------------------
534
+
535
+
536
+ def test_invalid_method_raises(
537
+ sample_rho_field: xr.DataArray,
538
+ sample_u_field: xr.DataArray,
539
+ sample_v_field: xr.DataArray,
540
+ ):
541
+ with pytest.raises(NotImplementedError):
542
+ interpolate_from_rho_to_u(sample_rho_field, method="unsupported")
543
+
544
+ with pytest.raises(NotImplementedError):
545
+ interpolate_from_rho_to_v(sample_rho_field, method="unsupported")
546
+
547
+ with pytest.raises(NotImplementedError):
548
+ interpolate_from_u_to_rho(sample_u_field, method="unsupported")
549
+
550
+ with pytest.raises(NotImplementedError):
551
+ interpolate_from_v_to_rho(sample_v_field, method="unsupported")
552
+
553
+
554
+ # Test rotate_velocities
555
+ @pytest.fixture
556
+ def sample_velocities_centered():
557
+ """Create a centered-grid velocity field with random values and grid angle."""
558
+ np.random.seed(42) # For reproducibility
559
+
560
+ eta_rho, xi_rho = 10, 15
561
+
562
+ u = xr.DataArray(
563
+ np.random.rand(eta_rho, xi_rho),
564
+ dims=("eta_rho", "xi_rho"),
565
+ coords={
566
+ "eta_rho": np.arange(eta_rho),
567
+ "xi_rho": np.arange(xi_rho),
568
+ },
569
+ )
570
+
571
+ v = xr.DataArray(
572
+ np.random.rand(eta_rho, xi_rho),
573
+ dims=("eta_rho", "xi_rho"),
574
+ coords={
575
+ "eta_rho": np.arange(eta_rho),
576
+ "xi_rho": np.arange(xi_rho),
577
+ },
578
+ )
579
+
580
+ angle = xr.DataArray(
581
+ np.random.rand(eta_rho, xi_rho) * np.pi / 2
582
+ - np.pi / 4, # random angles in [-45°, 45°]
583
+ dims=("eta_rho", "xi_rho"),
584
+ coords={
585
+ "eta_rho": np.arange(eta_rho),
586
+ "xi_rho": np.arange(xi_rho),
587
+ },
588
+ )
589
+
590
+ return u, v, angle
591
+
592
+
593
+ def test_rotate_velocities_roundtrip(sample_velocities_centered):
594
+ """Test rotation to grid and back recovers original velocities."""
595
+ u, v, angle = sample_velocities_centered
596
+
597
+ # Rotate forward: lat-lon → model grid
598
+ u_rot, v_rot = rotate_velocities(
599
+ u, v, angle, interpolate_before=False, interpolate_after=False
600
+ )
601
+
602
+ # Rotate backward: model grid → lat-lon
603
+ u_back, v_back = rotate_velocities(
604
+ u_rot, v_rot, -angle, interpolate_before=False, interpolate_after=False
605
+ )
606
+
607
+ np.testing.assert_allclose(u.values, u_back.values)
608
+ np.testing.assert_allclose(v.values, v_back.values)