1414
1515class CMIP6_CMORiser :
1616 """
17- Handles CMORisation of NetCDF datasets using CMIP6 metadata .
17+ Base class for CMIP6 CMORisers, providing shared logic for CMORisation .
1818 """
1919
2020 type_mapping = type_mapping
@@ -45,31 +45,9 @@ def load_dataset(self):
4545 )
4646
4747 def select_and_process_variables (self ):
48- bnds_required = [
49- v ["out_name" ] + "_bnds"
50- for v in self .vocab .axes .values ()
51- if v .get ("must_have_bounds" ) == "yes"
52- ]
53-
54- input_vars = self .mapping [self .cmor_name ]["model_variables" ]
55- calc = self .mapping [self .cmor_name ]["calculation" ]
56- self .ds = self .ds [input_vars + bnds_required ]
57-
58- if calc ["type" ] == "direct" :
59- self .ds = self .ds .rename ({input_vars [0 ]: self .cmor_name })
60- elif calc ["type" ] == "formula" :
61- local = {var : self .ds [var ] for var in input_vars }
62- self .ds [self .cmor_name ] = eval (calc ["formula" ], {}, local )
63- else :
64- raise ValueError (f"Unsupported calculation type: { calc ['type' ]} " )
65-
66- cmor_dims = self .vocab .variable ["dimensions" ].split ()
67- transpose_order = [
68- self .vocab .axes [dim ]["out_name" ]
69- for dim in cmor_dims
70- if "value" not in self .vocab .axes [dim ]
71- ]
72- self .ds [self .cmor_name ] = self .ds [self .cmor_name ].transpose (* transpose_order )
48+ raise NotImplementedError (
49+ "Subclasses must implement select_and_process_variables."
50+ )
7351
7452 def _check_units (self , var : str , expected : str ) -> bool :
7553 actual = self .ds [var ].attrs .get ("units" )
@@ -118,106 +96,7 @@ def drop_intermediates(self):
11896 self .ds = self .ds .drop_vars (var )
11997
12098 def update_attributes (self ):
121- self .ds .attrs = {
122- k : v
123- for k , v in self .vocab .get_required_global_attributes ().items ()
124- if v not in (None , "" )
125- }
126-
127- self .ds = self .ds .rename (self .mapping [self .cmor_name ]["dimensions" ])
128-
129- required_coords = {
130- v ["out_name" ] for v in self .vocab .axes .values () if "value" in v
131- }.union ({v ["out_name" ] for v in self .vocab .axes .values ()})
132- self .ds = self .ds .drop_vars (
133- [c for c in self .ds .coords if c not in required_coords ], errors = "ignore"
134- )
135-
136- cmor_attrs = self .vocab .variable
137- self ._check_units (self .cmor_name , cmor_attrs .get ("units" ))
138-
139- self .ds [self .cmor_name ].attrs .update (
140- {k : v for k , v in cmor_attrs .items () if v not in (None , "" )}
141- )
142- var_type = cmor_attrs .get ("type" , "double" )
143- self .ds [self .cmor_name ] = self .ds [self .cmor_name ].astype (
144- self .type_mapping .get (var_type , np .float64 )
145- )
146-
147- try :
148- if cmor_attrs .get ("valid_min" ) not in (None , "" ) and cmor_attrs .get (
149- "valid_max"
150- ) not in (None , "" ):
151- vmin = self .type_mapping .get (var_type , np .float64 )(
152- cmor_attrs ["valid_min" ]
153- )
154- vmax = self .type_mapping .get (var_type , np .float64 )(
155- cmor_attrs ["valid_max" ]
156- )
157- self ._check_range (self .cmor_name , vmin , vmax )
158- except ValueError as e :
159- raise ValueError (
160- f"Failed to validate value range for { self .cmor_name } : { e } "
161- )
162-
163- for dim , meta in self .vocab .axes .items ():
164- name = meta ["out_name" ]
165- dtype = self .type_mapping .get (meta .get ("type" , "double" ), np .float64 )
166- if name in self .ds :
167- self ._check_units (name , meta .get ("units" , "" ))
168- if meta .get ("standard_name" ) == "time" :
169- self ._check_calendar (name )
170- original_units = self .ds [name ].attrs .get ("units" , "" )
171- coord_attrs = {
172- k : v
173- for k , v in {
174- "standard_name" : meta .get ("standard_name" ),
175- "long_name" : meta .get ("long_name" ),
176- "units" : meta .get ("units" ),
177- "axis" : meta .get ("axis" ),
178- "positive" : meta .get ("positive" ),
179- "valid_min" : dtype (meta ["valid_min" ])
180- if "valid_min" in meta
181- else None ,
182- "valid_max" : dtype (meta ["valid_max" ])
183- if "valid_max" in meta
184- else None ,
185- }.items ()
186- if v is not None
187- }
188- if coord_attrs .get (
189- "units"
190- ) == "days since ?" and original_units .lower ().startswith ("days since" ):
191- coord_attrs ["units" ] = original_units
192- updated = self .ds [name ].astype (dtype )
193- updated .attrs .update (coord_attrs )
194- self .ds [name ] = updated
195- elif "value" in meta :
196- self .ds = self .ds .assign_coords (
197- {
198- name : xr .DataArray (
199- dtype (meta ["value" ]),
200- dims = (),
201- attrs = {
202- k : v
203- for k , v in {
204- "standard_name" : meta .get ("standard_name" ),
205- "long_name" : meta .get ("long_name" ),
206- "units" : meta .get ("units" ),
207- "axis" : meta .get ("axis" ),
208- "positive" : meta .get ("positive" ),
209- "valid_min" : dtype (meta ["valid_min" ])
210- if "valid_min" in meta
211- else None ,
212- "valid_max" : dtype (meta ["valid_max" ])
213- if "valid_max" in meta
214- else None ,
215- }.items ()
216- if v is not None
217- },
218- )
219- }
220- )
99+ raise NotImplementedError ("Subclasses must implement update_attributes." )
221100
222101 def reorder (self ):
223102 def ordered (ds , core = ("lat" , "lon" , "time" , "height" )):
@@ -333,6 +212,141 @@ def run(self):
333212 self .write ()
334213
335214
215+ class CMIP6_Atmosphere_CMORiser (CMIP6_CMORiser ):
216+ """
217+ Handles CMORisation of NetCDF datasets using CMIP6 metadata (Atmosphere/Land).
218+ """
219+
220+ def select_and_process_variables (self ):
221+ bnds_required = [
222+ v ["out_name" ] + "_bnds"
223+ for v in self .vocab .axes .values ()
224+ if v .get ("must_have_bounds" ) == "yes"
225+ ]
226+
227+ input_vars = self .mapping [self .cmor_name ]["model_variables" ]
228+ calc = self .mapping [self .cmor_name ]["calculation" ]
229+ self .ds = self .ds [input_vars + bnds_required ]
230+
231+ if calc ["type" ] == "direct" :
232+ self .ds = self .ds .rename ({input_vars [0 ]: self .cmor_name })
233+ elif calc ["type" ] == "formula" :
234+ local = {var : self .ds [var ] for var in input_vars }
235+ self .ds [self .cmor_name ] = eval (calc ["formula" ], {}, local )
236+ else :
237+ raise ValueError (f"Unsupported calculation type: { calc ['type' ]} " )
238+
239+ cmor_dims = self .vocab .variable ["dimensions" ].split ()
240+ transpose_order = [
241+ self .vocab .axes [dim ]["out_name" ]
242+ for dim in cmor_dims
243+ if "value" not in self .vocab .axes [dim ]
244+ ]
245+ self .ds [self .cmor_name ] = self .ds [self .cmor_name ].transpose (* transpose_order )
246+
247+ def update_attributes (self ):
248+ self .ds .attrs = {
249+ k : v
250+ for k , v in self .vocab .get_required_global_attributes ().items ()
251+ if v not in (None , "" )
252+ }
253+
254+ self .ds = self .ds .rename (self .mapping [self .cmor_name ]["dimensions" ])
255+
256+ required_coords = {
257+ v ["out_name" ] for v in self .vocab .axes .values () if "value" in v
258+ }.union ({v ["out_name" ] for v in self .vocab .axes .values ()})
259+ self .ds = self .ds .drop_vars (
260+ [c for c in self .ds .coords if c not in required_coords ], errors = "ignore"
261+ )
262+
263+ cmor_attrs = self .vocab .variable
264+ self ._check_units (self .cmor_name , cmor_attrs .get ("units" ))
265+
266+ self .ds [self .cmor_name ].attrs .update (
267+ {k : v for k , v in cmor_attrs .items () if v not in (None , "" )}
268+ )
269+ var_type = cmor_attrs .get ("type" , "double" )
270+ self .ds [self .cmor_name ] = self .ds [self .cmor_name ].astype (
271+ self .type_mapping .get (var_type , np .float64 )
272+ )
273+
274+ try :
275+ if cmor_attrs .get ("valid_min" ) not in (None , "" ) and cmor_attrs .get (
276+ "valid_max"
277+ ) not in (None , "" ):
278+ vmin = self .type_mapping .get (var_type , np .float64 )(
279+ cmor_attrs ["valid_min" ]
280+ )
281+ vmax = self .type_mapping .get (var_type , np .float64 )(
282+ cmor_attrs ["valid_max" ]
283+ )
284+ self ._check_range (self .cmor_name , vmin , vmax )
285+ except ValueError as e :
286+ raise ValueError (
287+ f"Failed to validate value range for { self .cmor_name } : { e } "
288+ )
289+
290+ for dim , meta in self .vocab .axes .items ():
291+ name = meta ["out_name" ]
292+ dtype = self .type_mapping .get (meta .get ("type" , "double" ), np .float64 )
293+ if name in self .ds :
294+ self ._check_units (name , meta .get ("units" , "" ))
295+ if meta .get ("standard_name" ) == "time" :
296+ self ._check_calendar (name )
297+ original_units = self .ds [name ].attrs .get ("units" , "" )
298+ coord_attrs = {
299+ k : v
300+ for k , v in {
301+ "standard_name" : meta .get ("standard_name" ),
302+ "long_name" : meta .get ("long_name" ),
303+ "units" : meta .get ("units" ),
304+ "axis" : meta .get ("axis" ),
305+ "positive" : meta .get ("positive" ),
306+ "valid_min" : dtype (meta ["valid_min" ])
307+ if "valid_min" in meta
308+ else None ,
309+ "valid_max" : dtype (meta ["valid_max" ])
310+ if "valid_max" in meta
311+ else None ,
312+ }.items ()
313+ if v is not None
314+ }
315+ if coord_attrs .get (
316+ "units"
317+ ) == "days since ?" and original_units .lower ().startswith ("days since" ):
318+ coord_attrs ["units" ] = original_units
319+ updated = self .ds [name ].astype (dtype )
320+ updated .attrs .update (coord_attrs )
321+ self .ds [name ] = updated
322+ elif "value" in meta :
323+ self .ds = self .ds .assign_coords (
324+ {
325+ name : xr .DataArray (
326+ dtype (meta ["value" ]),
327+ dims = (),
328+ attrs = {
329+ k : v
330+ for k , v in {
331+ "standard_name" : meta .get ("standard_name" ),
332+ "long_name" : meta .get ("long_name" ),
333+ "units" : meta .get ("units" ),
334+ "axis" : meta .get ("axis" ),
335+ "positive" : meta .get ("positive" ),
336+ "valid_min" : dtype (meta ["valid_min" ])
337+ if "valid_min" in meta
338+ else None ,
339+ "valid_max" : dtype (meta ["valid_max" ])
340+ if "valid_max" in meta
341+ else None ,
342+ }.items ()
343+ if v is not None
344+ },
345+ )
346+ }
347+ )
348+
349+
336350class CMIP6_Ocean_CMORiser (CMIP6_CMORiser ):
337351 """
338352 CMORiser subclass for ocean variables using curvilinear supergrid coordinates.
@@ -343,9 +357,8 @@ def __init__(
343357 input_paths : Union [str , List [str ]],
344358 output_path : str ,
345359 cmor_name : str ,
346- cmip6_vocab : Any ,
360+ cmip6_vocab : CMIP6Vocabulary ,
347361 variable_mapping : Dict [str , Any ],
348- supergrid_path : Union [str , Path ],
349362 drs_root : Optional [Path ] = None ,
350363 ):
351364 super ().__init__ (
@@ -356,7 +369,9 @@ def __init__(
356369 variable_mapping = variable_mapping ,
357370 drs_root = drs_root ,
358371 )
359- self .supergrid = Supergrid (supergrid_path )
372+
373+ nominal_resolution = cmip6_vocab ._get_nominal_resolution ()
374+ self .supergrid = Supergrid (nominal_resolution )
360375 self .grid_info = None
361376 self .grid_type = None
362377
@@ -479,7 +494,7 @@ def update_attributes(self):
479494 )
480495
481496
482- class CMIP6Workflow :
497+ class ACCESS_ESM_CMORiser :
483498 """
484499 Coordinates the CMORisation process using CMIP6Vocabulary and CMORiser.
485500 Handles DRS, versioning, and orchestrates the workflow.
@@ -515,14 +530,25 @@ def __init__(
515530 parent_info = self .parent_info ,
516531 )
517532
518- self .cmoriser = CMIP6_CMORiser (
519- input_paths = self .input_paths ,
520- output_path = str (self .output_path ),
521- cmor_name = self .vocab .cmor_name ,
522- cmip6_vocab = self .vocab ,
523- variable_mapping = self .variable_mapping ,
524- drs_root = drs_root if drs_root else None ,
525- )
533+ table , cmor_name = compound_name .split ("." )
534+ if table in ("Amon" , "Lmon" , "SImon" , "SImon" ):
535+ self .cmoriser = CMIP6_Atmosphere_CMORiser (
536+ input_paths = self .input_paths ,
537+ output_path = str (self .output_path ),
538+ cmor_name = cmor_name ,
539+ cmip6_vocab = self .vocab ,
540+ variable_mapping = self .variable_mapping ,
541+ drs_root = drs_root if drs_root else None ,
542+ )
543+ elif table in ("Oyr" , "Oday" , "Omon" , "Omon_curvilinear" ):
544+ self .cmoriser = CMIP6_Ocean_CMORiser (
545+ input_paths = self .input_paths ,
546+ output_path = str (self .output_path ),
547+ cmor_name = cmor_name ,
548+ cmip6_vocab = self .vocab ,
549+ variable_mapping = self .variable_mapping ,
550+ drs_root = drs_root if drs_root else None ,
551+ )
526552
527553 def run (self ):
528554 """
0 commit comments