roms-tools 3.4.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 (111) hide show
  1. roms_tools/datasets/lat_lon_datasets.py +12 -0
  2. roms_tools/datasets/roms_dataset.py +140 -53
  3. roms_tools/datasets/utils.py +14 -2
  4. roms_tools/regrid.py +76 -0
  5. roms_tools/setup/boundary_forcing.py +2 -2
  6. roms_tools/setup/grid.py +17 -3
  7. roms_tools/setup/initial_conditions.py +314 -55
  8. roms_tools/setup/mask.py +2 -5
  9. roms_tools/setup/nesting.py +6 -3
  10. roms_tools/setup/surface_forcing.py +1 -2
  11. roms_tools/setup/tides.py +6 -5
  12. roms_tools/setup/utils.py +220 -142
  13. roms_tools/tests/test_datasets/test_roms_dataset.py +225 -21
  14. roms_tools/tests/test_regrid.py +120 -1
  15. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK/c/0/0/0/0 +0 -0
  16. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK/zarr.json +57 -0
  17. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK_ALT_CO2/c/0/0/0/0 +0 -0
  18. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK_ALT_CO2/zarr.json +57 -0
  19. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_r/c/0 +0 -0
  20. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_r/zarr.json +47 -0
  21. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_w/c/0 +0 -0
  22. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_w/zarr.json +47 -0
  23. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC/c/0/0/0/0 +0 -0
  24. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC/zarr.json +57 -0
  25. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC_ALT_CO2/c/0/0/0/0 +0 -0
  26. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC_ALT_CO2/zarr.json +57 -0
  27. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOC/c/0/0/0/0 +0 -0
  28. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOC/zarr.json +57 -0
  29. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOCr/c/0/0/0/0 +0 -0
  30. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOCr/zarr.json +57 -0
  31. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DON/c/0/0/0/0 +0 -0
  32. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DON/zarr.json +57 -0
  33. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DONr/c/0/0/0/0 +0 -0
  34. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DONr/zarr.json +57 -0
  35. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOP/c/0/0/0/0 +0 -0
  36. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOP/zarr.json +57 -0
  37. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOPr/c/0/0/0/0 +0 -0
  38. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOPr/zarr.json +57 -0
  39. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Fe/c/0/0/0/0 +0 -0
  40. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Fe/zarr.json +57 -0
  41. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Lig/c/0/0/0/0 +0 -0
  42. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Lig/zarr.json +57 -0
  43. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NH4/c/0/0/0/0 +0 -0
  44. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NH4/zarr.json +57 -0
  45. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NO3/c/0/0/0/0 +0 -0
  46. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NO3/zarr.json +57 -0
  47. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/O2/c/0/0/0/0 +0 -0
  48. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/O2/zarr.json +57 -0
  49. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/PO4/c/0/0/0/0 +0 -0
  50. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/PO4/zarr.json +57 -0
  51. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/SiO3/c/0/0/0/0 +0 -0
  52. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/SiO3/zarr.json +57 -0
  53. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/abs_time/zarr.json +47 -0
  54. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatC/c/0/0/0/0 +0 -0
  55. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatC/zarr.json +57 -0
  56. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatChl/c/0/0/0/0 +0 -0
  57. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatChl/zarr.json +57 -0
  58. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatFe/c/0/0/0/0 +0 -0
  59. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatFe/zarr.json +57 -0
  60. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatP/c/0/0/0/0 +0 -0
  61. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatP/zarr.json +57 -0
  62. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatSi/c/0/0/0/0 +0 -0
  63. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatSi/zarr.json +57 -0
  64. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazC/c/0/0/0/0 +0 -0
  65. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazC/zarr.json +57 -0
  66. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazChl/c/0/0/0/0 +0 -0
  67. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazChl/zarr.json +57 -0
  68. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazFe/c/0/0/0/0 +0 -0
  69. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazFe/zarr.json +57 -0
  70. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazP/c/0/0/0/0 +0 -0
  71. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazP/zarr.json +57 -0
  72. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ocean_time/c/0 +0 -0
  73. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ocean_time/zarr.json +47 -0
  74. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/salt/c/0/0/0/0 +0 -0
  75. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/salt/zarr.json +57 -0
  76. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spC/c/0/0/0/0 +0 -0
  77. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spC/zarr.json +57 -0
  78. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spCaCO3/c/0/0/0/0 +0 -0
  79. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spCaCO3/zarr.json +57 -0
  80. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spChl/c/0/0/0/0 +0 -0
  81. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spChl/zarr.json +57 -0
  82. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spFe/c/0/0/0/0 +0 -0
  83. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spFe/zarr.json +57 -0
  84. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spP/c/0/0/0/0 +0 -0
  85. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spP/zarr.json +57 -0
  86. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/temp/c/0/0/0/0 +0 -0
  87. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/temp/zarr.json +57 -0
  88. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/u/c/0/0/0/0 +0 -0
  89. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/u/zarr.json +57 -0
  90. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ubar/c/0/0/0 +0 -0
  91. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ubar/zarr.json +54 -0
  92. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/v/c/0/0/0/0 +0 -0
  93. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/v/zarr.json +57 -0
  94. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/vbar/c/0/0/0 +0 -0
  95. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/vbar/zarr.json +54 -0
  96. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/w/zarr.json +57 -0
  97. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zarr.json +2481 -0
  98. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zeta/c/0/0/0 +0 -0
  99. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zeta/zarr.json +54 -0
  100. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zooC/c/0/0/0/0 +0 -0
  101. roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zooC/zarr.json +57 -0
  102. roms_tools/tests/test_setup/test_grid.py +24 -0
  103. roms_tools/tests/test_setup/test_initial_conditions.py +128 -11
  104. roms_tools/tests/test_setup/test_validation.py +15 -0
  105. roms_tools/tests/test_utils.py +287 -0
  106. roms_tools/utils.py +177 -72
  107. {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/METADATA +2 -3
  108. {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/RECORD +111 -24
  109. {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/WHEEL +1 -1
  110. {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/licenses/LICENSE +0 -0
  111. {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/top_level.txt +0 -0
@@ -17,8 +17,14 @@ from roms_tools.datasets.lat_lon_datasets import (
17
17
  LatLonDataset,
18
18
  UnifiedBGCDataset,
19
19
  )
20
+ from roms_tools.datasets.roms_dataset import ROMSDataset, choose_subdomain
20
21
  from roms_tools.plot import plot
21
- from roms_tools.regrid import LateralRegridToROMS, VerticalRegridToROMS
22
+ from roms_tools.regrid import (
23
+ LateralRegridFromROMS,
24
+ LateralRegridToROMS,
25
+ VerticalRegrid,
26
+ VerticalRegridToROMS,
27
+ )
22
28
  from roms_tools.setup.utils import (
23
29
  RawDataSource,
24
30
  compute_barotropic_velocity,
@@ -27,7 +33,7 @@ from roms_tools.setup.utils import (
27
33
  get_target_coords,
28
34
  get_variable_metadata,
29
35
  nan_check,
30
- rotate_velocities,
36
+ pop_grid_data,
31
37
  substitute_nans_by_fillvalue,
32
38
  to_dict,
33
39
  write_to_yaml,
@@ -35,6 +41,7 @@ from roms_tools.setup.utils import (
35
41
  from roms_tools.utils import (
36
42
  interpolate_from_rho_to_u,
37
43
  interpolate_from_rho_to_v,
44
+ rotate_velocities,
38
45
  save_datasets,
39
46
  transpose_dimensions,
40
47
  )
@@ -112,6 +119,18 @@ class InitialConditions:
112
119
  ... "climatology": False,
113
120
  ... },
114
121
  ... )
122
+
123
+ >>> initial_conditions = InitialConditions(
124
+ ... grid=grid,
125
+ ... ini_time=datetime(2022, 1, 1),
126
+ ... source={"name": "ROMS", "grid": parent_grid, "path": "restart.nc"},
127
+ ... bgc_source={
128
+ ... "name": "ROMS",
129
+ ... "grid": parent_grid,
130
+ ... "path": "restart.nc",
131
+ ... },
132
+ ... )
133
+
115
134
  """
116
135
 
117
136
  grid: Grid
@@ -145,7 +164,6 @@ class InitialConditions:
145
164
 
146
165
  def __post_init__(self):
147
166
  # Initialize depth coordinates
148
- self.adjust_depth_for_sea_surface_height = False
149
167
  self.ds_depth_coords = xr.Dataset()
150
168
 
151
169
  self._input_checks()
@@ -180,7 +198,6 @@ class InitialConditions:
180
198
  target_coords = get_target_coords(self.grid)
181
199
 
182
200
  data = self._get_data(forcing_type=type)
183
-
184
201
  data.choose_subdomain(
185
202
  target_coords,
186
203
  )
@@ -188,6 +205,7 @@ class InitialConditions:
188
205
  data.convert_to_float64()
189
206
  data.extrapolate_deepest_to_bottom()
190
207
  data.apply_lateral_fill()
208
+ data.rotate_velocities_to_east_and_north()
191
209
 
192
210
  self._set_variable_info(data, type=type)
193
211
  attr_name = f"variable_info_{type}"
@@ -202,6 +220,7 @@ class InitialConditions:
202
220
  var: {
203
221
  "name": data.var_names[var],
204
222
  "location": variable_info[var]["location"],
223
+ "is_3d": variable_info[var]["is_3d"],
205
224
  }
206
225
  for var in data.var_names.keys()
207
226
  if data.var_names[var] in data.ds.data_vars
@@ -212,27 +231,24 @@ class InitialConditions:
212
231
  var: {
213
232
  "name": data.opt_var_names[var],
214
233
  "location": variable_info[var]["location"],
234
+ "is_3d": variable_info[var]["is_3d"],
215
235
  }
216
236
  for var in data.opt_var_names.keys()
217
237
  if data.opt_var_names[var] in data.ds.data_vars
218
238
  }
219
239
  )
220
240
 
221
- # lateral regridding
222
- lateral_regrid = LateralRegridToROMS(target_coords, data.dim_names)
223
-
224
- for var_name in var_names:
225
- processed_fields[var_name] = lateral_regrid.apply(
226
- data.ds[var_names[var_name]["name"]]
227
- )
228
-
229
- # rotation of velocities and interpolation to u/v points
241
+ # Lateral regridding
242
+ processed_fields = self._regrid_laterally(
243
+ data, target_coords, processed_fields, var_names
244
+ )
245
+ # Rotation of velocities and interpolation to u/v points
230
246
  if "u" in var_names and "v" in var_names:
231
247
  processed_fields["u"], processed_fields["v"] = rotate_velocities(
232
248
  processed_fields["u"],
233
249
  processed_fields["v"],
234
250
  target_coords["angle"],
235
- interpolate=True,
251
+ interpolate_after=True,
236
252
  )
237
253
 
238
254
  if type == "bgc":
@@ -246,32 +262,11 @@ class InitialConditions:
246
262
  zeta = (
247
263
  processed_fields["zeta"] if self.adjust_depth_for_sea_surface_height else 0
248
264
  )
249
-
250
265
  for location in ["rho", "u", "v"]:
251
- # Filter var_names by location and check for 3D variables
252
- filtered_vars = [
253
- var_name
254
- for var_name, info in var_names.items()
255
- if info["location"] == location and variable_info[var_name]["is_3d"]
256
- ]
266
+ self._get_depth_coordinates(zeta, location, "layer")
257
267
 
258
- if filtered_vars:
259
- # Handle depth coordinates
260
- self._get_depth_coordinates(zeta, location, "layer")
261
-
262
- # Vertical regridding
263
- vertical_regrid = VerticalRegridToROMS(
264
- self.ds_depth_coords[f"layer_depth_{location}"],
265
- data.ds[data.dim_names["depth"]],
266
- )
267
- for var_name in filtered_vars:
268
- if var_name in processed_fields:
269
- field = processed_fields[var_name]
270
- if self.use_dask:
271
- field = field.chunk(
272
- _set_dask_chunks(location, self.horizontal_chunk_size)
273
- )
274
- processed_fields[var_name] = vertical_regrid.apply(field)
268
+ # Vertical regridding
269
+ processed_fields = self._regrid_vertically(data, processed_fields, var_names)
275
270
 
276
271
  # Compute barotropic velocities
277
272
  if "u" in var_names and "v" in var_names:
@@ -284,13 +279,157 @@ class InitialConditions:
284
279
 
285
280
  return processed_fields
286
281
 
287
- def _input_checks(self):
288
- # Check that ini_time is not None
289
- if self.ini_time is None:
290
- raise ValueError(
291
- "`ini_time` must be a valid datetime object and cannot be None."
282
+ def _regrid_laterally(
283
+ self,
284
+ data: ROMSDataset | LatLonDataset,
285
+ target_coords: dict[str, xr.DataArray],
286
+ processed_fields: dict[str, xr.DataArray],
287
+ var_names: dict[str, dict[str, str]],
288
+ ):
289
+ """Regrid variables in data.ds laterally to target coordinates.
290
+
291
+ Parameters
292
+ ----------
293
+ data : ROMSDataset or LatLonDataset
294
+ The dataset containing variables to regrid.
295
+ target_coords : dict[str, xr.DataArray]
296
+ Dictionary of target coordinates for regridding.
297
+ processed_fields : dict[str, xr.DataArray]
298
+ Dictionary where regridded variables will be stored.
299
+ var_names : dict[str, dict[str, str]]
300
+ Mapping from variable keys to dataset variable names and metadata.
301
+
302
+ Returns
303
+ -------
304
+ processed_fields : dict[str, xr.DataArray]
305
+ Updated dictionary with regridded variables.
306
+ """
307
+ if isinstance(data, ROMSDataset):
308
+ # Compute depth coordinates on source data for rho
309
+ data._get_depth_coordinates(depth_type="layer", locations=["rho"])
310
+ # Subset depth coordinate to target subdomain
311
+ data.ds_depth_coords = choose_subdomain(
312
+ data.ds_depth_coords,
313
+ data.grid.ds,
314
+ target_coords,
315
+ )
316
+
317
+ # Regrid all rho variables
318
+ ds_rho = data.ds[[var_names[var]["name"] for var in var_names]].rename(
319
+ {"lat_rho": "lat", "lon_rho": "lon"}
320
+ )
321
+ lateral_regrid_from_roms = LateralRegridFromROMS(ds_rho, target_coords)
322
+ ds_rho = lateral_regrid_from_roms.apply(ds_rho)
323
+
324
+ for var_name in var_names:
325
+ processed_fields[var_name] = ds_rho[var_name]
326
+
327
+ # Regrid depth coordinates
328
+ processed_fields["layer_depth_rho"] = lateral_regrid_from_roms.apply(
329
+ data.ds_depth_coords["layer_depth_rho"]
292
330
  )
293
331
 
332
+ else:
333
+ lateral_regrid_to_roms = LateralRegridToROMS(target_coords, data.dim_names)
334
+ for var_name in var_names:
335
+ processed_fields[var_name] = lateral_regrid_to_roms.apply(
336
+ data.ds[var_names[var_name]["name"]]
337
+ )
338
+
339
+ return processed_fields
340
+
341
+ def _regrid_vertically(
342
+ self,
343
+ data: ROMSDataset | LatLonDataset,
344
+ processed_fields: dict[str, xr.DataArray],
345
+ var_names: dict[str, dict[str, str | bool]],
346
+ ) -> dict[str, xr.DataArray]:
347
+ """
348
+ Perform vertical regridding of 3D variables to the model's vertical grid.
349
+
350
+ For each vertical location ('rho', 'u', 'v'), this method regrids variables
351
+ that are flagged as 3D in `var_names`. The regridding procedure differs
352
+ depending on whether the source dataset is a ROMSDataset or a LatLonDataset.
353
+
354
+ Parameters
355
+ ----------
356
+ data : ROMSDataset or LatLonDataset
357
+ Dataset containing the variables to regrid.
358
+ processed_fields : dict[str, xarray.DataArray]
359
+ Dictionary containing fields that have already been regridded laterally.
360
+ This method updates the entries in-place with vertically regridded fields.
361
+ var_names : dict[str, dict[str, str | bool]]
362
+ Mapping of variable keys to dataset variable metadata:
363
+ - 'name': dataset variable name
364
+ - 'location': vertical location ('rho', 'u', 'v')
365
+ - 'is_3d': whether the variable is 3D and requires vertical regridding
366
+
367
+ Returns
368
+ -------
369
+ processed_fields : dict[str, xarray.DataArray]
370
+ Dictionary containing the same variables as `processed_fields`, now updated
371
+ with vertically regridded values.
372
+ """
373
+ for location in ["rho", "u", "v"]:
374
+ # Select variables for this vertical location that are 3D
375
+ filtered_vars = [
376
+ var_name
377
+ for var_name, info in var_names.items()
378
+ if info["location"] == location and info["is_3d"]
379
+ ]
380
+
381
+ if not filtered_vars:
382
+ continue
383
+
384
+ if isinstance(data, ROMSDataset):
385
+ # Interpolate depth coordinates from rho to u/v points if needed
386
+ if location == "u":
387
+ processed_fields["layer_depth_u"] = interpolate_from_rho_to_u(
388
+ processed_fields["layer_depth_rho"]
389
+ )
390
+ elif location == "v":
391
+ processed_fields["layer_depth_v"] = interpolate_from_rho_to_v(
392
+ processed_fields["layer_depth_rho"]
393
+ )
394
+
395
+ # Use the first variable to initialize VerticalRegrid
396
+ ds_tmp = xr.Dataset(
397
+ {filtered_vars[0]: processed_fields[filtered_vars[0]]}
398
+ )
399
+ vertical_regrid = VerticalRegrid(ds_tmp)
400
+
401
+ for var_name in filtered_vars:
402
+ if var_name in processed_fields:
403
+ processed_fields[var_name] = vertical_regrid.apply(
404
+ processed_fields[var_name],
405
+ source_depth_coords=processed_fields[
406
+ f"layer_depth_{location}"
407
+ ],
408
+ target_depth_coords=self.ds_depth_coords[
409
+ f"layer_depth_{location}"
410
+ ],
411
+ mask_edges=False,
412
+ )
413
+ else:
414
+ # LatLonDataset: create a regrid object for all variables
415
+ vertical_regrid_to_roms = VerticalRegridToROMS(
416
+ self.ds_depth_coords[f"layer_depth_{location}"],
417
+ data.ds[data.dim_names["depth"]],
418
+ )
419
+
420
+ for var_name in filtered_vars:
421
+ if var_name not in processed_fields:
422
+ continue
423
+ field = processed_fields[var_name]
424
+ if getattr(self, "use_dask", False):
425
+ field = field.chunk(
426
+ _set_dask_chunks(location, self.horizontal_chunk_size)
427
+ )
428
+ processed_fields[var_name] = vertical_regrid_to_roms.apply(field)
429
+
430
+ return processed_fields
431
+
432
+ def _input_checks(self):
294
433
  if "name" not in self.source.keys():
295
434
  raise ValueError("`source` must include a 'name'.")
296
435
  if "path" not in self.source.keys():
@@ -318,8 +457,14 @@ class InitialConditions:
318
457
  **self.bgc_source,
319
458
  "climatology": self.bgc_source.get("climatology", False),
320
459
  }
460
+ if not isinstance(self.ini_time, datetime):
461
+ raise TypeError(
462
+ f"`ini_time` must be a datetime object, got {type(self.ini_time).__name__} instead."
463
+ )
321
464
 
322
- def _get_data(self, forcing_type=Literal["physics", "bgc"]) -> LatLonDataset:
465
+ def _get_data(
466
+ self, forcing_type=Literal["physics", "bgc"]
467
+ ) -> LatLonDataset | ROMSDataset:
323
468
  """Determine the correct `Dataset` type and return an instance.
324
469
 
325
470
  forcing_type : str
@@ -330,18 +475,22 @@ class InitialConditions:
330
475
  Returns
331
476
  -------
332
477
  Dataset
333
- The `Dataset` instance
478
+ The `LatLonDataset` or `ROMSDataset` instance
334
479
  """
335
- dataset_map: dict[str, dict[str, dict[str, type[LatLonDataset]]]] = {
480
+ dataset_map: dict[
481
+ str, dict[str, dict[str, type[LatLonDataset | ROMSDataset]]]
482
+ ] = {
336
483
  "physics": {
337
484
  "GLORYS": {
338
485
  "external": GLORYSDataset,
339
486
  "default": GLORYSDefaultDataset,
340
487
  },
488
+ "ROMS": defaultdict(lambda: ROMSDataset),
341
489
  },
342
490
  "bgc": {
343
491
  "CESM_REGRIDDED": defaultdict(lambda: CESMBGCDataset),
344
492
  "UNIFIED": defaultdict(lambda: UnifiedBGCDataset),
493
+ "ROMS": defaultdict(lambda: ROMSDataset),
345
494
  },
346
495
  }
347
496
 
@@ -369,13 +518,31 @@ class InitialConditions:
369
518
  if isinstance(source_dict["path"], bool):
370
519
  raise ValueError('source["path"] cannot be a boolean here')
371
520
 
372
- return data_type(
373
- filename=source_dict["path"],
374
- start_time=self.ini_time,
375
- climatology=source_dict["climatology"], # type: ignore
376
- allow_flex_time=self.allow_flex_time,
377
- use_dask=self.use_dask,
378
- )
521
+ if source_dict["name"] == "ROMS":
522
+ var_names = _set_required_vars(forcing_type)
523
+ self.adjust_depth_for_sea_surface_height = True
524
+
525
+ data = data_type(
526
+ path=source_dict["path"], # type: ignore
527
+ grid=source_dict["grid"], # type: ignore
528
+ var_names=var_names,
529
+ start_time=self.ini_time,
530
+ allow_flex_time=self.allow_flex_time,
531
+ adjust_depth_for_sea_surface_height=True,
532
+ use_dask=self.use_dask,
533
+ )
534
+
535
+ else:
536
+ self.adjust_depth_for_sea_surface_height = False
537
+ data = data_type(
538
+ filename=source_dict["path"], # type: ignore
539
+ start_time=self.ini_time,
540
+ climatology=source_dict["climatology"], # type: ignore
541
+ allow_flex_time=self.allow_flex_time,
542
+ use_dask=self.use_dask,
543
+ )
544
+
545
+ return data
379
546
 
380
547
  def _set_variable_info(self, data, type="physics"):
381
548
  """Sets up a dictionary with metadata for variables based on the type.
@@ -466,7 +633,16 @@ class InitialConditions:
466
633
  if var_name == "ALK":
467
634
  variable_info[var_name] = {**default_info, "validate": True}
468
635
  else:
469
- variable_info[var_name] = {**default_info, "validate": False}
636
+ if var_name == "zeta":
637
+ variable_info[var_name] = {
638
+ "location": "rho",
639
+ "is_vector": False,
640
+ "vector_pair": None,
641
+ "is_3d": False,
642
+ "validate": False,
643
+ }
644
+ else:
645
+ variable_info[var_name] = {**default_info, "validate": False}
470
646
 
471
647
  object.__setattr__(self, f"variable_info_{type}", variable_info)
472
648
 
@@ -579,7 +755,7 @@ class InitialConditions:
579
755
  model_reference_date = np.datetime64(self.model_reference_date)
580
756
 
581
757
  # Convert the time coordinate to the format expected by ROMS (seconds since model reference date)
582
- ocean_time = (ds["time"] - model_reference_date).astype("float64") * 1e-9
758
+ ocean_time = (ds["time"] - model_reference_date).dt.total_seconds()
583
759
  ds = ds.assign_coords(ocean_time=("time", ocean_time.data.astype("float64")))
584
760
  ds["ocean_time"].attrs["long_name"] = (
585
761
  f"relative time: seconds since {self.model_reference_date!s}"
@@ -853,6 +1029,14 @@ class InitialConditions:
853
1029
 
854
1030
  grid = Grid.from_yaml(filepath)
855
1031
  initial_conditions_params = from_yaml(cls, filepath)
1032
+
1033
+ # Deserialize nested grids inside 'source' and 'bgc_source'
1034
+ for name in ["source", "bgc_source"]:
1035
+ src_dict = initial_conditions_params.get(name)
1036
+ if src_dict and "grid" in src_dict and src_dict["grid"] is not None:
1037
+ grid_data = pop_grid_data(src_dict["grid"])
1038
+ src_dict["grid"] = Grid(**grid_data)
1039
+
856
1040
  return cls(
857
1041
  grid=grid,
858
1042
  **initial_conditions_params,
@@ -881,3 +1065,78 @@ def _set_dask_chunks(location: str, chunk_size: int):
881
1065
  "v": {"eta_v": chunk_size, "xi_rho": chunk_size},
882
1066
  }
883
1067
  return chunk_mapping.get(location, {})
1068
+
1069
+
1070
+ def _set_required_vars(var_type: str = "physics") -> dict[str, str]:
1071
+ """
1072
+ Return the canonical variable-name mapping for a ROMS dataset.
1073
+
1074
+ Parameters
1075
+ ----------
1076
+ var_type : str, optional
1077
+ Category of variables. Supported values:
1078
+ - "physics": physical variables (temperature, salinity, currents, etc.)
1079
+ - "bgc": biogeochemical variables (nutrients, pigments, carbon, etc.)
1080
+ Default is "physics".
1081
+
1082
+ Returns
1083
+ -------
1084
+ dict[str, str]
1085
+ Mapping from logical variable names to dataset variable names.
1086
+
1087
+ Raises
1088
+ ------
1089
+ ValueError
1090
+ If an unsupported `var_type` is provided.
1091
+ """
1092
+ var_mappings = {
1093
+ "physics": {
1094
+ "zeta": "zeta",
1095
+ "temp": "temp",
1096
+ "salt": "salt",
1097
+ "u": "u",
1098
+ "v": "v",
1099
+ },
1100
+ "bgc": {
1101
+ "zeta": "zeta", # to infer vertical coordinate
1102
+ "PO4": "PO4",
1103
+ "NO3": "NO3",
1104
+ "SiO3": "SiO3",
1105
+ "NH4": "NH4",
1106
+ "Fe": "Fe",
1107
+ "Lig": "Lig",
1108
+ "O2": "O2",
1109
+ "DIC": "DIC",
1110
+ "DIC_ALT_CO2": "DIC_ALT_CO2",
1111
+ "ALK": "ALK",
1112
+ "ALK_ALT_CO2": "ALK_ALT_CO2",
1113
+ "DOC": "DOC",
1114
+ "DON": "DON",
1115
+ "DOP": "DOP",
1116
+ "DOPr": "DOPr",
1117
+ "DONr": "DONr",
1118
+ "DOCr": "DOCr",
1119
+ "spChl": "spChl",
1120
+ "spC": "spC",
1121
+ "spP": "spP",
1122
+ "spFe": "spFe",
1123
+ "diatChl": "diatChl",
1124
+ "diatC": "diatC",
1125
+ "diatP": "diatP",
1126
+ "diatFe": "diatFe",
1127
+ "diatSi": "diatSi",
1128
+ "diazChl": "diazChl",
1129
+ "diazC": "diazC",
1130
+ "diazP": "diazP",
1131
+ "diazFe": "diazFe",
1132
+ "spCaCO3": "spCaCO3",
1133
+ "zooC": "zooC",
1134
+ },
1135
+ }
1136
+
1137
+ if var_type not in var_mappings:
1138
+ raise ValueError(
1139
+ f"Unsupported var_type '{var_type}'. Choose from {list(var_mappings.keys())}."
1140
+ )
1141
+
1142
+ return var_mappings[var_type]
roms_tools/setup/mask.py CHANGED
@@ -8,11 +8,8 @@ import regionmask
8
8
  import xarray as xr
9
9
  from scipy.ndimage import label
10
10
 
11
- from roms_tools.setup.utils import (
12
- handle_boundaries,
13
- interpolate_from_rho_to_u,
14
- interpolate_from_rho_to_v,
15
- )
11
+ from roms_tools.setup.utils import handle_boundaries
12
+ from roms_tools.utils import interpolate_from_rho_to_u, interpolate_from_rho_to_v
16
13
 
17
14
 
18
15
  def add_mask(ds: xr.Dataset, shapefile: str | Path | None = None) -> xr.Dataset:
@@ -16,13 +16,16 @@ from roms_tools.setup.utils import (
16
16
  check_and_set_boundaries,
17
17
  from_yaml,
18
18
  get_boundary_coords,
19
- interpolate_from_rho_to_u,
20
- interpolate_from_rho_to_v,
21
19
  pop_grid_data,
22
20
  to_dict,
23
21
  write_to_yaml,
24
22
  )
25
- from roms_tools.utils import save_datasets, wrap_longitudes
23
+ from roms_tools.utils import (
24
+ interpolate_from_rho_to_u,
25
+ interpolate_from_rho_to_v,
26
+ save_datasets,
27
+ wrap_longitudes,
28
+ )
26
29
 
27
30
 
28
31
  @dataclass(kw_only=True)
@@ -28,13 +28,13 @@ from roms_tools.setup.utils import (
28
28
  group_dataset,
29
29
  min_dist_to_land,
30
30
  nan_check,
31
- rotate_velocities,
32
31
  substitute_nans_by_fillvalue,
33
32
  to_dict,
34
33
  write_to_yaml,
35
34
  )
36
35
  from roms_tools.utils import (
37
36
  interpolate_from_climatology,
37
+ rotate_velocities,
38
38
  save_datasets,
39
39
  transpose_dimensions,
40
40
  )
@@ -201,7 +201,6 @@ class SurfaceForcing:
201
201
  processed_fields["uwnd"],
202
202
  processed_fields["vwnd"],
203
203
  target_coords["angle"],
204
- interpolate=False,
205
204
  )
206
205
 
207
206
  if self.type == "physics":
roms_tools/setup/tides.py CHANGED
@@ -15,15 +15,17 @@ from roms_tools.setup.utils import (
15
15
  get_target_coords,
16
16
  get_variable_metadata,
17
17
  get_vector_pairs,
18
- interpolate_from_rho_to_u,
19
- interpolate_from_rho_to_v,
20
18
  nan_check,
21
- rotate_velocities,
22
19
  substitute_nans_by_fillvalue,
23
20
  to_dict,
24
21
  write_to_yaml,
25
22
  )
26
- from roms_tools.utils import save_datasets
23
+ from roms_tools.utils import (
24
+ interpolate_from_rho_to_u,
25
+ interpolate_from_rho_to_v,
26
+ rotate_velocities,
27
+ save_datasets,
28
+ )
27
29
 
28
30
 
29
31
  @dataclass(kw_only=True)
@@ -141,7 +143,6 @@ class TidalForcing:
141
143
  processed_fields[u_component],
142
144
  processed_fields[v_component],
143
145
  target_coords["angle"],
144
- interpolate=False,
145
146
  )
146
147
 
147
148
  # convert to barotropic velocity