roms-tools 3.1.2__py3-none-any.whl → 3.2.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/__init__.py +3 -0
- roms_tools/analysis/cdr_analysis.py +203 -0
- roms_tools/analysis/cdr_ensemble.py +198 -0
- roms_tools/analysis/roms_output.py +80 -46
- roms_tools/data/grids/GLORYS_global_grid.nc +0 -0
- roms_tools/download.py +4 -0
- roms_tools/plot.py +75 -21
- roms_tools/setup/boundary_forcing.py +44 -19
- roms_tools/setup/cdr_forcing.py +122 -8
- roms_tools/setup/cdr_release.py +161 -8
- roms_tools/setup/datasets.py +626 -340
- roms_tools/setup/grid.py +138 -137
- roms_tools/setup/initial_conditions.py +113 -48
- roms_tools/setup/mask.py +63 -7
- roms_tools/setup/nesting.py +67 -42
- roms_tools/setup/river_forcing.py +45 -19
- roms_tools/setup/surface_forcing.py +4 -6
- roms_tools/setup/tides.py +1 -2
- roms_tools/setup/topography.py +4 -4
- roms_tools/setup/utils.py +134 -22
- roms_tools/tests/test_analysis/test_cdr_analysis.py +144 -0
- roms_tools/tests/test_analysis/test_cdr_ensemble.py +202 -0
- roms_tools/tests/test_analysis/test_roms_output.py +61 -3
- roms_tools/tests/test_setup/test_boundary_forcing.py +54 -52
- roms_tools/tests/test_setup/test_cdr_forcing.py +54 -0
- roms_tools/tests/test_setup/test_cdr_release.py +118 -1
- roms_tools/tests/test_setup/test_datasets.py +392 -44
- roms_tools/tests/test_setup/test_grid.py +222 -115
- roms_tools/tests/test_setup/test_initial_conditions.py +94 -41
- roms_tools/tests/test_setup/test_surface_forcing.py +2 -1
- roms_tools/tests/test_setup/test_utils.py +91 -1
- roms_tools/tests/test_setup/utils.py +71 -0
- roms_tools/tests/test_tiling/test_join.py +241 -0
- roms_tools/tests/test_utils.py +139 -17
- roms_tools/tiling/join.py +189 -0
- roms_tools/utils.py +131 -99
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/METADATA +12 -2
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/RECORD +41 -33
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/WHEEL +0 -0
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/licenses/LICENSE +0 -0
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/top_level.txt +0 -0
roms_tools/setup/cdr_release.py
CHANGED
|
@@ -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
|
|
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
|
-
@
|
|
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
|
|
459
|
+
if isinstance(tracer_concentrations, Concentration):
|
|
460
|
+
tracer_concentrations.check_length(num_times)
|
|
393
461
|
|
|
394
|
-
self.volume_fluxes
|
|
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,
|
|
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
|
|
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,
|
|
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:
|