Skip to content

Commit 8d6323d

Browse files
authored
fix _get_nominal_resolution() (#342)
* fix _get_nominal_resolution() * Refactor _get_nominal_resolution method indentation * fix test and pre-commit issue
1 parent 3f8136d commit 8d6323d

4 files changed

Lines changed: 149 additions & 4 deletions

File tree

src/access_moppy/ocean.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def __init__(
270270
drs_root=drs_root,
271271
)
272272

273-
nominal_resolution = vocab._get_nominal_resolution()
273+
nominal_resolution = vocab._get_nominal_resolution(target_realm="ocean")
274274
self.supergrid = Supergrid(nominal_resolution)
275275
self.grid_info = None
276276
self.grid_type = None
@@ -334,7 +334,7 @@ def __init__(
334334
drs_root=drs_root,
335335
)
336336

337-
nominal_resolution = vocab._get_nominal_resolution()
337+
nominal_resolution = vocab._get_nominal_resolution(target_realm="ocean")
338338
self.supergrid = Supergrid(nominal_resolution)
339339
self.grid_info = None
340340
self.grid_type = None

src/access_moppy/sea_ice.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(
4646
resampling_method=resampling_method,
4747
)
4848

49-
nominal_resolution = vocab._get_nominal_resolution()
49+
nominal_resolution = vocab._get_nominal_resolution(target_realm="seaIce")
5050
self.supergrid = Supergrid(nominal_resolution)
5151
self.grid_info = None
5252
self.grid_type = None

src/access_moppy/vocabulary_processors.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,8 +941,22 @@ def _get_institution(self) -> str:
941941

942942
return ",".join(institution_ids)
943943

944-
def _get_nominal_resolution(self) -> Optional[str]:
944+
def _get_nominal_resolution(
945+
self, target_realm: Optional[str] = None
946+
) -> Optional[str]:
945947
realm = self.variable.get("modeling_realm")
948+
if realm and len(realm.split()) > 1:
949+
if target_realm is None:
950+
raise ValueError(
951+
f"Variable has multiple modeling realms: '{realm}'. "
952+
f"Please specify 'target_realm' (one of: {realm.split()})."
953+
)
954+
if target_realm not in realm.split():
955+
raise ValueError(
956+
f"target_realm '{target_realm}' not found in variable's modeling realms: '{realm}'. "
957+
f"Must be one of: {realm.split()}."
958+
)
959+
realm = target_realm
946960
try:
947961
return self.source["model_component"][realm]["native_nominal_resolution"]
948962
except KeyError:

tests/unit/test_vocabulary_processors.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,3 +678,134 @@ def test_cmip7_generate_filename_numeric_time_branch(cmip7_vocab_instance):
678678

679679
assert "202001" in filename
680680
assert "202002" in filename
681+
682+
683+
def _make_cmip6_vocab(
684+
mock_vocab_data, mock_table_data, modeling_realm, source_components
685+
):
686+
vocab_data = dict(mock_vocab_data)
687+
vocab_data["source_id"] = {
688+
"ACCESS-ESM1.6": {
689+
**vocab_data["source_id"]["ACCESS-ESM1.6"],
690+
"model_component": source_components,
691+
}
692+
}
693+
table_data = {
694+
"Header": mock_table_data["Header"],
695+
"variable_entry": {
696+
"tas": {
697+
**mock_table_data["variable_entry"]["tas"],
698+
"modeling_realm": modeling_realm,
699+
}
700+
},
701+
}
702+
with (
703+
patch.object(
704+
CMIP6Vocabulary, "_load_controlled_vocab", return_value=vocab_data
705+
),
706+
patch.object(CMIP6Vocabulary, "_load_table", return_value=table_data),
707+
):
708+
return CMIP6Vocabulary(
709+
compound_name="Amon.tas",
710+
experiment_id="piControl",
711+
source_id="ACCESS-ESM1.6",
712+
variant_label="r1i1p1f1",
713+
grid_label="gn",
714+
)
715+
716+
717+
@pytest.mark.unit
718+
def test_get_nominal_resolution_single_realm(mock_vocab_data, mock_table_data):
719+
"""Single realm: returns native_nominal_resolution without needing target_realm."""
720+
vocab = _make_cmip6_vocab(
721+
mock_vocab_data,
722+
mock_table_data,
723+
modeling_realm="atmos",
724+
source_components={"atmos": {"native_nominal_resolution": "100 km"}},
725+
)
726+
assert vocab._get_nominal_resolution() == "100 km"
727+
728+
729+
@pytest.mark.unit
730+
def test_get_nominal_resolution_single_realm_missing_key(
731+
mock_vocab_data, mock_table_data
732+
):
733+
"""Single realm with no native_nominal_resolution key returns None."""
734+
vocab = _make_cmip6_vocab(
735+
mock_vocab_data,
736+
mock_table_data,
737+
modeling_realm="atmos",
738+
source_components={"atmos": {"description": "no resolution here"}},
739+
)
740+
assert vocab._get_nominal_resolution() is None
741+
742+
743+
@pytest.mark.unit
744+
def test_get_nominal_resolution_multiple_realms_no_target_raises(
745+
mock_vocab_data, mock_table_data
746+
):
747+
"""Multiple modeling realms without target_realm raises ValueError."""
748+
vocab = _make_cmip6_vocab(
749+
mock_vocab_data,
750+
mock_table_data,
751+
modeling_realm="atmos ocean",
752+
source_components={
753+
"atmos": {"native_nominal_resolution": "100 km"},
754+
"ocean": {"native_nominal_resolution": "50 km"},
755+
},
756+
)
757+
with pytest.raises(ValueError, match="multiple modeling realms"):
758+
vocab._get_nominal_resolution()
759+
760+
761+
@pytest.mark.unit
762+
def test_get_nominal_resolution_multiple_realms_invalid_target_raises(
763+
mock_vocab_data, mock_table_data
764+
):
765+
"""target_realm not in the variable's realms raises ValueError."""
766+
vocab = _make_cmip6_vocab(
767+
mock_vocab_data,
768+
mock_table_data,
769+
modeling_realm="atmos ocean",
770+
source_components={
771+
"atmos": {"native_nominal_resolution": "100 km"},
772+
"ocean": {"native_nominal_resolution": "50 km"},
773+
},
774+
)
775+
with pytest.raises(ValueError, match="not found in variable's modeling realms"):
776+
vocab._get_nominal_resolution(target_realm="land")
777+
778+
779+
@pytest.mark.unit
780+
def test_get_nominal_resolution_multiple_realms_valid_target(
781+
mock_vocab_data, mock_table_data
782+
):
783+
"""With a valid target_realm, returns resolution for the specified realm."""
784+
vocab = _make_cmip6_vocab(
785+
mock_vocab_data,
786+
mock_table_data,
787+
modeling_realm="atmos ocean",
788+
source_components={
789+
"atmos": {"native_nominal_resolution": "100 km"},
790+
"ocean": {"native_nominal_resolution": "50 km"},
791+
},
792+
)
793+
assert vocab._get_nominal_resolution(target_realm="atmos") == "100 km"
794+
assert vocab._get_nominal_resolution(target_realm="ocean") == "50 km"
795+
796+
797+
@pytest.mark.unit
798+
def test_get_nominal_resolution_multiple_realms_target_missing_key(
799+
mock_vocab_data, mock_table_data
800+
):
801+
"""Valid target_realm but no native_nominal_resolution in that component returns None."""
802+
vocab = _make_cmip6_vocab(
803+
mock_vocab_data,
804+
mock_table_data,
805+
modeling_realm="atmos ocean",
806+
source_components={
807+
"atmos": {"description": "no resolution"},
808+
"ocean": {"native_nominal_resolution": "50 km"},
809+
},
810+
)
811+
assert vocab._get_nominal_resolution(target_realm="atmos") is None

0 commit comments

Comments
 (0)