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
@@ -5,6 +5,8 @@ from datetime import datetime
5
5
  from enum import StrEnum, auto
6
6
  from typing import Annotated, Literal
7
7
 
8
+ import numpy as np
9
+ import pandas as pd
8
10
  from annotated_types import Ge, Le
9
11
  from pydantic import (
10
12
  BaseModel,
@@ -16,10 +18,17 @@ from pydantic import (
16
18
  )
17
19
  from pydantic_core.core_schema import ValidationInfo
18
20
 
19
- from roms_tools.setup.utils import get_tracer_defaults, get_tracer_metadata_dict
21
+ from roms_tools.setup.utils import (
22
+ convert_to_relative_days,
23
+ get_tracer_defaults,
24
+ get_tracer_metadata_dict,
25
+ )
20
26
 
21
27
  NonNegativeFloat = Annotated[float, Ge(0)]
22
28
 
29
+ # Show all columns when printing a DataFrame
30
+ pd.set_option("display.max_columns", None)
31
+
23
32
 
24
33
  @dataclass
25
34
  class ValueArray(ABC):
@@ -272,10 +281,68 @@ class Release(BaseModel):
272
281
  if self.times[-1] < end_time:
273
282
  self.times.append(end_time)
274
283
 
275
- @staticmethod
276
- def get_tracer_metadata():
284
+ @classmethod
285
+ def get_tracer_metadata(cls):
277
286
  return {}
278
287
 
288
+ @classmethod
289
+ def get_metadata(cls):
290
+ return pd.DataFrame(cls.get_tracer_metadata())
291
+
292
+ def _compute_integrated_tracers(
293
+ self,
294
+ roms_time_stamps: np.ndarray,
295
+ model_reference_date: datetime,
296
+ tracer_series_dict: dict[str, np.ndarray],
297
+ ) -> dict[str, float]:
298
+ """
299
+ Compute time-integrated tracer quantities over ROMS time steps using a left-hold rule.
300
+
301
+ This method performs a left-hold (stepwise constant) integration of tracer fluxes
302
+ over the intervals defined by the ROMS time stamps. It first interpolates the
303
+ tracer time series from the release schedule onto the ROMS time stamps, then
304
+ multiplies the value at the start of each interval by the duration of that interval.
305
+
306
+ Parameters
307
+ ----------
308
+ roms_time_stamps : np.ndarray
309
+ 1D array of ROMS model time stamps in seconds since `model_reference_date`.
310
+ Must be strictly increasing and contain at least two entries.
311
+ model_reference_date : datetime
312
+ Reference datetime of the ROMS model calendar, used to compute relative times
313
+ for interpolation.
314
+ tracer_series_dict : dict[str, np.ndarray]
315
+ Dictionary mapping tracer names to 1D arrays of tracer flux values at the
316
+ release schedule times (`self.times`). Each array must have the same length
317
+ as `self.times`.
318
+
319
+ Returns
320
+ -------
321
+ dict[str, float]
322
+ Dictionary mapping each tracer name to its integrated quantity over the
323
+ ROMS time period. Integration is performed using the left-hold rule,
324
+ ignoring the last release point because it defines the end of the final interval.
325
+
326
+ Raises
327
+ ------
328
+ ValueError
329
+ If `roms_time_stamps` has fewer than two entries, since at least one interval
330
+ is required for integration.
331
+ """
332
+ if len(roms_time_stamps) < 2:
333
+ raise ValueError("Need at least two ROMS time stamps to define intervals.")
334
+
335
+ dt = np.diff(roms_time_stamps)
336
+ results = {}
337
+ for tracer, series in tracer_series_dict.items():
338
+ interp_values = np.interp(
339
+ roms_time_stamps,
340
+ convert_to_relative_days(self.times, model_reference_date) * 3600 * 24,
341
+ series,
342
+ )
343
+ results[tracer] = np.sum(interp_values[:-1] * dt)
344
+ return results
345
+
279
346
 
280
347
  class VolumeRelease(Release):
281
348
  """Represents a CDR release with volume flux and tracer concentrations.
@@ -389,9 +456,11 @@ class VolumeRelease(Release):
389
456
  num_times = len(self.times)
390
457
 
391
458
  for tracer_concentrations in self.tracer_concentrations.values():
392
- tracer_concentrations.check_length(num_times)
459
+ if isinstance(tracer_concentrations, Concentration):
460
+ tracer_concentrations.check_length(num_times)
393
461
 
394
- self.volume_fluxes.check_length(num_times)
462
+ if isinstance(self.volume_fluxes, Flux):
463
+ self.volume_fluxes.check_length(num_times)
395
464
 
396
465
  return self
397
466
 
@@ -410,7 +479,52 @@ class VolumeRelease(Release):
410
479
  @staticmethod
411
480
  def get_tracer_metadata():
412
481
  """Returns long names and expected units for the tracer concentrations."""
413
- return get_tracer_metadata_dict(include_bgc=True, with_flux_units=False)
482
+ return get_tracer_metadata_dict(include_bgc=True, unit_type="concentration")
483
+
484
+ def _do_accounting(
485
+ self,
486
+ roms_time_stamps: np.ndarray,
487
+ model_reference_date: datetime,
488
+ ) -> dict[str, float]:
489
+ """
490
+ Compute time-integrated tracer quantities over ROMS time steps.
491
+
492
+ This method interpolates tracer flux time series from the CDR schedule
493
+ onto the provided ROMS time stamps (in seconds since model reference date),
494
+ then applies a "left-hold" rule: the interpolated value at t₀ is applied
495
+ across the full interval [t₀, t₁).
496
+
497
+ Parameters
498
+ ----------
499
+ roms_time_stamps : np.ndarray
500
+ 1D array of ROMS time stamps (seconds since `model_reference_date`).
501
+ Must be strictly increasing.
502
+ model_reference_date : datetime
503
+ Reference date of the ROMS model calendar.
504
+
505
+ Returns
506
+ -------
507
+ dict[str, float]
508
+ Dictionary mapping tracer names to the total integrated quantity over
509
+ the entire ROMS time period. Each value is the sum of the interpolated
510
+ tracer fluxes multiplied by the corresponding ROMS time step durations.
511
+ """
512
+ tracer_series_dict = {}
513
+ volume_array = (
514
+ np.asarray(self.volume_fluxes.values)
515
+ if isinstance(self.volume_fluxes, Flux)
516
+ else np.asarray(self.volume_fluxes)
517
+ )
518
+ for tracer, conc in self.tracer_concentrations.items():
519
+ tracer_array = (
520
+ np.asarray(conc.values)
521
+ if isinstance(conc, Concentration)
522
+ else np.asarray(conc)
523
+ )
524
+ tracer_series_dict[tracer] = volume_array * tracer_array
525
+ return self._compute_integrated_tracers(
526
+ roms_time_stamps, model_reference_date, tracer_series_dict
527
+ )
414
528
 
415
529
  @model_serializer(mode="wrap")
416
530
  def _simplified_dump(self, pydantic_serializer) -> dict:
@@ -503,7 +617,8 @@ class TracerPerturbation(Release):
503
617
  def _check_tracer_flux_lengths(self):
504
618
  num_times = len(self.times)
505
619
  for flux in self.tracer_fluxes.values():
506
- flux.check_length(num_times)
620
+ if isinstance(flux, Flux):
621
+ flux.check_length(num_times)
507
622
  return self
508
623
 
509
624
  def _extend_to_endpoints(self, start_time, end_time):
@@ -520,7 +635,45 @@ class TracerPerturbation(Release):
520
635
  @staticmethod
521
636
  def get_tracer_metadata():
522
637
  """Returns long names and expected units for the tracer fluxes."""
523
- return get_tracer_metadata_dict(include_bgc=True, with_flux_units=True)
638
+ return get_tracer_metadata_dict(include_bgc=True, unit_type="flux")
639
+
640
+ def _do_accounting(
641
+ self,
642
+ roms_time_stamps: np.ndarray,
643
+ model_reference_date: datetime,
644
+ ) -> dict[str, float]:
645
+ """
646
+ Compute time-integrated tracer quantities over ROMS time steps.
647
+
648
+ This method interpolates tracer flux time series from the CDR schedule
649
+ onto the provided ROMS time stamps (in days since model reference date),
650
+ then applies a "left-hold" rule: the interpolated value at t₀ is applied
651
+ across the full interval [t₀, t₁).
652
+
653
+ Parameters
654
+ ----------
655
+ roms_time_stamps : np.ndarray
656
+ 1D array of ROMS time stamps (days since `model_reference_date`).
657
+ Must be strictly increasing.
658
+ model_reference_date : datetime
659
+ Reference date of the ROMS model calendar.
660
+
661
+ Returns
662
+ -------
663
+ dict[str, float]
664
+ Dictionary mapping tracer names to the total integrated quantity over
665
+ the entire ROMS time period. Each value is the sum of the interpolated
666
+ tracer fluxes multiplied by the corresponding ROMS time step durations.
667
+ """
668
+ tracer_series_dict = {
669
+ tracer: np.asarray(flux.values)
670
+ if isinstance(flux, Flux)
671
+ else np.asarray(flux)
672
+ for tracer, flux in self.tracer_fluxes.items()
673
+ }
674
+ return self._compute_integrated_tracers(
675
+ roms_time_stamps, model_reference_date, tracer_series_dict
676
+ )
524
677
 
525
678
  @model_serializer(mode="wrap")
526
679
  def _simplified_dump(self, pydantic_serializer) -> dict:
roms_tools/setup/grid.py CHANGED
@@ -1,9 +1,9 @@
1
1
  import importlib.metadata
2
2
  import logging
3
3
  import re
4
- import time
5
4
  from dataclasses import asdict, dataclass, field
6
5
  from pathlib import Path
6
+ from typing import Any
7
7
 
8
8
  import numpy as np
9
9
  import xarray as xr
@@ -12,9 +12,10 @@ from matplotlib.axes import Axes
12
12
 
13
13
  from roms_tools.constants import MAXIMUM_GRID_SIZE, R_EARTH
14
14
  from roms_tools.plot import plot
15
- from roms_tools.setup.mask import _add_mask, _add_velocity_masks
16
- from roms_tools.setup.topography import _add_topography
15
+ from roms_tools.setup.mask import add_mask, add_velocity_masks
16
+ from roms_tools.setup.topography import add_topography
17
17
  from roms_tools.setup.utils import (
18
+ Timed,
18
19
  extract_single_value,
19
20
  gc_dist,
20
21
  get_target_coords,
@@ -64,6 +65,8 @@ class Grid:
64
65
  - "path" (Union[str, Path, List[Union[str, Path]]]): The path to the raw data file. Can be a string or a Path object.
65
66
 
66
67
  The default is "ETOPO5", which does not require a path.
68
+ mask_shapefile: str | Path | None, optional
69
+ Path to a custom shapefile to use to determine the land mask; if None, use NaturalEarth 10m.
67
70
  hmin : float, optional
68
71
  The minimum ocean depth (in meters). The default is 5.0.
69
72
  N : int, optional
@@ -106,8 +109,10 @@ class Grid:
106
109
  """The bottom control parameter."""
107
110
  hc: float = 300.0
108
111
  """The critical depth (in meters)."""
109
- topography_source: dict[str, str | Path | list[str | Path]] = None
112
+ topography_source: dict[str, str | Path | list[str | Path]] | None = None
110
113
  """Dictionary specifying the source of the topography data."""
114
+ mask_shapefile: str | Path | None = None
115
+ """Path to a custom shapefile to use to determine the landmask; if None, use NaturalEarth 10m."""
111
116
  hmin: float = 5.0
112
117
  """The minimum ocean depth (in meters)."""
113
118
  verbose: bool = False
@@ -129,7 +134,7 @@ class Grid:
129
134
  self._straddle()
130
135
 
131
136
  # Mask
132
- self._create_mask(verbose=self.verbose)
137
+ self.update_mask(mask_shapefile=self.mask_shapefile, verbose=self.verbose)
133
138
 
134
139
  # Coarsen the dataset if needed
135
140
  self._coarsen()
@@ -165,28 +170,50 @@ class Grid:
165
170
  "`topography_source` must include a 'path' key when the 'name' is not 'ETOPO5'."
166
171
  )
167
172
 
168
- def _create_mask(self, verbose=False) -> None:
169
- if verbose:
170
- start_time = time.time()
171
- logging.info("=== Creating the mask ===")
172
- ds = _add_mask(self.ds)
173
+ def update_mask(
174
+ self, mask_shapefile: str | Path | None = None, verbose: bool = False
175
+ ) -> None:
176
+ """
177
+ Update the land mask of the current grid dataset.
173
178
 
174
- if verbose:
175
- logging.info(f"Total time: {time.time() - start_time:.3f} seconds")
176
- logging.info(
177
- "========================================================================================================"
178
- )
179
+ This method generates a land mask based on the provided coastline
180
+ shapefile, fills enclosed basins with lands and updates the dataset
181
+ stored in `self.ds`. If no shapefile is provided, a default dataset (Natural
182
+ Earth 10m) is used. The operation is optionally timed and logged.
179
183
 
180
- self.ds = ds
184
+ Parameters
185
+ ----------
186
+ mask_shapefile : str or Path, optional
187
+ Path to a coastal shapefile to derive the land mask. If `None`,
188
+ the default Natural Earth 10m coastline dataset is used.
189
+ verbose : bool, default False
190
+ If True, prints timing and progress information.
191
+
192
+ Returns
193
+ -------
194
+ None
195
+ Updates the `self.ds` attribute in place with the new mask.
196
+
197
+ """
198
+ with Timed("=== Deriving the mask from coastlines ===", verbose=verbose):
199
+ ds = add_mask(self.ds, shapefile=mask_shapefile)
200
+ self.ds = ds
201
+ self.mask_shapefile = mask_shapefile
181
202
 
182
203
  def update_topography(
183
- self, topography_source=None, hmin=None, verbose=False
204
+ self,
205
+ topography_source: dict | None = None,
206
+ hmin: float | None = None,
207
+ verbose: bool = False,
184
208
  ) -> None:
185
209
  """Update the grid dataset with processed topography.
186
210
 
187
- This method performs several key operations, including regridding the topography, smoothing the
188
- topography over the entire domain and locally.
189
- The processed topography is then added to the grid's dataset.
211
+ This method performs the following operations:
212
+
213
+ 1. Regrids the topography from the specified source.
214
+ 2. Applies domain-wide and local smoothing.
215
+ 3. Enforces the minimum depth constraint ``hmin``.
216
+ 4. Updates the internal dataset (``self.ds``) with the processed bathymetry.
190
217
 
191
218
  Parameters
192
219
  ----------
@@ -215,36 +242,27 @@ class Grid:
215
242
  topography_source = topography_source or self.topography_source
216
243
  hmin = hmin or self.hmin
217
244
 
245
+ name = topography_source["name"] # type: ignore[index]
246
+
218
247
  # Extract target coordinates for processing
219
248
  target_coords = get_target_coords(self)
220
249
 
221
- # If verbose is enabled, start the timer and print the start message
222
- if verbose:
223
- start_time = time.time()
224
- logging.info(
225
- f"=== Generating the topography using {topography_source['name']} data and hmin = {hmin} meters ==="
226
- )
227
-
228
- # Add topography to the dataset
229
- ds = _add_topography(
230
- ds=self.ds,
231
- target_coords=target_coords,
232
- topography_source=topography_source,
233
- hmin=hmin,
250
+ with Timed(
251
+ f"=== Generating the topography using {name} data and hmin = {hmin} meters ===",
234
252
  verbose=verbose,
235
- )
236
-
237
- # If verbose is enabled, print elapsed time and a separator
238
- if verbose:
239
- logging.info(f"Total time: {time.time() - start_time:.3f} seconds")
240
- logging.info(
241
- "========================================================================================================"
253
+ ):
254
+ ds = add_topography(
255
+ ds=self.ds,
256
+ target_coords=target_coords,
257
+ topography_source=topography_source,
258
+ hmin=hmin,
259
+ verbose=verbose,
242
260
  )
243
261
 
244
- # Update the grid's dataset and related attributes
245
- self.ds = ds
246
- self.topography_source = topography_source
247
- self.hmin = hmin
262
+ # Update the grid's dataset and related attributes
263
+ self.ds = ds
264
+ self.topography_source = topography_source
265
+ self.hmin = hmin
248
266
 
249
267
  def update_vertical_coordinate(
250
268
  self, N=None, theta_s=None, theta_b=None, hc=None, verbose=False
@@ -281,69 +299,61 @@ class Grid:
281
299
  theta_b = theta_b or self.theta_b
282
300
  hc = hc or self.hc
283
301
 
284
- if verbose:
285
- start_time = time.time()
286
- logging.info(
287
- f"=== Preparing the vertical coordinate system using N = {N}, theta_s = {theta_s}, theta_b = {theta_b}, hc = {hc} ==="
288
- )
302
+ with Timed(
303
+ f"=== Preparing the vertical coordinate system using N = {N}, theta_s = {theta_s}, theta_b = {theta_b}, hc = {hc} ===",
304
+ verbose=verbose,
305
+ ):
306
+ ds = self.ds
307
+ # need to drop vertical coordinates because they could cause conflict if N changed
308
+ vars_to_drop = [
309
+ "layer_depth_rho",
310
+ "layer_depth_u",
311
+ "layer_depth_v",
312
+ "interface_depth_rho",
313
+ "interface_depth_u",
314
+ "interface_depth_v",
315
+ "sigma_r",
316
+ "sigma_w",
317
+ "Cs_w",
318
+ "Cs_r",
319
+ ]
289
320
 
290
- ds = self.ds
291
- # need to drop vertical coordinates because they could cause conflict if N changed
292
- vars_to_drop = [
293
- "layer_depth_rho",
294
- "layer_depth_u",
295
- "layer_depth_v",
296
- "interface_depth_rho",
297
- "interface_depth_u",
298
- "interface_depth_v",
299
- "sigma_r",
300
- "sigma_w",
301
- "Cs_w",
302
- "Cs_r",
303
- ]
304
-
305
- for var in vars_to_drop:
306
- if var in ds.variables:
307
- ds = ds.drop_vars(var)
308
-
309
- cs_r, sigma_r = sigma_stretch(theta_s, theta_b, N, "r")
310
- cs_w, sigma_w = sigma_stretch(theta_s, theta_b, N, "w")
311
-
312
- ds["sigma_r"] = sigma_r.astype(np.float32)
313
- ds["sigma_r"].attrs["long_name"] = (
314
- "Fractional vertical stretching coordinate at rho-points"
315
- )
316
- ds["sigma_r"].attrs["units"] = "nondimensional"
321
+ for var in vars_to_drop:
322
+ if var in ds.variables:
323
+ ds = ds.drop_vars(var)
317
324
 
318
- ds["Cs_r"] = cs_r.astype(np.float32)
319
- ds["Cs_r"].attrs["long_name"] = "Vertical stretching function at rho-points"
320
- ds["Cs_r"].attrs["units"] = "nondimensional"
325
+ cs_r, sigma_r = sigma_stretch(theta_s, theta_b, N, "r")
326
+ cs_w, sigma_w = sigma_stretch(theta_s, theta_b, N, "w")
321
327
 
322
- ds["sigma_w"] = sigma_w.astype(np.float32)
323
- ds["sigma_w"].attrs["long_name"] = (
324
- "Fractional vertical stretching coordinate at w-points"
325
- )
326
- ds["sigma_w"].attrs["units"] = "nondimensional"
327
-
328
- ds["Cs_w"] = cs_w.astype(np.float32)
329
- ds["Cs_w"].attrs["long_name"] = "Vertical stretching function at w-points"
330
- ds["Cs_w"].attrs["units"] = "nondimensional"
328
+ ds["sigma_r"] = sigma_r.astype(np.float32)
329
+ ds["sigma_r"].attrs["long_name"] = (
330
+ "Fractional vertical stretching coordinate at rho-points"
331
+ )
332
+ ds["sigma_r"].attrs["units"] = "nondimensional"
331
333
 
332
- ds.attrs["theta_s"] = np.float32(theta_s)
333
- ds.attrs["theta_b"] = np.float32(theta_b)
334
- ds.attrs["hc"] = np.float32(hc)
334
+ ds["Cs_r"] = cs_r.astype(np.float32)
335
+ ds["Cs_r"].attrs["long_name"] = "Vertical stretching function at rho-points"
336
+ ds["Cs_r"].attrs["units"] = "nondimensional"
335
337
 
336
- if verbose:
337
- logging.info(f"Total time: {time.time() - start_time:.3f} seconds")
338
- logging.info(
339
- "========================================================================================================"
338
+ ds["sigma_w"] = sigma_w.astype(np.float32)
339
+ ds["sigma_w"].attrs["long_name"] = (
340
+ "Fractional vertical stretching coordinate at w-points"
340
341
  )
342
+ ds["sigma_w"].attrs["units"] = "nondimensional"
341
343
 
342
- self.ds = ds
343
- self.theta_s = theta_s
344
- self.theta_b = theta_b
345
- self.hc = hc
346
- self.N = N
344
+ ds["Cs_w"] = cs_w.astype(np.float32)
345
+ ds["Cs_w"].attrs["long_name"] = "Vertical stretching function at w-points"
346
+ ds["Cs_w"].attrs["units"] = "nondimensional"
347
+
348
+ ds.attrs["theta_s"] = np.float32(theta_s)
349
+ ds.attrs["theta_b"] = np.float32(theta_b)
350
+ ds.attrs["hc"] = np.float32(hc)
351
+
352
+ self.ds = ds
353
+ self.theta_s = theta_s
354
+ self.theta_b = theta_b
355
+ self.hc = hc
356
+ self.N = N
347
357
 
348
358
  def _straddle(self) -> None:
349
359
  """Check if the Greenwich meridian goes through the domain.
@@ -603,7 +613,7 @@ class Grid:
603
613
  ds = xr.open_dataset(filepath)
604
614
 
605
615
  if not all(mask in ds for mask in ["mask_u", "mask_v"]):
606
- ds = _add_velocity_masks(ds)
616
+ ds = add_velocity_masks(ds)
607
617
 
608
618
  # Create a new Grid instance without calling __init__ and __post_init__
609
619
  grid = cls.__new__(cls)
@@ -758,24 +768,30 @@ class Grid:
758
768
  "hmin",
759
769
  ]:
760
770
  if attr in ds.attrs:
761
- a = float(ds.attrs[attr])
771
+ value = float(ds.attrs[attr])
762
772
  else:
763
- a = None
773
+ value = None
764
774
 
765
- object.__setattr__(grid, attr, a)
775
+ object.__setattr__(grid, attr, value)
766
776
 
767
777
  if "topography_source_name" in ds.attrs:
768
778
  if "topography_source_path" in ds.attrs:
769
- a = {
779
+ topo_source = {
770
780
  "name": ds.attrs["topography_source_name"],
771
781
  "path": ds.attrs["topography_source_path"],
772
782
  }
773
783
  else:
774
- a = {"name": ds.attrs["topography_source_name"]}
784
+ topo_source = {"name": ds.attrs["topography_source_name"]}
775
785
  else:
776
- a = None
786
+ topo_source = None
787
+ grid.topography_source = topo_source
777
788
 
778
- object.__setattr__(grid, "topography_source", a)
789
+ if "mask_shapefile" in ds.attrs:
790
+ mask_shapefile = ds.attrs["mask_shapefile"]
791
+ else:
792
+ mask_shapefile = None
793
+
794
+ grid.mask_shapefile = mask_shapefile
779
795
 
780
796
  return grid
781
797
 
@@ -796,10 +812,7 @@ class Grid:
796
812
 
797
813
  @classmethod
798
814
  def from_yaml(
799
- cls,
800
- filepath: str | Path,
801
- section_name: str = "Grid",
802
- verbose: bool = False,
815
+ cls, filepath: str | Path, verbose: bool = False, **kwargs: Any
803
816
  ) -> "Grid":
804
817
  """Create an instance of the class from a YAML file.
805
818
 
@@ -807,10 +820,13 @@ class Grid:
807
820
  ----------
808
821
  filepath : Union[str, Path]
809
822
  The path to the YAML file from which the parameters will be read.
810
- section_name : str, optional
811
- The name of the YAML section containing the grid configuration. Defaults to "Grid".
812
823
  verbose : bool, optional
813
824
  Indicates whether to print grid generation steps with timing. Defaults to False.
825
+ **kwargs : Any
826
+ Additional keyword arguments:
827
+
828
+ - section_name : str, optional (default: "Grid")
829
+ The name of the YAML section containing the grid configuration.
814
830
 
815
831
  Returns
816
832
  -------
@@ -828,6 +844,8 @@ class Grid:
828
844
  Issues a warning if the ROMS-Tools version in the YAML header does not match the
829
845
  currently installed version.
830
846
  """
847
+ section_name: str = kwargs.pop("section_name", None) or "Grid"
848
+
831
849
  filepath = Path(filepath)
832
850
  # Read the entire file content
833
851
  with filepath.open("r") as file:
@@ -881,7 +899,7 @@ class Grid:
881
899
  attr_str = ", ".join(f"{k}={v!r}" for k, v in attr_dict.items())
882
900
  return f"{cls_name}({attr_str})"
883
901
 
884
- def _create_horizontal_grid(self) -> xr.Dataset():
902
+ def _create_horizontal_grid(self) -> xr.Dataset:
885
903
  """Create the horizontal grid based on a Mercator projection and store it in the
886
904
  'ds' attribute.
887
905
 
@@ -899,41 +917,32 @@ class Grid:
899
917
  - Longitude values are adjusted to fall within the range [0, 360].
900
918
  - Grid rotation and translation are applied based on the specified parameters.
901
919
  """
902
- if self.verbose:
903
- start_time = time.time()
904
- logging.info("=== Creating the horizontal grid ===")
905
-
906
- self._raise_if_domain_size_too_large()
920
+ with Timed("=== Creating the horizontal grid ===", verbose=self.verbose):
921
+ self._raise_if_domain_size_too_large()
907
922
 
908
- coords = self._make_initial_lon_lat_ds()
923
+ coords = self._make_initial_lon_lat_ds()
909
924
 
910
- # rotate coordinate system
911
- coords = _rotate(coords, self.rot)
925
+ # rotate coordinate system
926
+ coords = _rotate(coords, self.rot)
912
927
 
913
- # translate coordinate system
914
- coords = _translate(coords, self.center_lat, self.center_lon)
928
+ # translate coordinate system
929
+ coords = _translate(coords, self.center_lat, self.center_lon)
915
930
 
916
- # compute 1/dx and 1/dy
917
- coords["pm"], coords["pn"] = _compute_coordinate_metrics(coords)
931
+ # compute 1/dx and 1/dy
932
+ coords["pm"], coords["pn"] = _compute_coordinate_metrics(coords)
918
933
 
919
- # compute angle of local grid positive x-axis relative to east
920
- coords["angle"] = _compute_angle(coords)
934
+ # compute angle of local grid positive x-axis relative to east
935
+ coords["angle"] = _compute_angle(coords)
921
936
 
922
- # make sure lons are in [0, 360] range
923
- for lon in ["lon", "lonu", "lonv", "lonq"]:
924
- coords[lon][coords[lon] < 0] = coords[lon][coords[lon] < 0] + 2 * np.pi
937
+ # make sure lons are in [0, 360] range
938
+ for lon in ["lon", "lonu", "lonv", "lonq"]:
939
+ coords[lon][coords[lon] < 0] = coords[lon][coords[lon] < 0] + 2 * np.pi
925
940
 
926
- ds = self._create_grid_ds(coords)
941
+ ds = self._create_grid_ds(coords)
927
942
 
928
- ds = self._add_global_metadata(ds)
943
+ ds = self._add_global_metadata(ds)
929
944
 
930
- if self.verbose:
931
- logging.info(f"Total time: {time.time() - start_time:.3f} seconds")
932
- logging.info(
933
- "========================================================================================================"
934
- )
935
-
936
- self.ds = ds
945
+ self.ds = ds
937
946
 
938
947
  def _add_global_metadata(self, ds):
939
948
  """Add global metadata and attributes to the dataset.