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
@@ -308,36 +308,136 @@ def test_that_coordinates_and_masks_are_added(use_dask):
308
308
  assert "mask_v" in output.ds
309
309
 
310
310
 
311
- def test_apply_lateral_fill():
312
- grid_parameters = {
313
- "nx": 10,
314
- "ny": 10,
315
- "size_x": 2500,
316
- "size_y": 3000,
317
- "center_lon": -30,
318
- "center_lat": 57,
319
- "rot": -20,
320
- }
311
+ # Test applying lateral fill
312
+
313
+
314
+ def make_roms_dataset(ds, grid):
315
+ roms = ROMSDataset.__new__(ROMSDataset)
316
+ roms.ds = ds
317
+ roms.grid = grid
318
+ return roms
319
+
321
320
 
322
- grid = Grid(**grid_parameters)
321
+ def test_apply_lateral_fill_rho_success():
322
+ grid = Grid(
323
+ nx=10, ny=10, size_x=2500, size_y=3000, center_lon=-30, center_lat=57, rot=-20
324
+ )
323
325
 
324
326
  ds = xr.Dataset()
325
327
  ds["field"] = 5 * grid.ds.mask_rho.copy()
326
328
  ds["mask_rho"] = grid.ds.mask_rho.copy()
329
+
330
+ roms = make_roms_dataset(ds, grid)
331
+
332
+ # land exists initially
333
+ assert (roms.ds["field"] == 0).any()
334
+
335
+ roms.apply_lateral_fill()
336
+
337
+ assert (roms.ds["field"] == 5).all()
338
+
339
+
340
+ def test_apply_lateral_fill_rho_missing_mask_raises():
341
+ grid = Grid(
342
+ nx=10, ny=10, size_x=2500, size_y=3000, center_lon=-30, center_lat=57, rot=-20
343
+ )
344
+
345
+ ds = xr.Dataset()
346
+ ds["field"] = grid.ds.mask_rho.copy()
347
+
348
+ roms = make_roms_dataset(ds, grid)
349
+
350
+ with pytest.raises(ValueError, match="mask_rho"):
351
+ roms.apply_lateral_fill()
352
+
353
+
354
+ def test_apply_lateral_fill_u_success():
355
+ grid = Grid(
356
+ nx=10, ny=10, size_x=2500, size_y=3000, center_lon=-30, center_lat=57, rot=-20
357
+ )
358
+
359
+ ds = xr.Dataset()
360
+ ds["u_field"] = 7 * grid.ds.mask_u.copy()
327
361
  ds["mask_u"] = grid.ds.mask_u.copy()
362
+
363
+ roms = make_roms_dataset(ds, grid)
364
+
365
+ assert (roms.ds["u_field"] == 0).any()
366
+
367
+ roms.apply_lateral_fill()
368
+
369
+ assert (roms.ds["u_field"] == 7).all()
370
+
371
+
372
+ def test_apply_lateral_fill_u_missing_mask_raises():
373
+ grid = Grid(
374
+ nx=10, ny=10, size_x=2500, size_y=3000, center_lon=-30, center_lat=57, rot=-20
375
+ )
376
+
377
+ ds = xr.Dataset()
378
+ ds["u_field"] = grid.ds.mask_u.copy()
379
+ ds["mask_rho"] = grid.ds.mask_rho.copy()
380
+
381
+ roms = make_roms_dataset(ds, grid)
382
+
383
+ with pytest.raises(ValueError, match="mask_u"):
384
+ roms.apply_lateral_fill()
385
+
386
+
387
+ def test_apply_lateral_fill_v_success():
388
+ grid = Grid(
389
+ nx=10, ny=10, size_x=2500, size_y=3000, center_lon=-30, center_lat=57, rot=-20
390
+ )
391
+
392
+ ds = xr.Dataset()
393
+ ds["v_field"] = 9 * grid.ds.mask_v.copy()
328
394
  ds["mask_v"] = grid.ds.mask_v.copy()
329
395
 
330
- roms_dataset = ROMSDataset.__new__(ROMSDataset)
331
- roms_dataset.ds = ds
332
- roms_dataset.grid = grid
396
+ roms = make_roms_dataset(ds, grid)
333
397
 
334
- # Check that initially some values are zero (land)
335
- assert (roms_dataset.ds["field"].values == 0).any()
398
+ assert (roms.ds["v_field"] == 0).any()
336
399
 
337
- roms_dataset.apply_lateral_fill()
400
+ roms.apply_lateral_fill()
338
401
 
339
- # After filling the ocean values (all 5) should have propagated into land
340
- assert (roms_dataset.ds["field"].values == 5).all()
402
+ assert (roms.ds["v_field"] == 9).all()
403
+
404
+
405
+ def test_apply_lateral_fill_v_missing_mask_raises():
406
+ grid = Grid(
407
+ nx=10, ny=10, size_x=2500, size_y=3000, center_lon=-30, center_lat=57, rot=-20
408
+ )
409
+
410
+ ds = xr.Dataset()
411
+ ds["v_field"] = grid.ds.mask_v.copy()
412
+ ds["mask_rho"] = grid.ds.mask_rho.copy()
413
+
414
+ roms = make_roms_dataset(ds, grid)
415
+
416
+ with pytest.raises(ValueError, match="mask_v"):
417
+ roms.apply_lateral_fill()
418
+
419
+
420
+ def test_apply_lateral_fill_mixed_grids():
421
+ grid = Grid(
422
+ nx=10, ny=10, size_x=2500, size_y=3000, center_lon=-30, center_lat=57, rot=-20
423
+ )
424
+
425
+ ds = xr.Dataset()
426
+ ds["rho"] = 1 * grid.ds.mask_rho.copy()
427
+ ds["u"] = 2 * grid.ds.mask_u.copy()
428
+ ds["v"] = 3 * grid.ds.mask_v.copy()
429
+
430
+ ds["mask_rho"] = grid.ds.mask_rho.copy()
431
+ ds["mask_u"] = grid.ds.mask_u.copy()
432
+ ds["mask_v"] = grid.ds.mask_v.copy()
433
+
434
+ roms = make_roms_dataset(ds, grid)
435
+
436
+ roms.apply_lateral_fill()
437
+
438
+ assert (roms.ds["rho"] == 1).all()
439
+ assert (roms.ds["u"] == 2).all()
440
+ assert (roms.ds["v"] == 3).all()
341
441
 
342
442
 
343
443
  # Test choose_subdomain
@@ -513,8 +613,7 @@ def test_choose_subdomain_with(big_params, small_params):
513
613
  target_coords = get_target_coords(small)
514
614
 
515
615
  # --- apply function ---
516
- sub = choose_subdomain(ds, big.ds, target_coords, buffer_points=1)
517
-
616
+ sub = choose_subdomain(ds, big.ds, target_coords, buffer_points=10)
518
617
  # --- rho tests ---
519
618
  assert sub.lat_rho.shape[0] <= ds.lat_rho.shape[0]
520
619
  assert sub.lat_rho.shape[1] <= ds.lat_rho.shape[1]
@@ -537,3 +636,108 @@ def test_choose_subdomain_with(big_params, small_params):
537
636
  assert float(sub.lat_v.min()) >= float(ds.lat_v.min()) - 1e-6
538
637
  assert float(sub.lat_v.max()) <= float(ds.lat_v.max()) + 1e-6
539
638
  assert not sub.field_v.isnull().any()
639
+
640
+
641
+ def test_choose_subdomain_dataset_and_grid(use_dask):
642
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
643
+ grid = Grid.from_file(fname_grid)
644
+
645
+ roms_dataset = ROMSDataset(
646
+ grid=grid,
647
+ path=Path(download_test_data("eastpac25km_rst.19980106000000.nc")),
648
+ use_dask=use_dask,
649
+ )
650
+
651
+ # Save original sizes
652
+ orig_grid_sizes = dict(roms_dataset.grid.ds.sizes)
653
+ orig_ds_sizes = dict(roms_dataset.ds.sizes)
654
+
655
+ # Create a small child grid and target coordinates
656
+ child_grid = Grid(
657
+ nx=5,
658
+ ny=5,
659
+ size_x=100,
660
+ size_y=100,
661
+ center_lon=-128.0,
662
+ center_lat=9.0,
663
+ rot=32.0,
664
+ )
665
+ target_coords = get_target_coords(child_grid)
666
+
667
+ # Apply subdomain selection
668
+ roms_dataset.choose_subdomain(target_coords, buffer_points=1)
669
+
670
+ new_grid_sizes = dict(roms_dataset.grid.ds.sizes)
671
+ new_ds_sizes = dict(roms_dataset.ds.sizes)
672
+
673
+ # Grid and dataset should both be reduced
674
+ assert new_grid_sizes != orig_grid_sizes, (
675
+ "Grid sizes did not change after choose_subdomain"
676
+ )
677
+ assert new_ds_sizes != orig_ds_sizes, (
678
+ "Dataset sizes did not change after choose_subdomain"
679
+ )
680
+
681
+ # Grid and dataset should remain consistent with each other
682
+ joint_dims = ["eta_rho", "xi_rho", "xi_u", "eta_v", "s_rho"]
683
+
684
+ for dim in joint_dims:
685
+ assert new_grid_sizes.get(dim) == new_ds_sizes.get(dim), (
686
+ f"Mismatch in dimension '{dim}': "
687
+ f"grid={new_grid_sizes.get(dim)}, ds={new_ds_sizes.get(dim)}"
688
+ )
689
+
690
+
691
+ def test_choose_subdomain_does_not_mutate_shared_grid(use_dask):
692
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
693
+ grid = Grid.from_file(fname_grid)
694
+
695
+ rd1 = ROMSDataset(
696
+ grid=grid,
697
+ path=Path(download_test_data("eastpac25km_rst.19980106000000.nc")),
698
+ use_dask=use_dask,
699
+ )
700
+ rd2 = ROMSDataset(
701
+ grid=grid,
702
+ path=Path(download_test_data("eastpac25km_rst.19980106000000.nc")),
703
+ use_dask=use_dask,
704
+ )
705
+
706
+ child_grid = Grid(
707
+ nx=5,
708
+ ny=5,
709
+ size_x=100,
710
+ size_y=100,
711
+ center_lon=-128.0,
712
+ center_lat=9.0,
713
+ rot=32.0,
714
+ )
715
+ target_coords = get_target_coords(child_grid)
716
+ rd1.choose_subdomain(target_coords, buffer_points=1)
717
+
718
+ assert rd2.grid.ds.sizes == grid.ds.sizes
719
+
720
+
721
+ def test_choose_subdomain_then_compute_depth_coordinates(use_dask):
722
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
723
+ grid = Grid.from_file(fname_grid)
724
+
725
+ rd = ROMSDataset(
726
+ grid=grid,
727
+ path=Path(download_test_data("eastpac25km_rst.19980106000000.nc")),
728
+ use_dask=use_dask,
729
+ )
730
+
731
+ child_grid = Grid(
732
+ nx=5,
733
+ ny=5,
734
+ size_x=100,
735
+ size_y=100,
736
+ center_lon=-128.0,
737
+ center_lat=9.0,
738
+ rot=32.0,
739
+ )
740
+ target_coords = get_target_coords(child_grid)
741
+ rd.choose_subdomain(target_coords, buffer_points=1)
742
+
743
+ rd._get_depth_coordinates(locations=["rho", "u", "v"])
@@ -2,7 +2,7 @@ import numpy as np
2
2
  import pytest
3
3
  import xarray as xr
4
4
 
5
- from roms_tools.regrid import VerticalRegridToROMS
5
+ from roms_tools.regrid import VerticalRegrid, VerticalRegridToROMS
6
6
 
7
7
  try:
8
8
  import xesmf # type: ignore
@@ -134,3 +134,122 @@ def test_vertical_regrid(request, depth_values, layer_depth_rho_values, temp_dat
134
134
  regridded = vertical_regrid.apply(data.temp_data, fill_nans=True)
135
135
  expected = np.interp(layer_depth_rho_values, depth_values, temp_data)
136
136
  assert np.allclose(expected, regridded.data, equal_nan=True)
137
+
138
+
139
+ # Test VerticalRegrid
140
+ def test_vertical_regrid_2d_depths_different_vertical_levels():
141
+ """
142
+ Vertical regridding with 2D (eta, xi) depth coordinates where
143
+ source and target have different numbers of vertical levels.
144
+ """
145
+ # --- dimensions ---
146
+ time = xr.DataArray(
147
+ np.array(["2000-01-01", "2000-01-02"], dtype="datetime64[ns]"),
148
+ dims="time",
149
+ )
150
+
151
+ s_rho_src = xr.DataArray(np.arange(6), dims="s_rho") # source: 6 levels
152
+ s_rho_tgt = xr.DataArray(np.arange(10), dims="s_rho") # target: 10 levels
153
+
154
+ eta = xr.DataArray(np.arange(3), dims="eta")
155
+ xi = xr.DataArray(np.arange(4), dims="xi")
156
+
157
+ # --- source depth coords: 2D + s_rho ---
158
+ base_depth = -(100 + 10 * eta.values[:, None] + 5 * xi.values[None, :])
159
+
160
+ source_depth = xr.DataArray(
161
+ base_depth,
162
+ dims=("eta", "xi"),
163
+ coords={"eta": eta, "xi": xi},
164
+ ).expand_dims(time=time, s_rho=s_rho_src)
165
+
166
+ source_depth = source_depth + 20 * s_rho_src
167
+
168
+ # --- target depth coords: 2D + different s_rho ---
169
+ target_depth = xr.DataArray(
170
+ base_depth,
171
+ dims=("eta", "xi"),
172
+ coords={"eta": eta, "xi": xi},
173
+ ).expand_dims(s_rho=s_rho_tgt)
174
+
175
+ target_depth = target_depth + 20 * s_rho_tgt
176
+
177
+ # --- synthetic temperature field: varies linearly with depth ---
178
+ temp_data = xr.DataArray(
179
+ np.broadcast_to(
180
+ s_rho_src.values[None, :, None, None] * 2.0,
181
+ (len(time), len(s_rho_src), len(eta), len(xi)),
182
+ ),
183
+ dims=("time", "s_rho", "eta", "xi"),
184
+ coords={
185
+ "time": time,
186
+ "s_rho": s_rho_src,
187
+ "eta": eta,
188
+ "xi": xi,
189
+ },
190
+ name="temp",
191
+ )
192
+
193
+ ds = xr.Dataset(
194
+ {
195
+ "temp": temp_data,
196
+ },
197
+ coords={
198
+ "time": time,
199
+ "s_rho": s_rho_src,
200
+ "eta": eta,
201
+ "xi": xi,
202
+ },
203
+ )
204
+
205
+ # --- regrid ---
206
+ regridder = VerticalRegrid(ds)
207
+ out = regridder.apply(
208
+ temp_data,
209
+ source_depth_coords=source_depth,
210
+ target_depth_coords=target_depth,
211
+ )
212
+
213
+ # --- assertions ---
214
+ assert isinstance(out, xr.DataArray)
215
+
216
+ # vertical dimension changed to target resolution
217
+ assert out.sizes["s_rho"] == s_rho_tgt.size
218
+
219
+ # horizontal + time preserved
220
+ assert out.sizes["time"] == time.size
221
+ assert out.sizes["eta"] == eta.size
222
+ assert out.sizes["xi"] == xi.size
223
+
224
+ # output contains finite values
225
+ assert np.isfinite(out).any()
226
+
227
+ # interpolation stays within source bounds
228
+ assert out.min() >= temp_data.min()
229
+ assert out.max() <= temp_data.max()
230
+
231
+
232
+ def test_vertical_regrid_mask_edges():
233
+ """Values outside source depth range should be masked when mask_edges=True."""
234
+ s_rho = xr.DataArray(np.linspace(-1, 0, 5), dims="s_rho")
235
+ source_depth = xr.DataArray([-100, -75, -50, -25, 0], dims="s_rho")
236
+
237
+ target_depth = xr.DataArray([-200, -50, 10], dims="s_rho")
238
+
239
+ data = xr.DataArray(
240
+ source_depth.values,
241
+ dims="s_rho",
242
+ coords={"s_rho": s_rho},
243
+ )
244
+
245
+ ds = xr.Dataset(
246
+ {"data": data, "depth": source_depth},
247
+ coords={"s_rho": s_rho},
248
+ )
249
+
250
+ regridder = VerticalRegrid(ds)
251
+ out = regridder.apply(data, source_depth, target_depth, mask_edges=True)
252
+
253
+ assert np.isnan(out[0])
254
+ assert np.isnan(out[-1])
255
+ assert not np.isnan(out[1])
@@ -0,0 +1,57 @@
1
+ {
2
+ "shape": [
3
+ 1,
4
+ 3,
5
+ 7,
6
+ 7
7
+ ],
8
+ "data_type": "float32",
9
+ "chunk_grid": {
10
+ "name": "regular",
11
+ "configuration": {
12
+ "chunk_shape": [
13
+ 1,
14
+ 3,
15
+ 7,
16
+ 7
17
+ ]
18
+ }
19
+ },
20
+ "chunk_key_encoding": {
21
+ "name": "default",
22
+ "configuration": {
23
+ "separator": "/"
24
+ }
25
+ },
26
+ "fill_value": 0.0,
27
+ "codecs": [
28
+ {
29
+ "name": "bytes",
30
+ "configuration": {
31
+ "endian": "little"
32
+ }
33
+ },
34
+ {
35
+ "name": "zstd",
36
+ "configuration": {
37
+ "level": 0,
38
+ "checksum": false
39
+ }
40
+ }
41
+ ],
42
+ "attributes": {
43
+ "long_name": "alkalinity",
44
+ "units": "meq/m^3",
45
+ "coordinates": "abs_time",
46
+ "_FillValue": "AAAAAAAA+H8="
47
+ },
48
+ "dimension_names": [
49
+ "ocean_time",
50
+ "s_rho",
51
+ "eta_rho",
52
+ "xi_rho"
53
+ ],
54
+ "zarr_format": 3,
55
+ "node_type": "array",
56
+ "storage_transformers": []
57
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "shape": [
3
+ 1,
4
+ 3,
5
+ 7,
6
+ 7
7
+ ],
8
+ "data_type": "float32",
9
+ "chunk_grid": {
10
+ "name": "regular",
11
+ "configuration": {
12
+ "chunk_shape": [
13
+ 1,
14
+ 3,
15
+ 7,
16
+ 7
17
+ ]
18
+ }
19
+ },
20
+ "chunk_key_encoding": {
21
+ "name": "default",
22
+ "configuration": {
23
+ "separator": "/"
24
+ }
25
+ },
26
+ "fill_value": 0.0,
27
+ "codecs": [
28
+ {
29
+ "name": "bytes",
30
+ "configuration": {
31
+ "endian": "little"
32
+ }
33
+ },
34
+ {
35
+ "name": "zstd",
36
+ "configuration": {
37
+ "level": 0,
38
+ "checksum": false
39
+ }
40
+ }
41
+ ],
42
+ "attributes": {
43
+ "long_name": "alkalinity, alternative CO2",
44
+ "units": "meq/m^3",
45
+ "coordinates": "abs_time",
46
+ "_FillValue": "AAAAAAAA+H8="
47
+ },
48
+ "dimension_names": [
49
+ "ocean_time",
50
+ "s_rho",
51
+ "eta_rho",
52
+ "xi_rho"
53
+ ],
54
+ "zarr_format": 3,
55
+ "node_type": "array",
56
+ "storage_transformers": []
57
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "shape": [
3
+ 3
4
+ ],
5
+ "data_type": "float32",
6
+ "chunk_grid": {
7
+ "name": "regular",
8
+ "configuration": {
9
+ "chunk_shape": [
10
+ 3
11
+ ]
12
+ }
13
+ },
14
+ "chunk_key_encoding": {
15
+ "name": "default",
16
+ "configuration": {
17
+ "separator": "/"
18
+ }
19
+ },
20
+ "fill_value": 0.0,
21
+ "codecs": [
22
+ {
23
+ "name": "bytes",
24
+ "configuration": {
25
+ "endian": "little"
26
+ }
27
+ },
28
+ {
29
+ "name": "zstd",
30
+ "configuration": {
31
+ "level": 0,
32
+ "checksum": false
33
+ }
34
+ }
35
+ ],
36
+ "attributes": {
37
+ "long_name": "Vertical stretching function at rho-points",
38
+ "units": "nondimensional",
39
+ "_FillValue": "AAAAAAAA+H8="
40
+ },
41
+ "dimension_names": [
42
+ "s_rho"
43
+ ],
44
+ "zarr_format": 3,
45
+ "node_type": "array",
46
+ "storage_transformers": []
47
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "shape": [
3
+ 4
4
+ ],
5
+ "data_type": "float32",
6
+ "chunk_grid": {
7
+ "name": "regular",
8
+ "configuration": {
9
+ "chunk_shape": [
10
+ 4
11
+ ]
12
+ }
13
+ },
14
+ "chunk_key_encoding": {
15
+ "name": "default",
16
+ "configuration": {
17
+ "separator": "/"
18
+ }
19
+ },
20
+ "fill_value": 0.0,
21
+ "codecs": [
22
+ {
23
+ "name": "bytes",
24
+ "configuration": {
25
+ "endian": "little"
26
+ }
27
+ },
28
+ {
29
+ "name": "zstd",
30
+ "configuration": {
31
+ "level": 0,
32
+ "checksum": false
33
+ }
34
+ }
35
+ ],
36
+ "attributes": {
37
+ "long_name": "Vertical stretching function at w-points",
38
+ "units": "nondimensional",
39
+ "_FillValue": "AAAAAAAA+H8="
40
+ },
41
+ "dimension_names": [
42
+ "s_w"
43
+ ],
44
+ "zarr_format": 3,
45
+ "node_type": "array",
46
+ "storage_transformers": []
47
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "shape": [
3
+ 1,
4
+ 3,
5
+ 7,
6
+ 7
7
+ ],
8
+ "data_type": "float32",
9
+ "chunk_grid": {
10
+ "name": "regular",
11
+ "configuration": {
12
+ "chunk_shape": [
13
+ 1,
14
+ 3,
15
+ 7,
16
+ 7
17
+ ]
18
+ }
19
+ },
20
+ "chunk_key_encoding": {
21
+ "name": "default",
22
+ "configuration": {
23
+ "separator": "/"
24
+ }
25
+ },
26
+ "fill_value": 0.0,
27
+ "codecs": [
28
+ {
29
+ "name": "bytes",
30
+ "configuration": {
31
+ "endian": "little"
32
+ }
33
+ },
34
+ {
35
+ "name": "zstd",
36
+ "configuration": {
37
+ "level": 0,
38
+ "checksum": false
39
+ }
40
+ }
41
+ ],
42
+ "attributes": {
43
+ "long_name": "dissolved inorganic carbon",
44
+ "units": "mmol/m^3",
45
+ "coordinates": "abs_time",
46
+ "_FillValue": "AAAAAAAA+H8="
47
+ },
48
+ "dimension_names": [
49
+ "ocean_time",
50
+ "s_rho",
51
+ "eta_rho",
52
+ "xi_rho"
53
+ ],
54
+ "zarr_format": 3,
55
+ "node_type": "array",
56
+ "storage_transformers": []
57
+ }