Skip to content

Commit

Permalink
WIP promoting the bounds to a cube
Browse files Browse the repository at this point in the history
  • Loading branch information
HGWright committed Feb 28, 2025
1 parent 9eb29de commit 656ee1c
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 43 deletions.
5 changes: 5 additions & 0 deletions lib/iris/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def __init__(
pandas_ndim=False,
save_split_attrs=False,
date_microseconds=False,
derived_bounds=False,
):
"""Container for run-time options controls.
Expand Down Expand Up @@ -182,6 +183,8 @@ def __init__(
behaviour, such as when using :class:`~iris.Constraint`, and you
may need to defend against floating point precision issues where
you didn't need to before.
derived_bounds : bool, default=False
When True, uses the new form for deriving bounds with the load.
"""
# The flag 'example_future_flag' is provided as a reference for the
Expand All @@ -195,6 +198,7 @@ def __init__(
self.__dict__["pandas_ndim"] = pandas_ndim
self.__dict__["save_split_attrs"] = save_split_attrs
self.__dict__["date_microseconds"] = date_microseconds
self.__dict__["derived_bounds"] = derived_bounds

# TODO: next major release: set IrisDeprecation to subclass
# DeprecationWarning instead of UserWarning.
Expand All @@ -208,6 +212,7 @@ def __repr__(self):
self.pandas_ndim,
self.save_split_attrs,
self.date_microseconds,
self.derived_bounds,
)

# deprecated_options = {'example_future_flag': 'warning',}
Expand Down
134 changes: 91 additions & 43 deletions lib/iris/fileformats/cf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from abc import ABCMeta, abstractmethod
from collections.abc import Iterable, MutableMapping
import contextlib
import os
import re
from typing import ClassVar, Optional
Expand Down Expand Up @@ -94,6 +95,8 @@ def __init__(self, name, data):
#: CF-netCDF formula terms that his variable participates in.
self.cf_terms_by_root = {}

self._to_be_promoted = False

self.cf_attrs_reset()

@staticmethod
Expand Down Expand Up @@ -1416,44 +1419,70 @@ def _translate(self, variables):
# Identify and register all CF formula terms.
formula_terms = _CFFormulaTermsVariable.identify(variables)

for cf_var in formula_terms.values():
for cf_root, cf_term in cf_var.cf_terms_by_root.items():
bounds_name = None
cf_root_coord = self.cf_group.coordinates.get(cf_root)
with contextlib.suppress(AttributeError):
# Copes with cf_root_coord not existing, OR not having
# `bounds` attribute.
bounds_name = cf_root_coord.bounds
if bounds_name is not None:
# This will error if more or less than 1 variable is found.
(bounds_var,) = [
f
for f in formula_terms.values()
if f.cf_terms_by_root.get(bounds_name) == cf_term
]
if bounds_var != cf_var:
cf_var.bounds = bounds_var.cf_name
new_var = CFBoundaryVariable(
bounds_var.cf_name, bounds_var.cf_data
)
new_var.add_formula_term(bounds_name, cf_term)
self.cf_group[bounds_var.cf_name] = new_var

if cf_root not in self.cf_group.bounds:
cf_name = cf_var.cf_name
if cf_var.cf_name not in self.cf_group:
new_var = CFAuxiliaryCoordinateVariable(cf_name, cf_var.cf_data)
if hasattr(cf_var, "bounds"):
new_var.bounds = cf_var.bounds
new_var.add_formula_term(cf_root, cf_term)
self.cf_group[cf_name] = new_var
if iris.FUTURE.derived_bounds:
# cf_var = CFFormulaTermsVariable (loops through everything that appears in formula terms)
for cf_var in formula_terms.values():
# eg. eta:'a' | cf_root = eta and cf_term = a. cf_var.cf_terms_by_root = {'eta': 'a'} (looking at all appearances in formula terms)
for cf_root, cf_term in cf_var.cf_terms_by_root.items():
# gets set to the bounds of the coord from cf_root_coord
bounds_name = None
# cf_root_coord = CFCoordinateVariable of the coordinate relating to the root
cf_root_coord = self.cf_group.coordinates.get(cf_root)
if cf_root_coord is None:
cf_root_coord = self.cf_group.auxiliary_coordinates.get(cf_root)
with contextlib.suppress(AttributeError):
# Copes with cf_root_coord not existing, OR not having
# `bounds` attribute.
bounds_name = cf_root_coord.bounds

if bounds_name is not None:
try:
# This will error if more or less than 1 variable is found.
# TODO: try a try/except here or logical alternative
(bounds_var,) = [
# loop through all formula terms and add them if they have a cf_term_by_root
# where (bounds of cf_root): cf_term (same as before)
f
for f in formula_terms.values()
if f.cf_terms_by_root.get(bounds_name) == cf_term
]
if bounds_var != cf_var:
cf_var.bounds = bounds_var.cf_name
new_var = CFBoundaryVariable(
bounds_var.cf_name, bounds_var.cf_data
)
new_var.add_formula_term(bounds_name, cf_term)
self.cf_group[bounds_var.cf_name] = new_var
except ValueError:
# Modify the boundary_variable set _to_be_promoted to True
self.cf_group.get(bounds_name)._to_be_promoted = True

if cf_root not in self.cf_group.bounds:
cf_name = cf_var.cf_name
if cf_var.cf_name not in self.cf_group:
new_var = CFAuxiliaryCoordinateVariable(
cf_name, cf_var.cf_data
)
if hasattr(cf_var, "bounds"):
new_var.bounds = cf_var.bounds
new_var.add_formula_term(cf_root, cf_term)
self.cf_group[cf_name] = new_var
else:
for cf_var in formula_terms.values():
for cf_root, cf_term in cf_var.cf_terms_by_root.items():
# Ignore formula terms owned by a bounds variable.
if cf_root not in self.cf_group.bounds:
cf_name = cf_var.cf_name
if cf_var.cf_name not in self.cf_group:
self.cf_group[cf_name] = CFAuxiliaryCoordinateVariable(
cf_name, cf_var.cf_data
)
self.cf_group[cf_name].add_formula_term(cf_root, cf_term)

# Determine the CF data variables.
data_variable_names = (
set(netcdf_variable_names) - self.cf_group.non_data_variable_names
)
print("name")

for name in data_variable_names:
self.cf_group[name] = CFDataVariable(name, variables[name])
Expand All @@ -1477,7 +1506,7 @@ def _span_check(
"""Sanity check dimensionality."""
var = self.cf_group[var_name]
# No span check is necessary if variable is attached to a mesh.
if is_mesh_var or var.spans(cf_variable):
if (is_mesh_var or var.spans(cf_variable)) and not var._to_be_promoted:
cf_group[var_name] = var
else:
# Register the ignored variable.
Expand Down Expand Up @@ -1521,12 +1550,13 @@ def _span_check(
for cf_name in match:
_span_check(cf_name)

if hasattr(cf_variable, "bounds"):
if cf_variable.bounds not in cf_group:
bounds_var = self.cf_group[cf_variable.bounds]
# TODO: warning if fails spans
if bounds_var.spans(cf_variable):
cf_group[bounds_var.cf_name] = bounds_var
if iris.FUTURE.derived_bounds:
if hasattr(cf_variable, "bounds"):
if cf_variable.bounds not in cf_group:
bounds_var = self.cf_group[cf_variable.bounds]
# TODO: warning if fails spans
if bounds_var.spans(cf_variable):
cf_group[bounds_var.cf_name] = bounds_var

# Build CF data variable relationships.
if isinstance(cf_variable, CFDataVariable):
Expand Down Expand Up @@ -1570,13 +1600,30 @@ def _span_check(
# may be promoted to a CFDataVariable and restrict promotion to only
# those formula terms that are reference surface/phenomenon.
for cf_var in self.cf_group.formula_terms.values():
if self.cf_group[cf_var.cf_name] is CFBoundaryVariable:
continue
if iris.FUTURE.derived_bounds:
if self.cf_group[cf_var.cf_name] is CFBoundaryVariable:
continue
else:
for cf_root, cf_term in cf_var.cf_terms_by_root.items():
cf_root_var = self.cf_group[cf_root]
if not hasattr(cf_root_var, "standard_name"):
continue
name = cf_root_var.standard_name or cf_root_var.long_name
terms = reference_terms.get(name, [])
if isinstance(terms, str) or not isinstance(terms, Iterable):
terms = [terms]
cf_var_name = cf_var.cf_name
if (
cf_term in terms
and cf_var_name not in self.cf_group.promoted
):
data_var = CFDataVariable(cf_var_name, cf_var.cf_data)
self.cf_group.promoted[cf_var_name] = data_var
_build(data_var)
break
else:
for cf_root, cf_term in cf_var.cf_terms_by_root.items():
cf_root_var = self.cf_group[cf_root]
if not hasattr(cf_root_var, "standard_name"):
continue
name = cf_root_var.standard_name or cf_root_var.long_name
terms = reference_terms.get(name, [])
if isinstance(terms, str) or not isinstance(terms, Iterable):
Expand All @@ -1587,6 +1634,7 @@ def _span_check(
self.cf_group.promoted[cf_var_name] = data_var
_build(data_var)
break

# Promote any ignored variables.
promoted = set()
not_promoted = ignored.difference(promoted)
Expand Down

0 comments on commit 656ee1c

Please sign in to comment.