Skip to content

Commit aa5021d

Browse files
committed
add vanilla summarize function
1 parent 7747b55 commit aa5021d

3 files changed

Lines changed: 682 additions & 1 deletion

File tree

oceanval/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
nc.session_info["stamp"] = nc.session_info["stamp"] + "_ecoval_output_"
99
import copy
1010
from oceanval.matchall import matchup
11+
from oceanval.summarize import summarize
1112
import dill
1213

1314
# from oceanval.fixers import tidy_name
@@ -19,7 +20,7 @@
1920
from oceanval.fvcom import fvcom_preprocess
2021
import importlib
2122

22-
from oceanval.parsers import Validator, definitions
23+
from oceanval.parsers import Validator, definitions, Summary, summaries
2324

2425

2526
def reset():
@@ -31,13 +32,15 @@ def reset():
3132
# reset session_info["short_title"] to empty dict
3233
session_info["short_title"] = dict()
3334
definitions.reset()
35+
summaries.reset()
3436

3537

3638
notebook_dict = dict()
3739

3840

3941
add_point_comparison = definitions.add_point_comparison
4042
add_gridded_comparison = definitions.add_gridded_comparison
43+
add_summary = summaries.add_summary
4144

4245

4346
def fix_toc(concise=True, data_dir=None, out_dir=None, info = False):

oceanval/parsers.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,181 @@ def add_point_comparison(self,
852852
definitions = Validator()
853853

854854

855+
class SummaryVariable:
856+
"""Class to hold metadata for summary variables"""
857+
def __init__(self):
858+
self.name = None
859+
self.model_variable = None
860+
self.long_name = None
861+
self.short_name = None
862+
self.units = None
863+
self.vertical_integration = False
864+
self.vertical_average = False
865+
self.horizontal_average = False
866+
867+
def __str__(self):
868+
attrs = vars(self)
869+
return '\n'.join("%s: %s" % item for item in attrs.items())
870+
871+
def __repr__(self):
872+
attrs = vars(self)
873+
return '\n'.join("%s: %s" % item for item in attrs.items())
874+
875+
876+
class Summary:
877+
"""
878+
Summary class for defining variables to be summarized from model output.
879+
880+
This class allows you to specify how variables should be processed for summaries,
881+
including vertical integration, vertical averaging, horizontal averaging, etc.
882+
"""
883+
884+
keys = []
885+
886+
def __delattr__(self, name):
887+
"""Remove variable from keys list when deleted"""
888+
if name != "keys":
889+
if name in self.keys:
890+
self.keys.remove(name)
891+
super().__delattr__(name)
892+
893+
def remove(self, name):
894+
"""Remove a variable from the summary definitions"""
895+
if name != "keys":
896+
if name in self.keys:
897+
self.keys.remove(name)
898+
super().__delattr__(name)
899+
900+
def reset(self):
901+
"""Reset all summary definitions"""
902+
for key in self.keys:
903+
super().__delattr__(key)
904+
self.keys = []
905+
906+
def __setattr__(self, name, value):
907+
"""Add variable name to keys list when set"""
908+
if name != "keys":
909+
if name not in self.keys:
910+
self.keys.append(name)
911+
super().__setattr__(name, value)
912+
913+
def __getitem__(self, key):
914+
"""Allow dictionary-style access to variables"""
915+
return getattr(self, key, None)
916+
917+
def add_summary(
918+
self,
919+
name=None,
920+
model_variable=None,
921+
long_name=None,
922+
short_name=None,
923+
vertical_integration=False,
924+
vertical_average=False,
925+
depth_range=None,
926+
climatology_years = None
927+
):
928+
"""
929+
Add a variable to be summarized from model output.
930+
931+
Parameters
932+
----------
933+
name : str
934+
Name identifier for the variable. Must contain only letters and numbers.
935+
model_variable : str
936+
Name of the variable in the model output files.
937+
long_name : str, optional
938+
Long descriptive name for the variable.
939+
short_name : str, optional
940+
Short name for the variable. Defaults to name if not provided.
941+
vertical_integration : bool, default False
942+
Whether to vertically integrate the variable.
943+
vertical_average : bool, default False
944+
Whether to vertically average the variable.
945+
946+
Returns
947+
-------
948+
None
949+
950+
Raises
951+
------
952+
ValueError
953+
If name is not provided, contains invalid characters, or if both
954+
vertical_integration and vertical_average are True.
955+
956+
Examples
957+
--------
958+
>>> summary = Summary()
959+
>>> summary.add_summary(
960+
... name="temperature",
961+
... model_variable="temp",
962+
... long_name="Sea Water Temperature",
963+
... vertical_average=True,
964+
... )
965+
"""
966+
967+
# climatology_years must be a list of two integers if provided
968+
if climatology_years is None:
969+
raise ValueError("climatology_years must be provided as [start_year, end_year]")
970+
if climatology_years is not None:
971+
if not isinstance(climatology_years, (list, tuple)):
972+
raise ValueError("climatology_years must be a list or tuple of two integers [start_year, end_year]")
973+
if len(climatology_years) != 2:
974+
raise ValueError("climatology_years must contain exactly 2 values [start_year, end_year]")
975+
try:
976+
start_year = int(climatology_years[0])
977+
end_year = int(climatology_years[1])
978+
if start_year > end_year:
979+
raise ValueError("start_year must be less than end_year in climatology_years")
980+
except (TypeError, ValueError) as e:
981+
raise ValueError("climatology_years values must be integers")
982+
983+
984+
# Validate required parameters
985+
if name is None:
986+
raise ValueError("Name must be provided")
987+
988+
# Validate name contains only letters and numbers
989+
if not re.match("^[A-Za-z0-9]+$", name):
990+
raise ValueError("Name can only contain letters and numbers")
991+
992+
if model_variable is None:
993+
raise ValueError("Model variable must be provided")
994+
995+
# Validate that both vertical operations are not True
996+
if vertical_integration and vertical_average:
997+
raise ValueError("Cannot specify both vertical_integration and vertical_average")
998+
999+
1000+
# Create variable if it doesn't exist, or get existing one
1001+
if getattr(self, name, None) is None:
1002+
var = SummaryVariable()
1003+
setattr(self, name, var)
1004+
else:
1005+
var = self[name]
1006+
1007+
# Set attributes
1008+
var.name = name
1009+
var.model_variable = model_variable
1010+
var.climatology_years = climatology_years
1011+
1012+
# Set optional attributes with defaults
1013+
if long_name is not None:
1014+
var.long_name = long_name
1015+
else:
1016+
var.long_name = name if var.long_name is None else var.long_name
1017+
1018+
if short_name is not None:
1019+
var.short_name = short_name
1020+
else:
1021+
var.short_name = name if var.short_name is None else var.short_name
1022+
1023+
var.vertical_integration = vertical_integration
1024+
var.vertical_average = vertical_average
1025+
1026+
1027+
summaries = Summary()
1028+
1029+
8551030
def generate_mapping(ds):
8561031
"""
8571032
Generate mapping of model and observational variables
@@ -890,4 +1065,38 @@ def generate_mapping(ds):
8901065

8911066
return model_dict
8921067

1068+
def generate_mapping_summary(ds):
1069+
"""
1070+
Generate mapping of model and observational variables
1071+
"""
1072+
1073+
model_dict = {}
1074+
try:
1075+
candidate_variables = summaries.keys
1076+
ds1 = nc.open_data(ds[0], checks=False)
1077+
ds_contents = ds1.contents
1078+
1079+
ds_contents["long_name"] = [str(x) for x in ds_contents["long_name"]]
8931080

1081+
ds_contents_top = ds_contents.query("nlevels == 1").reset_index(drop=True)
1082+
n_levels = int(ds_contents.nlevels.max())
1083+
if n_levels > session_info["n_levels"]:
1084+
session_info["n_levels"] = n_levels
1085+
# number of rows in ds_contents
1086+
if len(ds_contents) == 0:
1087+
ds_contents = ds_contents_top
1088+
except:
1089+
return model_dict
1090+
1091+
for vv in candidate_variables:
1092+
variables = summaries[vv].model_variable.split("+")
1093+
include = True
1094+
for var in variables:
1095+
if var not in ds_contents.variable.values:
1096+
include = False
1097+
if include:
1098+
model_dict[vv] = summaries[vv].model_variable
1099+
n_levels = ds_contents.query("variable in @variables")["nlevels"].max()
1100+
continue
1101+
1102+
return model_dict

0 commit comments

Comments
 (0)