@@ -296,6 +296,24 @@ def __setitem__(self, key, value):
296296 def __repr__ (self ):
297297 return repr (self .ds )
298298
299+ def _is_fx_variable (self ) -> bool :
300+ """Return True when compound_name corresponds to a fixed-field CMIP table."""
301+ if not self .compound_name :
302+ return False
303+
304+ table_id = self .compound_name .split ("." , 1 )[0 ].lower ()
305+ return table_id .endswith ("fx" )
306+
307+ def _squeeze_fx_singleton_time (self ) -> None :
308+ """Drop UM-style singleton time axis for fixed (fx) variables."""
309+ if (
310+ self .ds is not None
311+ and self ._is_fx_variable ()
312+ and "time" in self .ds .dims
313+ and self .ds .sizes .get ("time" ) == 1
314+ ):
315+ self .ds = self .ds .isel (time = 0 , drop = True )
316+
299317 def load_dataset (self , required_vars : Optional [List [str ]] = None ):
300318 """
301319 Load dataset from input files or use provided xarray objects with optional frequency validation.
@@ -363,11 +381,30 @@ def load_dataset(self, required_vars: Optional[List[str]] = None):
363381 # Original file-based loading logic
364382 def _preprocess (ds ):
365383 ds = ds [list (required_vars & set (ds .data_vars ))]
366- # Drop auxiliary UM time coordinates (time_0, time_1) that differ
367- # across files. Without this, xr.open_mfdataset's join='outer'
368- # unions every distinct value into a growing dimension, inflating
369- # the Dask task graph to several GiB before any computation starts.
370- aux_time_coords = [c for c in ("time_0" , "time_1" ) if c in ds ]
384+ # Canonicalize UM auxiliary time dimensions (time_0/time_1) to
385+ # a single "time" axis when the selected variables use exactly
386+ # one such axis. Keep that primary axis and drop the unused one.
387+ selected_data_vars = list (ds .data_vars )
388+ used_time_dims = {
389+ dim
390+ for var in selected_data_vars
391+ for dim in ds [var ].dims
392+ if dim .startswith ("time" )
393+ }
394+ primary_time_dim = "time" if "time" in used_time_dims else None
395+ if primary_time_dim is None and len (used_time_dims ) == 1 :
396+ primary_time_dim = next (iter (used_time_dims ))
397+
398+ if primary_time_dim and primary_time_dim != "time" :
399+ ds = ds .rename ({primary_time_dim : "time" })
400+ used_time_dims .discard (primary_time_dim )
401+ used_time_dims .add ("time" )
402+
403+ aux_time_coords = [
404+ c
405+ for c in ("time_0" , "time_1" )
406+ if c in ds .coords and c not in used_time_dims
407+ ]
371408 if aux_time_coords :
372409 ds = ds .drop_vars (aux_time_coords )
373410 return ds
@@ -383,8 +420,9 @@ def _preprocess(ds):
383420 if required_vars
384421 else list (_probe .data_vars )
385422 )
386- _has_time = (required_vars is None or "time" in required_vars ) and any (
387- "time" in _probe [v ].dims for v in _probe_target_vars
423+ _has_time = any (
424+ any (dim .startswith ("time" ) for dim in _probe [v ].dims )
425+ for v in _probe_target_vars
388426 )
389427
390428 # Validate frequency consistency and CMIP6 compatibility before concatenation
@@ -394,7 +432,7 @@ def _preprocess(ds):
394432 # Time-independent variables typically have "fx" (fixed) in their table ID
395433 is_time_independent = (
396434 self .compound_name and "fx" in self .compound_name .lower ()
397- ) or "time" not in _probe_dims
435+ ) or not any ( dim . startswith ( "time" ) for dim in _probe_dims )
398436
399437 if is_time_independent :
400438 logger .debug (
@@ -496,11 +534,13 @@ def _preprocess(ds):
496534 if required_vars :
497535 vars_to_keep = [v for v in required_vars if v in self .ds .data_vars ]
498536 self .ds = self .ds [vars_to_keep ]
499- # UM source files always include a time dimension (size=1) even for
500- # static fields. Drop it so downstream CMOR processing sees the
501- # expected (lat, lon) shape rather than (time=1, lat, lon).
502- if "time" in self .ds .dims :
503- self .ds = self .ds .isel (time = 0 , drop = True )
537+ # UM source files can include a time=1 axis for static fields.
538+ # Keep squeeze behavior centralized in _squeeze_fx_singleton_time().
539+
540+ # UM source files can carry time=1 for fixed fields even when loaded
541+ # through the time-aware branch. Squeeze once here before any downstream
542+ # frequency handling, rechunking, or missing-value normalization.
543+ self ._squeeze_fx_singleton_time ()
504544
505545 # Apply temporal resampling if enabled and needed
506546 if self .enable_resampling and self .compound_name :
0 commit comments