roms-tools 3.3.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 (174) 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 +76 -64
  6. roms_tools/{setup → datasets}/river_datasets.py +9 -4
  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 +4 -4
  11. roms_tools/setup/boundary_forcing.py +51 -43
  12. roms_tools/setup/cdr_release.py +2 -4
  13. roms_tools/setup/grid.py +29 -12
  14. roms_tools/setup/initial_conditions.py +19 -19
  15. roms_tools/setup/nesting.py +8 -4
  16. roms_tools/setup/river_forcing.py +4 -4
  17. roms_tools/setup/surface_forcing.py +14 -9
  18. roms_tools/setup/tides.py +1 -1
  19. roms_tools/setup/topography.py +10 -2
  20. roms_tools/setup/utils.py +72 -524
  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}/test_lat_lon_datasets.py +4 -4
  24. roms_tools/tests/{test_setup → test_datasets}/test_river_datasets.py +1 -1
  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/zarr.json +293 -2021
  31. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/zarr.json +294 -2022
  32. roms_tools/tests/test_setup/test_grid.py +42 -1
  33. roms_tools/tests/test_setup/test_initial_conditions.py +3 -94
  34. roms_tools/tests/test_setup/test_nesting.py +2 -1
  35. roms_tools/tests/test_setup/test_surface_forcing.py +1 -1
  36. roms_tools/tests/test_setup/test_tides.py +1 -1
  37. roms_tools/tests/test_setup/test_utils.py +100 -15
  38. roms_tools/tests/test_tiling/test_partition.py +63 -15
  39. roms_tools/tests/test_utils.py +78 -0
  40. roms_tools/tiling/partition.py +81 -211
  41. roms_tools/utils.py +193 -0
  42. {roms_tools-3.3.0.dist-info → roms_tools-3.4.0.dist-info}/METADATA +1 -1
  43. {roms_tools-3.3.0.dist-info → roms_tools-3.4.0.dist-info}/RECORD +46 -170
  44. {roms_tools-3.3.0.dist-info → roms_tools-3.4.0.dist-info}/WHEEL +1 -1
  45. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/c/0/0/0 +0 -0
  46. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/zarr.json +0 -54
  47. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/c/0/0/0 +0 -0
  48. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/zarr.json +0 -54
  49. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/c/0/0/0 +0 -0
  50. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/zarr.json +0 -54
  51. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/c/0/0/0 +0 -0
  52. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/zarr.json +0 -54
  53. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/c/0/0/0 +0 -0
  54. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/zarr.json +0 -54
  55. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/c/0/0/0 +0 -0
  56. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/zarr.json +0 -54
  57. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/c/0/0/0 +0 -0
  58. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/zarr.json +0 -54
  59. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/c/0/0/0 +0 -0
  60. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/zarr.json +0 -54
  61. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/c/0/0/0 +0 -0
  62. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/zarr.json +0 -54
  63. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/c/0/0/0 +0 -0
  64. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/zarr.json +0 -54
  65. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/c/0/0/0 +0 -0
  66. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/zarr.json +0 -54
  67. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/c/0/0/0 +0 -0
  68. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/zarr.json +0 -54
  69. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/c/0/0/0 +0 -0
  70. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/zarr.json +0 -54
  71. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/c/0/0/0 +0 -0
  72. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/zarr.json +0 -54
  73. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/c/0/0/0 +0 -0
  74. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/zarr.json +0 -54
  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/PO4_west/zarr.json +0 -54
  77. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/c/0/0/0 +0 -0
  78. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/zarr.json +0 -54
  79. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/c/0/0/0 +0 -0
  80. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/zarr.json +0 -54
  81. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/c/0/0/0 +0 -0
  82. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/zarr.json +0 -54
  83. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/c/0/0/0 +0 -0
  84. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/zarr.json +0 -54
  85. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/c/0/0/0 +0 -0
  86. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/zarr.json +0 -54
  87. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/c/0/0/0 +0 -0
  88. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/zarr.json +0 -54
  89. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/c/0/0/0 +0 -0
  90. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/zarr.json +0 -54
  91. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/c/0/0/0 +0 -0
  92. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/zarr.json +0 -54
  93. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/c/0/0/0 +0 -0
  94. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/zarr.json +0 -54
  95. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/c/0/0/0 +0 -0
  96. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/zarr.json +0 -54
  97. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/c/0/0/0 +0 -0
  98. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/zarr.json +0 -54
  99. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/c/0/0/0 +0 -0
  100. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/zarr.json +0 -54
  101. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/c/0/0/0 +0 -0
  102. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/zarr.json +0 -54
  103. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/c/0/0/0 +0 -0
  104. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/zarr.json +0 -54
  105. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/c/0/0/0 +0 -0
  106. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/zarr.json +0 -54
  107. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/c/0/0/0 +0 -0
  108. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/zarr.json +0 -54
  109. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_ALT_CO2_west/c/0/0/0 +0 -0
  110. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_ALT_CO2_west/zarr.json +0 -54
  111. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_west/c/0/0/0 +0 -0
  112. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/ALK_west/zarr.json +0 -54
  113. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_ALT_CO2_west/c/0/0/0 +0 -0
  114. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_ALT_CO2_west/zarr.json +0 -54
  115. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_west/c/0/0/0 +0 -0
  116. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DIC_west/zarr.json +0 -54
  117. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOC_west/c/0/0/0 +0 -0
  118. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOC_west/zarr.json +0 -54
  119. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOCr_west/c/0/0/0 +0 -0
  120. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOCr_west/zarr.json +0 -54
  121. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DON_west/c/0/0/0 +0 -0
  122. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DON_west/zarr.json +0 -54
  123. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DONr_west/c/0/0/0 +0 -0
  124. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DONr_west/zarr.json +0 -54
  125. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOP_west/c/0/0/0 +0 -0
  126. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOP_west/zarr.json +0 -54
  127. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOPr_west/c/0/0/0 +0 -0
  128. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/DOPr_west/zarr.json +0 -54
  129. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Fe_west/c/0/0/0 +0 -0
  130. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Fe_west/zarr.json +0 -54
  131. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Lig_west/c/0/0/0 +0 -0
  132. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/Lig_west/zarr.json +0 -54
  133. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NH4_west/c/0/0/0 +0 -0
  134. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NH4_west/zarr.json +0 -54
  135. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NO3_west/c/0/0/0 +0 -0
  136. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/NO3_west/zarr.json +0 -54
  137. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/O2_west/c/0/0/0 +0 -0
  138. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/O2_west/zarr.json +0 -54
  139. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/PO4_west/c/0/0/0 +0 -0
  140. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/PO4_west/zarr.json +0 -54
  141. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/SiO3_west/c/0/0/0 +0 -0
  142. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/SiO3_west/zarr.json +0 -54
  143. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatC_west/c/0/0/0 +0 -0
  144. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatC_west/zarr.json +0 -54
  145. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatChl_west/c/0/0/0 +0 -0
  146. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatChl_west/zarr.json +0 -54
  147. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatFe_west/c/0/0/0 +0 -0
  148. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatFe_west/zarr.json +0 -54
  149. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatP_west/c/0/0/0 +0 -0
  150. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatP_west/zarr.json +0 -54
  151. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatSi_west/c/0/0/0 +0 -0
  152. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diatSi_west/zarr.json +0 -54
  153. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazC_west/c/0/0/0 +0 -0
  154. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazC_west/zarr.json +0 -54
  155. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazChl_west/c/0/0/0 +0 -0
  156. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazChl_west/zarr.json +0 -54
  157. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazFe_west/c/0/0/0 +0 -0
  158. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazFe_west/zarr.json +0 -54
  159. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazP_west/c/0/0/0 +0 -0
  160. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/diazP_west/zarr.json +0 -54
  161. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spC_west/c/0/0/0 +0 -0
  162. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spC_west/zarr.json +0 -54
  163. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spCaCO3_west/c/0/0/0 +0 -0
  164. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spCaCO3_west/zarr.json +0 -54
  165. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spChl_west/c/0/0/0 +0 -0
  166. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spChl_west/zarr.json +0 -54
  167. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spFe_west/c/0/0/0 +0 -0
  168. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spFe_west/zarr.json +0 -54
  169. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spP_west/c/0/0/0 +0 -0
  170. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/spP_west/zarr.json +0 -54
  171. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/zooC_west/c/0/0/0 +0 -0
  172. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_unified_climatology.zarr/zooC_west/zarr.json +0 -54
  173. {roms_tools-3.3.0.dist-info → roms_tools-3.4.0.dist-info}/licenses/LICENSE +0 -0
  174. {roms_tools-3.3.0.dist-info → roms_tools-3.4.0.dist-info}/top_level.txt +0 -0
roms_tools/setup/utils.py CHANGED
@@ -4,12 +4,11 @@ import time
4
4
  import typing
5
5
  from collections.abc import Sequence
6
6
  from dataclasses import asdict, fields, is_dataclass
7
- from datetime import datetime, timedelta
7
+ from datetime import datetime
8
8
  from enum import StrEnum
9
9
  from pathlib import Path
10
10
  from typing import Any, Literal, TypeAlias
11
11
 
12
- import cftime
13
12
  import numba as nb
14
13
  import numpy as np
15
14
  import pandas as pd
@@ -128,32 +127,6 @@ def substitute_nans_by_fillvalue(field, fill_value=0.0) -> xr.DataArray:
128
127
  return field.fillna(fill_value)
129
128
 
130
129
 
131
- def one_dim_fill(da: xr.DataArray, dim: str, direction="forward") -> xr.DataArray:
132
- """Fill NaN values in a DataArray along a specified dimension.
133
-
134
- Parameters
135
- ----------
136
- da : xr.DataArray
137
- The input DataArray with NaN values to be filled, which must include the specified dimension.
138
- dim : str
139
- The name of the dimension along which to fill NaN values (e.g., 'depth' or 'time').
140
- direction : str, optional
141
- The filling direction; either "forward" to propagate non-NaN values downward or "backward" to propagate them upward.
142
- Defaults to "forward".
143
-
144
- Returns
145
- -------
146
- xr.DataArray
147
- A new DataArray with NaN values filled in the specified direction, leaving the original data unchanged.
148
- """
149
- if dim in da.dims:
150
- if direction == "forward":
151
- return da.ffill(dim=dim)
152
- elif direction == "backward":
153
- return da.bfill(dim=dim)
154
- return da
155
-
156
-
157
130
  def assign_dates_to_climatology(ds: xr.Dataset, time_dim: str) -> xr.Dataset:
158
131
  """Assigns climatology dates to the dataset's time dimension.
159
132
 
@@ -182,240 +155,6 @@ def assign_dates_to_climatology(ds: xr.Dataset, time_dim: str) -> xr.Dataset:
182
155
  return ds
183
156
 
184
157
 
185
- def interpolate_cyclic_time(
186
- data_array: xr.DataArray,
187
- time_dim_name: str,
188
- day_of_year: int | float | np.ndarray | xr.DataArray | Sequence[int | float],
189
- ) -> xr.DataArray:
190
- """Interpolates a DataArray cyclically across the start and end of the year.
191
-
192
- This function extends the data cyclically by appending the last time step
193
- (shifted back by one year) at the beginning and the first time step
194
- (shifted forward by one year) at the end. It then performs linear interpolation
195
- to match the specified `day_of_year` values.
196
-
197
- Parameters
198
- ----------
199
- data_array : xr.DataArray
200
- The input data array containing a time-like dimension.
201
- time_dim_name : str
202
- The name of the time dimension in the dataset.
203
- day_of_year : Union[int, float, np.ndarray, xr.DataArray, Sequence[Union[int, float]]]
204
- The target day(s) of the year for interpolation. This can be:
205
- - A single integer or float representing the day of the year.
206
- - A NumPy array or xarray DataArray containing multiple days.
207
- - A list or tuple of integers or floats for multiple target days.
208
-
209
- Returns
210
- -------
211
- xr.DataArray
212
- The interpolated DataArray, ensuring cyclic continuity across year boundaries.
213
-
214
- Notes
215
- -----
216
- - This function is useful for interpolating climatological data, where the time axis
217
- represents a repeating annual cycle.
218
- - The `day_of_year` values should be within the range [1, 365] or [1, 366] for leap years.
219
- """
220
- # Concatenate across the beginning and end of the year
221
- time_concat = xr.concat(
222
- [
223
- data_array[time_dim_name][-1] - 365.25, # Shift last time backward
224
- data_array[time_dim_name],
225
- data_array[time_dim_name][0] + 365.25, # Shift first time forward
226
- ],
227
- dim=time_dim_name,
228
- )
229
-
230
- data_array_concat = xr.concat(
231
- [
232
- data_array.isel(
233
- **{time_dim_name: -1}
234
- ), # Append last value at the beginning
235
- data_array,
236
- data_array.isel(**{time_dim_name: 0}), # Append first value at the end
237
- ],
238
- dim=time_dim_name,
239
- )
240
- data_array_concat[time_dim_name] = time_concat
241
-
242
- # Interpolate to specified times
243
- data_array_interpolated = data_array_concat.interp(
244
- **{time_dim_name: day_of_year}, method="linear"
245
- )
246
-
247
- return data_array_interpolated
248
-
249
-
250
- def interpolate_from_climatology(
251
- field: xr.DataArray | xr.Dataset,
252
- time_dim_name: str,
253
- time: xr.DataArray | pd.DatetimeIndex,
254
- ) -> xr.DataArray | xr.Dataset:
255
- """Interpolates a climatological field to specified time points.
256
-
257
- This function interpolates the input `field` based on `day_of_year` values
258
- extracted from the provided `time` points. If `field` is an `xarray.Dataset`,
259
- interpolation is applied to all its data variables individually.
260
-
261
- Parameters
262
- ----------
263
- field : xarray.DataArray or xarray.Dataset
264
- The input field to be interpolated.
265
- - If `field` is an `xarray.DataArray`, it must have a time dimension identified by `time_dim_name`.
266
- - If `field` is an `xarray.Dataset`, all variables within the dataset are interpolated along `time_dim_name`.
267
- The time dimension is assumed to represent `day_of_year` for climatological purposes.
268
- time_dim_name : str
269
- The name of the time dimension in `field`. This dimension is used for interpolation.
270
- time : xarray.DataArray or pandas.DatetimeIndex
271
- The target time points for interpolation. These are internally converted to `day_of_year`
272
- before performing interpolation.
273
-
274
- Returns
275
- -------
276
- xarray.DataArray or xarray.Dataset
277
- The interpolated field, maintaining the same type (`xarray.DataArray` or `xarray.Dataset`)
278
- but aligned to the specified `time` values.
279
-
280
- Notes
281
- -----
282
- - This function assumes that `field` represents a climatological dataset, where time is expressed as `day_of_year` (1-365).
283
- - The `time` input is automatically converted to `day_of_year`, so manual conversion is not required before calling this function.
284
- """
285
-
286
- def interpolate_single_field(data_array: xr.DataArray) -> xr.DataArray:
287
- if isinstance(time, xr.DataArray):
288
- # Extract day of year from xarray.DataArray
289
- day_of_year = time.dt.dayofyear
290
- else:
291
- if np.size(time) == 1:
292
- # Convert single datetime64 object to pandas.Timestamp
293
- date = pd.Timestamp(time)
294
- day_of_year = (
295
- date.dayofyear
296
- + (date.hour / 24)
297
- + (date.minute / 1440)
298
- + (date.second / 86400)
299
- )
300
- else:
301
- # Convert each datetime64 object in the array to pandas.Timestamp and compute fractional day of year
302
- day_of_year = np.array(
303
- [
304
- pd.Timestamp(t).dayofyear
305
- + (pd.Timestamp(t).hour / 24)
306
- + (pd.Timestamp(t).minute / 1440)
307
- + (pd.Timestamp(t).second / 86400)
308
- for t in time
309
- ]
310
- )
311
-
312
- data_array_interpolated = interpolate_cyclic_time(
313
- data_array, time_dim_name, day_of_year
314
- )
315
-
316
- if np.size(time) == 1:
317
- data_array_interpolated = data_array_interpolated.expand_dims(
318
- {time_dim_name: 1}
319
- )
320
- return data_array_interpolated
321
-
322
- if isinstance(field, xr.DataArray):
323
- return interpolate_single_field(field)
324
- elif isinstance(field, xr.Dataset):
325
- interpolated_data_vars = {
326
- var: interpolate_single_field(data_array)
327
- for var, data_array in field.data_vars.items()
328
- }
329
- return xr.Dataset(interpolated_data_vars, attrs=field.attrs)
330
-
331
- else:
332
- raise TypeError("Input 'field' must be an xarray.DataArray or xarray.Dataset.")
333
-
334
-
335
- def get_time_type(data_array: xr.DataArray) -> str:
336
- """Determines the type of time values in the xarray DataArray.
337
-
338
- Parameters
339
- ----------
340
- data_array : xr.DataArray
341
- The xarray DataArray to be checked for time data types.
342
-
343
- Returns
344
- -------
345
- str
346
- A string indicating the type of the time data: 'cftime', 'datetime', or 'int'.
347
-
348
- Raises
349
- ------
350
- TypeError
351
- If the values in the DataArray are not of type numpy.ndarray or list.
352
- """
353
- # List of cftime datetime types
354
- cftime_types = (
355
- cftime.DatetimeNoLeap,
356
- cftime.DatetimeJulian,
357
- cftime.DatetimeGregorian,
358
- cftime.Datetime360Day,
359
- cftime.DatetimeProlepticGregorian,
360
- )
361
-
362
- # Check if any of the coordinate values are of cftime, datetime, or integer type
363
- if isinstance(data_array.values, np.ndarray | list):
364
- # Check if the data type is numpy datetime64, indicating standard datetime objects
365
- if data_array.values.dtype == "datetime64[ns]":
366
- return "datetime"
367
-
368
- # Check if any values in the array are instances of cftime types
369
- if any(isinstance(value, cftime_types) for value in data_array.values):
370
- return "cftime"
371
-
372
- # Check if all values are of integer type (e.g., for indices or time steps)
373
- if np.issubdtype(data_array.values.dtype, np.integer):
374
- return "int"
375
-
376
- # If none of the above conditions are met, raise a ValueError
377
- raise ValueError("Unsupported data type for time values in input dataset.")
378
-
379
- # Handle unexpected types
380
- raise TypeError("DataArray values must be of type numpy.ndarray or list.")
381
-
382
-
383
- def convert_cftime_to_datetime(data_array: np.ndarray) -> np.ndarray:
384
- """Converts cftime datetime objects to numpy datetime64 objects in a numpy ndarray.
385
-
386
- Parameters
387
- ----------
388
- data_array : np.ndarray
389
- The numpy ndarray containing cftime datetime objects to be converted.
390
-
391
- Returns
392
- -------
393
- np.ndarray
394
- The ndarray with cftime datetimes converted to numpy datetime64 objects.
395
-
396
- Notes
397
- -----
398
- This function is intended to be used with numpy ndarrays. If you need to convert
399
- cftime datetime objects in an xarray.DataArray, please use the appropriate function
400
- to handle xarray.DataArray conversions.
401
- """
402
- # List of cftime datetime types
403
- cftime_types = (
404
- cftime.DatetimeNoLeap,
405
- cftime.DatetimeJulian,
406
- cftime.DatetimeGregorian,
407
- )
408
-
409
- # Define a conversion function for cftime to numpy datetime64
410
- def convert_datetime(dt):
411
- if isinstance(dt, cftime_types):
412
- # Convert to ISO format and then to nanosecond precision
413
- return np.datetime64(dt.isoformat(), "ns")
414
- return np.datetime64(dt, "ns")
415
-
416
- return np.vectorize(convert_datetime)(data_array)
417
-
418
-
419
158
  def get_variable_metadata():
420
159
  """Retrieves metadata for commonly used variables in the dataset.
421
160
 
@@ -1861,38 +1600,6 @@ def get_boundary_coords():
1861
1600
  return bdry_coords
1862
1601
 
1863
1602
 
1864
- def wrap_longitudes(grid_ds, straddle):
1865
- """Adjusts longitude values in a dataset to handle dateline crossing.
1866
-
1867
- Parameters
1868
- ----------
1869
- grid_ds : xr.Dataset
1870
- The dataset containing longitude variables to adjust.
1871
- straddle : bool
1872
- If True, adjusts longitudes to the range [-180, 180] for datasets
1873
- that straddle the dateline. If False, adjusts longitudes to the
1874
- range [0, 360].
1875
-
1876
- Returns
1877
- -------
1878
- xr.Dataset
1879
- The dataset with adjusted longitude values.
1880
- """
1881
- for lon_dim in ["lon_rho", "lon_u", "lon_v"]:
1882
- if straddle:
1883
- grid_ds[lon_dim] = xr.where(
1884
- grid_ds[lon_dim] > 180,
1885
- grid_ds[lon_dim] - 360,
1886
- grid_ds[lon_dim],
1887
- )
1888
- else:
1889
- grid_ds[lon_dim] = xr.where(
1890
- grid_ds[lon_dim] < 0, grid_ds[lon_dim] + 360, grid_ds[lon_dim]
1891
- )
1892
-
1893
- return grid_ds
1894
-
1895
-
1896
1603
  def to_float(val):
1897
1604
  """Convert a value or list of values to float.
1898
1605
 
@@ -1967,260 +1674,101 @@ def validate_names(
1967
1674
  return names
1968
1675
 
1969
1676
 
1970
- def check_dataset(
1971
- ds: xr.Dataset,
1972
- dim_names: dict[str, str],
1973
- var_names: dict[str, str],
1974
- opt_var_names: dict[str, str] | None = None,
1975
- ) -> None:
1976
- """Check if the dataset contains the specified variables and dimensions.
1677
+ def check_and_set_boundaries(
1678
+ boundaries: dict[str, bool] | None,
1679
+ mask: xr.DataArray,
1680
+ ) -> dict[str, bool]:
1681
+ """
1682
+ Validate and finalize the `boundaries` dictionary.
1977
1683
 
1978
1684
  Parameters
1979
1685
  ----------
1980
- ds : xr.Dataset
1981
- The xarray Dataset to check.
1982
- dim_names: Dict[str, str], optional
1983
- Dictionary specifying the names of dimensions in the dataset.
1984
- var_names: Dict[str, str]
1985
- Dictionary of variable names that are required in the dataset.
1986
- opt_var_names : Optional[Dict[str, str]], optional
1987
- Dictionary of optional variable names.
1988
- These variables are not strictly required, and the function will not raise an error if they are missing.
1989
- Default is None, meaning no optional variables are considered.
1686
+ boundaries : dict[str, bool] or None
1687
+ User-supplied dictionary controlling which boundaries are active.
1688
+ Keys may include any subset of {"south", "east", "north", "west"}.
1689
+ Missing keys will be filled from mask-based defaults.
1690
+ If None, all boundaries are inferred from the land mask.
1990
1691
 
1692
+ mask : xr.DataArray
1693
+ 2D land/sea mask on rho-points. Used to determine which boundaries
1694
+ contain at least one ocean point.
1991
1695
 
1992
- Raises
1993
- ------
1994
- ValueError
1995
- If the dataset does not contain the specified variables or dimensions.
1696
+ Returns
1697
+ -------
1698
+ dict[str, bool]
1699
+ Completed and validated boundary configuration.
1996
1700
  """
1997
- missing_dims = [dim for dim in dim_names.values() if dim not in ds.dims]
1998
- if missing_dims:
1999
- raise ValueError(
2000
- f"Dataset does not contain all required dimensions. The following dimensions are missing: {missing_dims}"
1701
+ valid_keys = {"south", "east", "north", "west"}
1702
+
1703
+ # --------------------------------------------
1704
+ # Case 1: boundaries not provided infer them
1705
+ # --------------------------------------------
1706
+ if boundaries is None:
1707
+ inferred = _infer_valid_boundaries_from_mask(mask)
1708
+ logging.info(f"No `boundaries` provided. Using mask-based defaults: {inferred}")
1709
+ return inferred
1710
+
1711
+ # --------------------------------------------
1712
+ # Case 2: boundaries provided → validate
1713
+ # --------------------------------------------
1714
+ if not isinstance(boundaries, dict):
1715
+ raise TypeError(
1716
+ "`boundaries` must be a dict mapping boundary names to booleans."
2001
1717
  )
2002
1718
 
2003
- missing_vars = [var for var in var_names.values() if var not in ds.data_vars]
2004
- if missing_vars:
1719
+ # Unknown keys?
1720
+ unknown_keys = set(boundaries) - valid_keys
1721
+ if unknown_keys:
2005
1722
  raise ValueError(
2006
- f"Dataset does not contain all required variables. The following variables are missing: {missing_vars}"
1723
+ f"`boundaries` contains invalid keys: {unknown_keys}. "
1724
+ "Allowed keys are: 'south', 'east', 'north', 'west'."
2007
1725
  )
2008
1726
 
2009
- if opt_var_names:
2010
- missing_optional_vars = [
2011
- var for var in opt_var_names.values() if var not in ds.data_vars
2012
- ]
2013
- if missing_optional_vars:
2014
- logging.warning(
2015
- f"Optional variables missing (but not critical): {missing_optional_vars}"
1727
+ # Type-check provided values
1728
+ for key, val in boundaries.items():
1729
+ if not isinstance(val, bool):
1730
+ raise TypeError(f"Boundary '{key}' must be a boolean.")
1731
+
1732
+ # Fill missing boundaries using defaults
1733
+ inferred_defaults = _infer_valid_boundaries_from_mask(mask)
1734
+ completed = boundaries.copy()
1735
+
1736
+ for key in valid_keys:
1737
+ if key not in completed:
1738
+ completed[key] = inferred_defaults[key]
1739
+ logging.info(
1740
+ f"`boundaries[{key!r}]` not provided — defaulting to "
1741
+ f"{inferred_defaults[key]}"
2016
1742
  )
2017
1743
 
1744
+ logging.info(f"Using boundary configuration: {completed}")
1745
+ return completed
2018
1746
 
2019
- def select_relevant_times(
2020
- ds: xr.Dataset,
2021
- time_dim: str,
2022
- start_time: datetime,
2023
- end_time: datetime | None = None,
2024
- climatology: bool = False,
2025
- allow_flex_time: bool = False,
2026
- ) -> xr.Dataset:
2027
- """
2028
- Select a subset of the dataset based on time constraints.
2029
-
2030
- This function supports two main use cases:
2031
-
2032
- 1. **Time range selection (start_time + end_time provided):**
2033
- - Returns all records strictly between `start_time` and `end_time`.
2034
- - Ensures at least one record at or before `start_time` and one record at or
2035
- after `end_time` are included, even if they fall outside the strict range.
2036
-
2037
- 2. **Initial condition selection (start_time provided, end_time=None):**
2038
- - Delegates to `_select_initial_time`, which reduces the dataset to exactly one
2039
- time entry.
2040
- - If `allow_flex_time=True`, a +24-hour buffer around `start_time` is allowed,
2041
- and the closest timestamp is chosen.
2042
- - If `allow_flex_time=False`, requires an exact timestamp match.
2043
-
2044
- Additional behavior:
2045
- - If `climatology=True`, the dataset must contain exactly 12 time steps. If valid,
2046
- the climatology dataset is returned without further filtering.
2047
- - If the dataset uses `cftime` datetime objects, these are converted to
2048
- `np.datetime64` before filtering.
2049
-
2050
- Parameters
2051
- ----------
2052
- ds : xr.Dataset
2053
- The dataset to filter. Must contain a valid time dimension.
2054
- time_dim : str
2055
- Name of the time dimension in `ds`.
2056
- start_time : datetime
2057
- Start time for filtering.
2058
- end_time : datetime or None
2059
- End time for filtering. If `None`, the function assumes an initial condition
2060
- use case and selects exactly one timestamp.
2061
- climatology : bool, optional
2062
- If True, requires exactly 12 time steps and bypasses normal filtering.
2063
- Defaults to False.
2064
- allow_flex_time : bool, optional
2065
- Whether to allow a +24h search window after `start_time` when `end_time`
2066
- is None. If False (default), requires an exact match.
2067
1747
 
2068
- Returns
2069
- -------
2070
- xr.Dataset
2071
- A filtered dataset containing only the selected time entries.
2072
-
2073
- Raises
2074
- ------
2075
- ValueError
2076
- - If `climatology=True` but the dataset does not contain exactly 12 time steps.
2077
- - If `climatology=False` and the dataset contains integer time values.
2078
- - If no valid records are found within the requested range or window.
2079
-
2080
- Warns
2081
- -----
2082
- UserWarning
2083
- - If no records exist at or before `start_time` or at or after `end_time`.
2084
- - If the specified time dimension does not exist in the dataset.
2085
-
2086
- Notes
2087
- -----
2088
- - For initial conditions (end_time=None), see `_select_initial_time` for details
2089
- on strict vs. flexible selection behavior.
2090
- - Logs warnings instead of failing hard when boundary records are missing, and
2091
- defaults to using the earliest or latest available time in such cases.
1748
+ def _infer_valid_boundaries_from_mask(mask: xr.DataArray) -> dict[str, bool]:
2092
1749
  """
2093
- if time_dim not in ds.variables:
2094
- logging.warning(
2095
- f"Dataset does not contain time dimension '{time_dim}'. "
2096
- "Please check variable naming or dataset structure."
2097
- )
2098
- return ds
2099
-
2100
- time_type = get_time_type(ds[time_dim])
2101
-
2102
- if climatology:
2103
- if len(ds[time_dim]) != 12:
2104
- raise ValueError(
2105
- f"The dataset contains {len(ds[time_dim])} time steps, but the climatology flag is set to True, which requires exactly 12 time steps."
2106
- )
2107
- else:
2108
- if time_type == "int":
2109
- raise ValueError(
2110
- "The dataset contains integer time values, which are only supported when the climatology flag is set to True. However, your climatology flag is set to False."
2111
- )
2112
- if time_type == "cftime":
2113
- ds = ds.assign_coords({time_dim: convert_cftime_to_datetime(ds[time_dim])})
2114
-
2115
- if not end_time:
2116
- # Assume we are looking for exactly one time record for initial conditions
2117
- return _select_initial_time(
2118
- ds, time_dim, start_time, climatology, allow_flex_time
2119
- )
2120
-
2121
- if climatology:
2122
- return ds
2123
-
2124
- # Identify records before or at start_time
2125
- before_start = ds[time_dim] <= np.datetime64(start_time)
2126
- if before_start.any():
2127
- closest_before_start = ds[time_dim].where(before_start, drop=True)[-1]
2128
- else:
2129
- logging.warning(f"No records found at or before the start_time: {start_time}.")
2130
- closest_before_start = ds[time_dim][0]
2131
-
2132
- # Identify records after or at end_time
2133
- after_end = ds[time_dim] >= np.datetime64(end_time)
2134
- if after_end.any():
2135
- closest_after_end = ds[time_dim].where(after_end, drop=True).min()
2136
- else:
2137
- logging.warning(f"No records found at or after the end_time: {end_time}.")
2138
- closest_after_end = ds[time_dim].max()
2139
-
2140
- # Select records within the time range and add the closest before/after
2141
- within_range = (ds[time_dim] > np.datetime64(start_time)) & (
2142
- ds[time_dim] < np.datetime64(end_time)
2143
- )
2144
- selected_times = ds[time_dim].where(
2145
- within_range
2146
- | (ds[time_dim] == closest_before_start)
2147
- | (ds[time_dim] == closest_after_end),
2148
- drop=True,
2149
- )
2150
- ds = ds.sel({time_dim: selected_times})
2151
-
2152
- return ds
1750
+ Determine which grid boundaries contain at least one ocean point.
2153
1751
 
2154
-
2155
- def _select_initial_time(
2156
- ds: xr.Dataset,
2157
- time_dim: str,
2158
- ini_time: datetime,
2159
- climatology: bool,
2160
- allow_flex_time: bool = False,
2161
- ) -> xr.Dataset:
2162
- """Select exactly one initial time from dataset.
1752
+ Any boundary consisting entirely of land is considered inactive.
2163
1753
 
2164
1754
  Parameters
2165
1755
  ----------
2166
- ds : xr.Dataset
2167
- The input dataset with a time dimension.
2168
- time_dim : str
2169
- Name of the time dimension.
2170
- ini_time : datetime
2171
- The desired initial time.
2172
- allow_flex_time : bool
2173
- - If True: allow a +24h window and pick the closest available timestamp.
2174
- - If False (default): require an exact match, otherwise raise ValueError.
1756
+ mask : xr.DataArray
1757
+ 2D mask array on rho-points where 1 = ocean, 0 = land.
2175
1758
 
2176
1759
  Returns
2177
1760
  -------
2178
- xr.Dataset
2179
- Dataset reduced to exactly one timestamp.
2180
-
2181
- Raises
2182
- ------
2183
- ValueError
2184
- If no matching time is found (when `allow_flex_time=False`), or no entries are
2185
- available within the +24h window (when `allow_flex_time=True`).
1761
+ dict[str, bool]
1762
+ Boolean availability for {south, east, north, west}.
2186
1763
  """
2187
- if climatology:
2188
- # Convert from timedelta64[ns] to fractional days
2189
- ds["time"] = ds["time"] / np.timedelta64(1, "D")
2190
- # Interpolate from climatology for initial conditions
2191
- return interpolate_from_climatology(ds, time_dim, ini_time)
2192
-
2193
- if allow_flex_time:
2194
- # Look in time range [ini_time, ini_time + 24h)
2195
- end_time = ini_time + timedelta(days=1)
2196
- times = (np.datetime64(ini_time) <= ds[time_dim]) & (
2197
- ds[time_dim] < np.datetime64(end_time)
2198
- )
2199
-
2200
- if np.all(~times):
2201
- raise ValueError(
2202
- f"No time entries found between {ini_time} and {end_time}."
2203
- )
1764
+ bdry_coords = get_boundary_coords()
1765
+ boundaries = {}
2204
1766
 
2205
- ds = ds.where(times, drop=True)
2206
- if ds.sizes[time_dim] > 1:
2207
- # Pick the time closest to start_time
2208
- ds = ds.isel({time_dim: 0})
1767
+ for direction in ["south", "east", "north", "west"]:
1768
+ coords = bdry_coords["rho"][direction]
1769
+ bdry_mask = mask.isel(**coords)
2209
1770
 
2210
- logging.warning(
2211
- f"Selected time entry closest to the specified start_time in +24 hour range: {ds[time_dim].values}"
2212
- )
1771
+ # Boundary is valid if ANY ocean point exists
1772
+ boundaries[direction] = bool(bdry_mask.values.any())
2213
1773
 
2214
- else:
2215
- # Strict match required
2216
- if not (ds[time_dim].values == np.datetime64(ini_time)).any():
2217
- raise ValueError(
2218
- f"No exact match found for initial time {ini_time}. Consider setting allow_flex_time to True."
2219
- )
2220
-
2221
- ds = ds.sel({time_dim: np.datetime64(ini_time)})
2222
-
2223
- if time_dim not in ds.dims:
2224
- ds = ds.expand_dims(time_dim)
2225
-
2226
- return ds
1774
+ return boundaries
@@ -86,18 +86,16 @@ def test_extract_efficiency(create_member_ds: xr.Dataset) -> None:
86
86
  assert eff_rel.time.attrs.get("long_name") == "time since release start"
87
87
 
88
88
 
89
- def test_extract_efficiency_missing_abs_time() -> None:
90
- """Test that _extract_efficiency raises an error if 'abs_time' is missing."""
89
+ def test_extract_efficiency_missing_time() -> None:
90
+ """Test that _extract_efficiency raises an error if 'time' is missing."""
91
91
  times = np.array(["2000-01-01", "2000-01-02"], dtype="datetime64[ns]")
92
92
  ds = xr.Dataset(
93
93
  {"cdr_efficiency": ("time", [0.1, 0.2])},
94
- coords={"time": times}, # Note: no 'abs_time' coordinate
94
+ coords={"abs_time": times}, # Note: no 'time' coordinate
95
95
  )
96
96
 
97
97
  ens = Ensemble.__new__(Ensemble)
98
- with pytest.raises(
99
- ValueError, match="Dataset must contain an 'abs_time' coordinate."
100
- ):
98
+ with pytest.raises(ValueError, match="Dataset must contain a 'time' coordinate."):
101
99
  ens._extract_efficiency(ds)
102
100
 
103
101