Skip to content

Commit c9ee6d7

Browse files
committed
Work on ocean support
1 parent b4beddd commit c9ee6d7

File tree

5 files changed

+418
-67
lines changed

5 files changed

+418
-67
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ v2.0.0
88
------
99

1010
- Fully supported by ACCESS-NRI
11+
- Remove dependency on CMOR
1112
- Remove MOPPeR database
1213
- Remove integration with NCI queue system (PBS)

GettingStartedOcean.ipynb

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "66ea26fc-9f2f-4667-92ff-a46ea27a16d5",
7+
"metadata": {},
8+
"outputs": [
9+
{
10+
"name": "stdout",
11+
"output_type": "stream",
12+
"text": [
13+
"\n",
14+
"Loaded Configuration:\n",
15+
"Creator Name: Romain Beucher\n",
16+
"Organisation: ACCESS-NRI\n",
17+
"Creator Email: romain.beucher@anu.edu.au\n",
18+
"Creator URL: 0000-0003-3891-5444\n"
19+
]
20+
}
21+
],
22+
"source": [
23+
"from access_mopper.vocabulary_processors import CMIP6Vocabulary"
24+
]
25+
},
26+
{
27+
"cell_type": "code",
28+
"execution_count": 2,
29+
"id": "e1601178-2f7c-4538-ab98-8e856d4185c2",
30+
"metadata": {},
31+
"outputs": [],
32+
"source": [
33+
"input_paths=\"/home/romain/PROJECTS/ACCESS-MOPPeR/ACCESS-MOPPeR/tests/data/esm1-6/ocean/ocean-2d-surface_temp-1monthly-mean-ym_0966_01.nc\""
34+
]
35+
},
36+
{
37+
"cell_type": "code",
38+
"execution_count": 3,
39+
"id": "fd293ca3-d6e2-4fdc-935c-bd43d06d3110",
40+
"metadata": {},
41+
"outputs": [],
42+
"source": [
43+
"parent_experiment_config = {\n",
44+
" \"parent_experiment_id\": \"piControl\",\n",
45+
" \"parent_activity_id\": \"CMIP\",\n",
46+
" \"parent_source_id\": \"ACCESS-ESM1-5\",\n",
47+
" \"parent_variant_label\": \"r1i1p1f1\",\n",
48+
" \"parent_time_units\": \"days since 0001-01-01 00:00:00\",\n",
49+
" \"parent_mip_era\": \"CMIP6\",\n",
50+
" \"branch_time_in_child\": 0.0,\n",
51+
" \"branch_time_in_parent\": 54786.0,\n",
52+
" \"branch_method\": \"standard\"\n",
53+
"}"
54+
]
55+
},
56+
{
57+
"cell_type": "code",
58+
"execution_count": 4,
59+
"id": "fb371092-d40d-487d-8b4d-28cede52e6bb",
60+
"metadata": {},
61+
"outputs": [],
62+
"source": [
63+
"voc = CMIP6Vocabulary(\n",
64+
" compound_name=\"Omon.tos\",\n",
65+
" experiment_id=\"historical\",\n",
66+
" source_id=\"ACCESS-ESM1-5\",\n",
67+
" variant_label=\"r1i1p1f1\",\n",
68+
" grid_label=\"gn\",\n",
69+
" activity_id=\"CMIP\",\n",
70+
" parent_info=parent_experiment_config)"
71+
]
72+
},
73+
{
74+
"cell_type": "code",
75+
"execution_count": 5,
76+
"id": "d7ad164d-46a4-4d73-ab2d-c09fc6c1e257",
77+
"metadata": {},
78+
"outputs": [],
79+
"source": [
80+
"from access_mopper.cmip6_cmoriser import CMIP6_Ocean_CMORiser\n",
81+
"from access_mopper.utilities import load_cmip6_mappings\n",
82+
"from access_mopper.ocean_supergrid import Supergrid"
83+
]
84+
},
85+
{
86+
"cell_type": "code",
87+
"execution_count": 6,
88+
"id": "f5e56ce6-ee0e-4c38-9526-f08746fbc49e",
89+
"metadata": {},
90+
"outputs": [],
91+
"source": [
92+
"variable_mapping = load_cmip6_mappings(\"Omon.tos\")"
93+
]
94+
},
95+
{
96+
"cell_type": "code",
97+
"execution_count": 7,
98+
"id": "397687e8-46de-4d3f-87fd-0682c00afede",
99+
"metadata": {},
100+
"outputs": [],
101+
"source": [
102+
"supergrid = \"/home/romain/PROJECTS/ACCESS-MOPPeR/grids/access-om2/input_20201102/mom_1deg/ocean_hgrid.nc\""
103+
]
104+
},
105+
{
106+
"cell_type": "code",
107+
"execution_count": 8,
108+
"id": "51cbaca6-bf14-4849-b074-dd709f4c822e",
109+
"metadata": {},
110+
"outputs": [],
111+
"source": [
112+
"cmoriser = CMIP6_Ocean_CMORiser(\n",
113+
" input_paths=input_paths,\n",
114+
" output_path=\".\",\n",
115+
" cmor_name=voc.cmor_name,\n",
116+
" cmip6_vocab=voc,\n",
117+
" variable_mapping=variable_mapping,\n",
118+
" supergrid_path=supergrid\n",
119+
")"
120+
]
121+
},
122+
{
123+
"cell_type": "code",
124+
"execution_count": 9,
125+
"id": "54efceb4-1d4f-4f51-ba5d-8a2644aa7652",
126+
"metadata": {},
127+
"outputs": [
128+
{
129+
"name": "stdout",
130+
"output_type": "stream",
131+
"text": [
132+
"CMORised output written to tos_Omon_ACCESS-ESM1-5_historical_r1i1p1f1_gn_096601-096612.nc\n"
133+
]
134+
}
135+
],
136+
"source": [
137+
"cmoriser.run()"
138+
]
139+
}
140+
],
141+
"metadata": {
142+
"kernelspec": {
143+
"display_name": "Python 3 (ipykernel)",
144+
"language": "python",
145+
"name": "python3"
146+
},
147+
"language_info": {
148+
"codemirror_mode": {
149+
"name": "ipython",
150+
"version": 3
151+
},
152+
"file_extension": ".py",
153+
"mimetype": "text/x-python",
154+
"name": "python",
155+
"nbconvert_exporter": "python",
156+
"pygments_lexer": "ipython3",
157+
"version": "3.11.11"
158+
}
159+
},
160+
"nbformat": 4,
161+
"nbformat_minor": 5
162+
}

src/access_mopper/cmip6_cmoriser.py

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import xarray as xr
88
from cftime import num2date
99

10+
from access_mopper.ocean_supergrid import Supergrid
1011
from access_mopper.utilities import load_cmip6_mappings, type_mapping
1112
from access_mopper.vocabulary_processors import CMIP6Vocabulary
1213

@@ -303,7 +304,10 @@ def write(self):
303304
for k, v in attrs.items():
304305
dst.setncattr(k, v)
305306
for dim, size in self.ds.sizes.items():
306-
dst.createDimension(dim, size)
307+
if dim == "time":
308+
dst.createDimension(dim, None) # Unlimited dimension
309+
else:
310+
dst.createDimension(dim, size)
307311
for var in self.ds.variables:
308312
vdat = self.ds[var]
309313
fill = None if var.endswith("_bnds") else vdat.attrs.get("_FillValue")
@@ -329,6 +333,152 @@ def run(self):
329333
self.write()
330334

331335

336+
class CMIP6_Ocean_CMORiser(CMIP6_CMORiser):
337+
"""
338+
CMORiser subclass for ocean variables using curvilinear supergrid coordinates.
339+
"""
340+
341+
def __init__(
342+
self,
343+
input_paths: Union[str, List[str]],
344+
output_path: str,
345+
cmor_name: str,
346+
cmip6_vocab: Any,
347+
variable_mapping: Dict[str, Any],
348+
supergrid_path: Union[str, Path],
349+
drs_root: Optional[Path] = None,
350+
):
351+
super().__init__(
352+
input_paths=input_paths,
353+
output_path=output_path,
354+
cmor_name=cmor_name,
355+
cmip6_vocab=cmip6_vocab,
356+
variable_mapping=variable_mapping,
357+
drs_root=drs_root,
358+
)
359+
self.supergrid = Supergrid(supergrid_path)
360+
self.grid_info = None
361+
self.grid_type = None
362+
363+
def infer_grid_type(self):
364+
coord_sets = {
365+
"T": {"xt_ocean", "yt_ocean"},
366+
"U": {"xu_ocean", "yu_ocean"},
367+
"V": {"xv_ocean", "yv_ocean"},
368+
"Q": {"xq_ocean", "yq_ocean"},
369+
}
370+
present_coords = set(self.ds.coords)
371+
for grid, required in coord_sets.items():
372+
if required.issubset(present_coords):
373+
return grid
374+
raise ValueError("Could not infer grid type from dataset coordinates.")
375+
376+
def select_and_process_variables(self):
377+
input_vars = self.mapping[self.cmor_name]["model_variables"]
378+
calc = self.mapping[self.cmor_name]["calculation"]
379+
380+
self.ds = self.ds[input_vars]
381+
382+
if calc["type"] == "direct":
383+
self.ds[self.cmor_name] = self.ds[input_vars[0]]
384+
elif calc["type"] == "formula":
385+
local = {var: self.ds[var] for var in input_vars}
386+
self.ds[self.cmor_name] = eval(calc["formula"], {}, local)
387+
else:
388+
raise ValueError(f"Unsupported calculation type: {calc['type']}")
389+
390+
dim_rename = {
391+
"xt_ocean": "i",
392+
"yt_ocean": "j",
393+
"xu_ocean": "i",
394+
"yu_ocean": "j",
395+
"xq_ocean": "i",
396+
"yq_ocean": "j",
397+
"xv_ocean": "i",
398+
"yv_ocean": "j",
399+
}
400+
dims_to_rename = {
401+
k: v for k, v in dim_rename.items() if k in self.ds[self.cmor_name].dims
402+
}
403+
self.ds[self.cmor_name] = self.ds[self.cmor_name].rename(dims_to_rename)
404+
self.ds[self.cmor_name] = self.ds[self.cmor_name].transpose("time", "j", "i")
405+
406+
self.grid_type = self.infer_grid_type()
407+
# Drop all other data variables except the CMOR variable
408+
self.ds = self.ds[[self.cmor_name]]
409+
410+
# Drop unused coordinates
411+
used_coords = set()
412+
for dim in self.ds[self.cmor_name].dims:
413+
if dim in self.ds.coords:
414+
used_coords.add(dim)
415+
else:
416+
# Might be implicit dimension (e.g. from formula), check all coords
417+
for coord in self.ds.coords:
418+
if dim in self.ds[coord].dims:
419+
used_coords.add(coord)
420+
self.ds = self.ds.drop_vars([c for c in self.ds.coords if c not in used_coords])
421+
422+
def update_attributes(self):
423+
grid_type = self.grid_type
424+
self.grid_info = self.supergrid.extract_grid(grid_type)
425+
426+
self.ds = self.ds.assign_coords(
427+
{
428+
"i": self.grid_info["i"],
429+
"j": self.grid_info["j"],
430+
"vertices": self.grid_info["vertices"],
431+
}
432+
)
433+
434+
self.ds["latitude"] = self.grid_info["latitude"]
435+
self.ds["longitude"] = self.grid_info["longitude"]
436+
self.ds["vertices_latitude"] = self.grid_info["vertices_latitude"]
437+
self.ds["vertices_longitude"] = self.grid_info["vertices_longitude"]
438+
439+
self.ds["latitude"].attrs.update(
440+
{
441+
"standard_name": "latitude",
442+
"units": "degrees_north",
443+
"bounds": "vertices_latitude",
444+
}
445+
)
446+
self.ds["longitude"].attrs.update(
447+
{
448+
"standard_name": "longitude",
449+
"units": "degrees_east",
450+
"bounds": "vertices_longitude",
451+
}
452+
)
453+
self.ds["vertices_latitude"].attrs.update(
454+
{"standard_name": "latitude", "units": "degrees_north"}
455+
)
456+
self.ds["vertices_longitude"].attrs.update(
457+
{"standard_name": "longitude", "units": "degrees_east"}
458+
)
459+
460+
self.ds.attrs = {
461+
k: v
462+
for k, v in self.vocab.get_required_global_attributes().items()
463+
if v not in (None, "")
464+
}
465+
466+
if "nv" in self.ds.dims:
467+
self.ds = self.ds.rename_dims({"nv": "bnds"}).rename_vars({"nv": "bnds"})
468+
self.ds["bnds"].attrs.update(
469+
{"long_name": "vertex number of the bounds", "units": "1"}
470+
)
471+
472+
cmor_attrs = self.vocab.variable
473+
self.ds[self.cmor_name].attrs.update(
474+
{k: v for k, v in cmor_attrs.items() if v not in (None, "")}
475+
)
476+
var_type = cmor_attrs.get("type", "double")
477+
self.ds[self.cmor_name] = self.ds[self.cmor_name].astype(
478+
self.type_mapping.get(var_type, np.float64)
479+
)
480+
481+
332482
class CMIP6Workflow:
333483
"""
334484
Coordinates the CMORisation process using CMIP6Vocabulary and CMORiser.

src/access_mopper/mappings/Mappings_CMIP6_Omon.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -663,19 +663,19 @@
663663
}
664664
},
665665
"tos": {
666-
"dimensions": [
667-
"time",
668-
"yt_ocean",
669-
"xt_ocean"
670-
],
666+
"dimensions": {
667+
"time": "time",
668+
"lon": "yt_ocean",
669+
"lat": "xt_ocean"
670+
},
671671
"units": "K",
672672
"positive": null,
673673
"model_variables": [
674-
"sst"
674+
"surface_temp"
675675
],
676676
"calculation": {
677-
"type": "formula",
678-
"formula": "K_degC(sst)"
677+
"type": "direct",
678+
"formula": "surface_temp"
679679
}
680680
},
681681
"umo": {

0 commit comments

Comments
 (0)