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.
- roms_tools/datasets/lat_lon_datasets.py +12 -0
- roms_tools/datasets/roms_dataset.py +140 -53
- roms_tools/datasets/utils.py +14 -2
- roms_tools/regrid.py +76 -0
- roms_tools/setup/boundary_forcing.py +2 -2
- roms_tools/setup/grid.py +17 -3
- roms_tools/setup/initial_conditions.py +314 -55
- roms_tools/setup/mask.py +2 -5
- roms_tools/setup/nesting.py +6 -3
- roms_tools/setup/surface_forcing.py +1 -2
- roms_tools/setup/tides.py +6 -5
- roms_tools/setup/utils.py +220 -142
- roms_tools/tests/test_datasets/test_roms_dataset.py +225 -21
- roms_tools/tests/test_regrid.py +120 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK_ALT_CO2/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK_ALT_CO2/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_r/c/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_r/zarr.json +47 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_w/c/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_w/zarr.json +47 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC_ALT_CO2/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC_ALT_CO2/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOCr/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOCr/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DON/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DON/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DONr/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DONr/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOP/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOPr/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOPr/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Fe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Fe/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Lig/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Lig/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NH4/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NH4/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NO3/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NO3/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/O2/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/O2/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/PO4/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/PO4/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/SiO3/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/SiO3/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/abs_time/zarr.json +47 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatChl/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatChl/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatFe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatFe/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatP/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatSi/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatSi/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazChl/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazChl/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazFe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazFe/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazP/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ocean_time/c/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ocean_time/zarr.json +47 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/salt/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/salt/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spCaCO3/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spCaCO3/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spChl/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spChl/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spFe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spFe/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spP/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/temp/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/temp/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/u/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/u/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ubar/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ubar/zarr.json +54 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/v/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/v/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/vbar/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/vbar/zarr.json +54 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/w/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zarr.json +2481 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zeta/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zeta/zarr.json +54 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zooC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zooC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_grid.py +24 -0
- roms_tools/tests/test_setup/test_initial_conditions.py +128 -11
- roms_tools/tests/test_setup/test_validation.py +15 -0
- roms_tools/tests/test_utils.py +287 -0
- roms_tools/utils.py +177 -72
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/METADATA +2 -3
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/RECORD +111 -24
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/WHEEL +1 -1
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/licenses/LICENSE +0 -0
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/top_level.txt +0 -0
roms_tools/setup/utils.py
CHANGED
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
import time
|
|
4
4
|
import typing
|
|
5
5
|
from collections.abc import Sequence
|
|
6
|
+
from copy import deepcopy
|
|
6
7
|
from dataclasses import asdict, fields, is_dataclass
|
|
7
8
|
from datetime import datetime
|
|
8
9
|
from enum import StrEnum
|
|
@@ -17,7 +18,6 @@ import yaml
|
|
|
17
18
|
from pydantic import BaseModel
|
|
18
19
|
|
|
19
20
|
from roms_tools.constants import R_EARTH
|
|
20
|
-
from roms_tools.utils import interpolate_from_rho_to_u, interpolate_from_rho_to_v
|
|
21
21
|
|
|
22
22
|
if typing.TYPE_CHECKING:
|
|
23
23
|
from roms_tools.setup.grid import Grid
|
|
@@ -966,46 +966,6 @@ def get_target_coords(
|
|
|
966
966
|
return target_coords
|
|
967
967
|
|
|
968
968
|
|
|
969
|
-
def rotate_velocities(
|
|
970
|
-
u: xr.DataArray, v: xr.DataArray, angle: xr.DataArray, interpolate: bool = True
|
|
971
|
-
) -> tuple[xr.DataArray, xr.DataArray]:
|
|
972
|
-
"""Rotate and optionally interpolate velocity components to align with grid
|
|
973
|
-
orientation.
|
|
974
|
-
|
|
975
|
-
Parameters
|
|
976
|
-
----------
|
|
977
|
-
u : xarray.DataArray
|
|
978
|
-
Zonal (east-west) velocity component at u-points.
|
|
979
|
-
v : xarray.DataArray
|
|
980
|
-
Meridional (north-south) velocity component at v-points.
|
|
981
|
-
angle : xarray.DataArray
|
|
982
|
-
Grid angle values for rotation.
|
|
983
|
-
interpolate : bool, optional
|
|
984
|
-
If True, interpolates rotated velocities to grid points (default is True).
|
|
985
|
-
|
|
986
|
-
Returns
|
|
987
|
-
-------
|
|
988
|
-
tuple of xarray.DataArray
|
|
989
|
-
Rotated velocity components (u_rot, v_rot).
|
|
990
|
-
|
|
991
|
-
Notes
|
|
992
|
-
-----
|
|
993
|
-
- Rotation formulas:
|
|
994
|
-
- u_rot = u * cos(angle) + v * sin(angle)
|
|
995
|
-
- v_rot = v * cos(angle) - u * sin(angle)
|
|
996
|
-
"""
|
|
997
|
-
# Rotate velocities to grid orientation
|
|
998
|
-
u_rot = u * np.cos(angle) + v * np.sin(angle)
|
|
999
|
-
v_rot = v * np.cos(angle) - u * np.sin(angle)
|
|
1000
|
-
|
|
1001
|
-
# Interpolate to u- and v-points
|
|
1002
|
-
if interpolate:
|
|
1003
|
-
u_rot = interpolate_from_rho_to_u(u_rot)
|
|
1004
|
-
v_rot = interpolate_from_rho_to_v(v_rot)
|
|
1005
|
-
|
|
1006
|
-
return u_rot, v_rot
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
969
|
def compute_barotropic_velocity(
|
|
1010
970
|
vel: xr.DataArray, interface_depth: xr.DataArray
|
|
1011
971
|
) -> xr.DataArray:
|
|
@@ -1365,142 +1325,262 @@ def write_to_yaml(yaml_data, filepath: str | Path) -> None:
|
|
|
1365
1325
|
)
|
|
1366
1326
|
|
|
1367
1327
|
|
|
1328
|
+
def serialize_paths(value: Any) -> Any:
|
|
1329
|
+
"""Recursively convert Path objects to strings."""
|
|
1330
|
+
if isinstance(value, Path):
|
|
1331
|
+
return str(value)
|
|
1332
|
+
if isinstance(value, list):
|
|
1333
|
+
return [serialize_paths(v) for v in value]
|
|
1334
|
+
if isinstance(value, dict):
|
|
1335
|
+
return {k: serialize_paths(v) for k, v in value.items()}
|
|
1336
|
+
return value
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
def normalize_paths(value: Any) -> Any:
|
|
1340
|
+
"""Recursively convert path-like strings back to Path objects.
|
|
1341
|
+
|
|
1342
|
+
Heuristic: strings containing '/' or ending with '.nc' are treated as paths.
|
|
1343
|
+
"""
|
|
1344
|
+
if isinstance(value, str):
|
|
1345
|
+
return Path(value) if "/" in value or value.endswith(".nc") else value
|
|
1346
|
+
if isinstance(value, list):
|
|
1347
|
+
return [normalize_paths(v) for v in value]
|
|
1348
|
+
if isinstance(value, dict):
|
|
1349
|
+
return {k: normalize_paths(v) for k, v in value.items()}
|
|
1350
|
+
return value
|
|
1351
|
+
|
|
1352
|
+
|
|
1353
|
+
def serialize_datetime(value: datetime | list[datetime] | Any) -> Any:
|
|
1354
|
+
"""Convert datetime or list of datetimes to ISO 8601 strings."""
|
|
1355
|
+
if isinstance(value, datetime):
|
|
1356
|
+
return value.isoformat()
|
|
1357
|
+
if isinstance(value, list) and all(isinstance(v, datetime) for v in value):
|
|
1358
|
+
return [v.isoformat() for v in value]
|
|
1359
|
+
return value
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
def deserialize_datetime(
|
|
1363
|
+
value: str | list[str] | datetime | Any,
|
|
1364
|
+
) -> datetime | list[datetime] | Any:
|
|
1365
|
+
"""Convert ISO 8601 string(s) to datetime object(s).
|
|
1366
|
+
|
|
1367
|
+
Returns:
|
|
1368
|
+
datetime if input is string,
|
|
1369
|
+
list of datetime if input is list of strings,
|
|
1370
|
+
original value if parsing fails or input is already datetime.
|
|
1371
|
+
"""
|
|
1372
|
+
if isinstance(value, list):
|
|
1373
|
+
result: list[datetime | Any] = []
|
|
1374
|
+
for v in value:
|
|
1375
|
+
try:
|
|
1376
|
+
result.append(datetime.fromisoformat(str(v)))
|
|
1377
|
+
except ValueError:
|
|
1378
|
+
result.append(v)
|
|
1379
|
+
return result
|
|
1380
|
+
|
|
1381
|
+
if isinstance(value, str):
|
|
1382
|
+
try:
|
|
1383
|
+
return datetime.fromisoformat(value)
|
|
1384
|
+
except ValueError:
|
|
1385
|
+
return value
|
|
1386
|
+
|
|
1387
|
+
return value
|
|
1388
|
+
|
|
1389
|
+
|
|
1390
|
+
def serialize_source_dict(src: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
1391
|
+
"""Serialize a source or BGC source dictionary for YAML or JSON output.
|
|
1392
|
+
|
|
1393
|
+
This function performs the following transformations:
|
|
1394
|
+
- Converts any `Path` objects (including nested lists or dicts) to strings.
|
|
1395
|
+
- Serializes any nested `Grid` objects using `serialize_grid`.
|
|
1396
|
+
- Creates a deep copy of the input dictionary to avoid modifying the original.
|
|
1397
|
+
|
|
1398
|
+
Parameters
|
|
1399
|
+
----------
|
|
1400
|
+
src : dict[str, Any] | None
|
|
1401
|
+
The source or BGC source dictionary to serialize. Keys typically include:
|
|
1402
|
+
- "path": path(s) to files
|
|
1403
|
+
- "grid": a Grid object
|
|
1404
|
+
|
|
1405
|
+
Returns
|
|
1406
|
+
-------
|
|
1407
|
+
dict[str, Any] | None
|
|
1408
|
+
A serialized dictionary suitable for saving to YAML or JSON, with:
|
|
1409
|
+
- Paths converted to strings
|
|
1410
|
+
- Nested Grid objects serialized
|
|
1411
|
+
Returns `None` if input `src` is `None`.
|
|
1412
|
+
"""
|
|
1413
|
+
if src is None:
|
|
1414
|
+
return None
|
|
1415
|
+
|
|
1416
|
+
src = deepcopy(src)
|
|
1417
|
+
|
|
1418
|
+
# Serialize paths
|
|
1419
|
+
if "path" in src:
|
|
1420
|
+
src["path"] = serialize_paths(src["path"])
|
|
1421
|
+
|
|
1422
|
+
# Serialize nested grid
|
|
1423
|
+
if "grid" in src and src["grid"] is not None:
|
|
1424
|
+
src["grid"] = serialize_grid(src["grid"])
|
|
1425
|
+
|
|
1426
|
+
return src
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
def deserialize_source_dict(src: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
1430
|
+
"""Deserialize a source / bgc_source dictionary.
|
|
1431
|
+
|
|
1432
|
+
Converts string paths back to Path objects.
|
|
1433
|
+
|
|
1434
|
+
Parameters
|
|
1435
|
+
----------
|
|
1436
|
+
src : dict[str, Any] | None
|
|
1437
|
+
Serialized source or bgc_source dictionary.
|
|
1438
|
+
|
|
1439
|
+
Returns
|
|
1440
|
+
-------
|
|
1441
|
+
dict[str, Any] | None
|
|
1442
|
+
Dictionary with paths converted to Path objects.
|
|
1443
|
+
"""
|
|
1444
|
+
if src is None:
|
|
1445
|
+
return None
|
|
1446
|
+
|
|
1447
|
+
src = deepcopy(src)
|
|
1448
|
+
|
|
1449
|
+
# Deserialize paths
|
|
1450
|
+
if "path" in src:
|
|
1451
|
+
src["path"] = normalize_paths(src["path"])
|
|
1452
|
+
|
|
1453
|
+
return src
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
def serialize_grid(grid_obj: Any) -> dict[str, Any]:
|
|
1457
|
+
"""Serialize a Grid object to a dictionary, excluding non-serializable attributes."""
|
|
1458
|
+
return pop_grid_data(asdict(grid_obj))
|
|
1459
|
+
|
|
1460
|
+
|
|
1461
|
+
def pop_grid_data(grid_data: dict[str, Any]) -> dict[str, Any]:
|
|
1462
|
+
"""Remove non-serializable or unnecessary keys from a Grid dictionary.
|
|
1463
|
+
|
|
1464
|
+
Removes 'ds', 'straddle', and 'verbose' keys if present.
|
|
1465
|
+
|
|
1466
|
+
Parameters
|
|
1467
|
+
----------
|
|
1468
|
+
grid_data : dict
|
|
1469
|
+
Dictionary representation of a Grid object.
|
|
1470
|
+
|
|
1471
|
+
Returns
|
|
1472
|
+
-------
|
|
1473
|
+
dict
|
|
1474
|
+
Cleaned dictionary suitable for serialization.
|
|
1475
|
+
"""
|
|
1476
|
+
for key in ("ds", "straddle", "verbose"):
|
|
1477
|
+
grid_data.pop(key, None)
|
|
1478
|
+
return grid_data
|
|
1479
|
+
|
|
1480
|
+
|
|
1368
1481
|
def to_dict(forcing_object, exclude: list[str] | None = None) -> dict:
|
|
1369
1482
|
"""Serialize a forcing object (including its grid) into a dictionary.
|
|
1370
1483
|
|
|
1371
|
-
This function serializes a
|
|
1372
|
-
|
|
1373
|
-
that are not serializable or meant to be excluded.
|
|
1484
|
+
This function serializes a forcing object (dataclass or pydantic model),
|
|
1485
|
+
including its associated grid(s), into a dictionary suitable for YAML output.
|
|
1374
1486
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1487
|
+
- Top-level grids (`grid`, `parent_grid`) are serialized consistently
|
|
1488
|
+
- Nested grids inside `source` and `bgc_source` are also serialized
|
|
1489
|
+
- Datetime objects are converted to ISO strings
|
|
1490
|
+
- Path objects are converted to strings
|
|
1377
1491
|
|
|
1378
1492
|
Parameters
|
|
1379
1493
|
----------
|
|
1380
1494
|
forcing_object : object
|
|
1381
|
-
|
|
1382
|
-
such as `grid`, `start_time`, `end_time`, etc.
|
|
1495
|
+
A dataclass or pydantic model representing a forcing configuration.
|
|
1383
1496
|
exclude : list[str], optional
|
|
1384
|
-
List of
|
|
1497
|
+
List of field names to exclude from serialization. The fields
|
|
1498
|
+
"grid", "parent_grid", and "ds" are always excluded.
|
|
1385
1499
|
|
|
1386
1500
|
Returns
|
|
1387
1501
|
-------
|
|
1388
1502
|
dict
|
|
1503
|
+
Serialized representation of the forcing object.
|
|
1389
1504
|
"""
|
|
1390
|
-
|
|
1505
|
+
exclude_list = exclude or []
|
|
1506
|
+
exclude_set: set[str] = {"grid", "parent_grid", "ds", *exclude_list}
|
|
1507
|
+
|
|
1508
|
+
# --- Serialize top-level grid(s) ---
|
|
1509
|
+
yaml_data = {}
|
|
1510
|
+
|
|
1391
1511
|
if hasattr(forcing_object, "grid") and forcing_object.grid is not None:
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
if attr is not None and "path" in attr:
|
|
1402
|
-
paths = attr["path"]
|
|
1403
|
-
if isinstance(paths, list):
|
|
1404
|
-
attr["path"] = [str(p) if isinstance(p, Path) else p for p in paths]
|
|
1405
|
-
elif isinstance(paths, Path):
|
|
1406
|
-
attr["path"] = str(paths)
|
|
1407
|
-
elif isinstance(paths, dict):
|
|
1408
|
-
for key, path in paths.items():
|
|
1409
|
-
attr["path"][key] = str(path)
|
|
1410
|
-
|
|
1411
|
-
ensure_paths_are_strings(forcing_object, "source")
|
|
1412
|
-
ensure_paths_are_strings(forcing_object, "bgc_source")
|
|
1413
|
-
|
|
1414
|
-
# Prepare Forcing Data
|
|
1415
|
-
forcing_data = {}
|
|
1512
|
+
yaml_data["Grid"] = serialize_grid(forcing_object.grid)
|
|
1513
|
+
|
|
1514
|
+
if (
|
|
1515
|
+
hasattr(forcing_object, "parent_grid")
|
|
1516
|
+
and forcing_object.parent_grid is not None
|
|
1517
|
+
):
|
|
1518
|
+
yaml_data["ParentGrid"] = serialize_grid(forcing_object.parent_grid)
|
|
1519
|
+
|
|
1520
|
+
# --- Collect forcing fields ---
|
|
1416
1521
|
if isinstance(forcing_object, BaseModel):
|
|
1417
|
-
field_names = forcing_object.model_fields
|
|
1522
|
+
field_names = forcing_object.model_fields.keys()
|
|
1418
1523
|
elif is_dataclass(forcing_object):
|
|
1419
|
-
field_names = [
|
|
1524
|
+
field_names = [f.name for f in fields(forcing_object)]
|
|
1420
1525
|
else:
|
|
1421
|
-
raise TypeError("Forcing object
|
|
1422
|
-
|
|
1423
|
-
if exclude is None:
|
|
1424
|
-
exclude = []
|
|
1425
|
-
exclude = ["grid", "parent_grid", "ds", *exclude]
|
|
1526
|
+
raise TypeError("Forcing object must be a dataclass or pydantic model")
|
|
1426
1527
|
|
|
1427
|
-
|
|
1528
|
+
forcing_data = {}
|
|
1428
1529
|
|
|
1429
|
-
for
|
|
1430
|
-
|
|
1431
|
-
|
|
1530
|
+
for name in field_names:
|
|
1531
|
+
if name in exclude_set:
|
|
1532
|
+
continue
|
|
1432
1533
|
|
|
1433
|
-
|
|
1434
|
-
if isinstance(value, datetime):
|
|
1435
|
-
value = value.isoformat()
|
|
1436
|
-
# Convert list of datetimes to list of ISO strings
|
|
1437
|
-
elif isinstance(value, list) and all(isinstance(v, datetime) for v in value):
|
|
1438
|
-
value = [v.isoformat() for v in value]
|
|
1534
|
+
value = getattr(forcing_object, name)
|
|
1439
1535
|
|
|
1440
|
-
|
|
1441
|
-
|
|
1536
|
+
if name in {"source", "bgc_source"}:
|
|
1537
|
+
forcing_data[name] = serialize_source_dict(value)
|
|
1538
|
+
continue
|
|
1442
1539
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
**grid_yaml_data, # Add the grid data to the final YAML structure
|
|
1446
|
-
forcing_object.__class__.__name__: forcing_data, # Include the serialized forcing object data
|
|
1447
|
-
}
|
|
1540
|
+
value = serialize_datetime(value)
|
|
1541
|
+
value = serialize_paths(value)
|
|
1448
1542
|
|
|
1449
|
-
|
|
1543
|
+
forcing_data[name] = value
|
|
1450
1544
|
|
|
1545
|
+
# --- Final YAML structure ---
|
|
1546
|
+
yaml_data[forcing_object.__class__.__name__] = forcing_data
|
|
1451
1547
|
|
|
1452
|
-
|
|
1453
|
-
grid_data.pop("ds", None) # Remove 'ds' attribute (non-serializable)
|
|
1454
|
-
grid_data.pop("straddle", None)
|
|
1455
|
-
grid_data.pop("verbose", None)
|
|
1456
|
-
|
|
1457
|
-
return grid_data
|
|
1548
|
+
return yaml_data
|
|
1458
1549
|
|
|
1459
1550
|
|
|
1460
1551
|
def from_yaml(forcing_object: type, filepath: str | Path) -> dict[str, Any]:
|
|
1461
|
-
"""
|
|
1552
|
+
"""Load configuration for a forcing object from a YAML file.
|
|
1462
1553
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1554
|
+
Searches for a dictionary keyed by the class name of `forcing_object` and
|
|
1555
|
+
returns it, converting:
|
|
1556
|
+
- ISO-format date strings to `datetime` objects
|
|
1557
|
+
- Path-like strings back to `Path` objects
|
|
1558
|
+
- `source` and `bgc_source` nested dictionaries back to proper Grid objects
|
|
1467
1559
|
|
|
1468
1560
|
Parameters
|
|
1469
1561
|
----------
|
|
1470
|
-
|
|
1471
|
-
The
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
from the YAML file. The class name is used to locate the relevant data in
|
|
1475
|
-
the YAML structure.
|
|
1562
|
+
forcing_object : type
|
|
1563
|
+
The class type whose configuration to load (e.g., `TidalForcing`).
|
|
1564
|
+
filepath : str | Path
|
|
1565
|
+
Path to the YAML file containing the configuration.
|
|
1476
1566
|
|
|
1477
1567
|
Returns
|
|
1478
1568
|
-------
|
|
1479
|
-
dict
|
|
1480
|
-
|
|
1481
|
-
This dictionary contains key-value pairs where the keys are the parameter
|
|
1482
|
-
names, and the values are the corresponding values from the YAML file.
|
|
1483
|
-
Any date fields are converted from ISO format if necessary.
|
|
1569
|
+
dict[str, Any]
|
|
1570
|
+
Dictionary of configuration parameters with dates, paths, and nested grids restored.
|
|
1484
1571
|
|
|
1485
1572
|
Raises
|
|
1486
1573
|
------
|
|
1487
1574
|
ValueError
|
|
1488
|
-
If no configuration for the specified class
|
|
1575
|
+
If no configuration for the specified class is found in the YAML file.
|
|
1489
1576
|
"""
|
|
1490
|
-
# Ensure filepath is a Path object
|
|
1491
1577
|
filepath = Path(filepath)
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
with filepath.open("r") as file:
|
|
1495
|
-
file_content = file.read()
|
|
1496
|
-
|
|
1497
|
-
# Split the content into YAML documents
|
|
1498
|
-
documents = list(yaml.safe_load_all(file_content))
|
|
1578
|
+
with filepath.open("r") as f:
|
|
1579
|
+
documents = list(yaml.safe_load_all(f))
|
|
1499
1580
|
|
|
1500
1581
|
forcing_data = None
|
|
1501
1582
|
forcing_object_name = forcing_object.__name__
|
|
1502
1583
|
|
|
1503
|
-
# Process the YAML documents to find the forcing data for the given object
|
|
1504
1584
|
for doc in documents:
|
|
1505
1585
|
if doc is None:
|
|
1506
1586
|
continue
|
|
@@ -1513,21 +1593,19 @@ def from_yaml(forcing_object: type, filepath: str | Path) -> dict[str, Any]:
|
|
|
1513
1593
|
f"No {forcing_object_name} configuration found in the YAML file."
|
|
1514
1594
|
)
|
|
1515
1595
|
|
|
1516
|
-
# Convert
|
|
1596
|
+
# Convert ISO date strings to datetime objects
|
|
1517
1597
|
for key, value in forcing_data.items():
|
|
1518
|
-
forcing_data[key] =
|
|
1598
|
+
forcing_data[key] = deserialize_datetime(value)
|
|
1519
1599
|
|
|
1520
|
-
#
|
|
1521
|
-
|
|
1600
|
+
# Convert path-like strings back to Path objects
|
|
1601
|
+
forcing_data = normalize_paths(forcing_data)
|
|
1522
1602
|
|
|
1603
|
+
# Deserialize source and bgc_source nested dictionaries
|
|
1604
|
+
for key in ["source", "bgc_source"]:
|
|
1605
|
+
if key in forcing_data:
|
|
1606
|
+
forcing_data[key] = deserialize_source_dict(forcing_data[key])
|
|
1523
1607
|
|
|
1524
|
-
|
|
1525
|
-
try:
|
|
1526
|
-
# Return the parsed datetime object if successful
|
|
1527
|
-
return datetime.fromisoformat(str(value))
|
|
1528
|
-
except ValueError:
|
|
1529
|
-
# Return None or raise an exception if parsing fails
|
|
1530
|
-
return value
|
|
1608
|
+
return forcing_data
|
|
1531
1609
|
|
|
1532
1610
|
|
|
1533
1611
|
def handle_boundaries(field):
|