@@ -239,7 +239,15 @@ def name(self):
239239 return self .defn .name ()
240240
241241
242- class _SkeletonCube (namedtuple ("SkeletonCube" , ["signature" , "data" ])):
242+ class _SkeletonCube (
243+ namedtuple (
244+ "SkeletonCube" ,
245+ ["signature" , "data" , "shape" ],
246+ defaults = [
247+ None ,
248+ ],
249+ )
250+ ):
243251 """Basis of a source-cube.
244252
245253 Basis of a source-cube, containing the associated coordinate metadata,
@@ -575,8 +583,6 @@ def concatenate(
575583 """
576584 cube_signatures = []
577585 for cube in cubes :
578- if cube .is_dataless ():
579- raise iris .exceptions .DatalessError ("concatenate" )
580586 cube_signatures .append (_CubeSignature (cube ))
581587
582588 proto_cubes : list [_ProtoCube ] = []
@@ -869,8 +875,13 @@ def match(self, other, error_on_mismatch):
869875 msgs .append (
870876 msg_template .format ("Data dimensions" , "" , self .ndim , other .ndim )
871877 )
872- # Check data type.
873- if self .data_type != other .data_type :
878+ if (
879+ self .data_type is not None
880+ and other .data_type is not None
881+ and self .data_type != other .data_type
882+ ):
883+ # N.B. allow "None" to match any other dtype: this means that dataless
884+ # cubes can merge with 'dataful' ones.
874885 msgs .append (
875886 msg_template .format ("Data types" , "" , self .data_type , other .data_type )
876887 )
@@ -1026,7 +1037,9 @@ def __init__(self, cube_signature):
10261037
10271038 # The list of source-cubes relevant to this proto-cube.
10281039 self ._skeletons = []
1029- self ._add_skeleton (self ._coord_signature , self ._cube .lazy_data ())
1040+ self ._add_skeleton (
1041+ self ._coord_signature , self ._cube .lazy_data (), shape = self ._cube .shape
1042+ )
10301043
10311044 # The nominated axis of concatenation.
10321045 self ._axis = None
@@ -1090,6 +1103,10 @@ def concatenate(self):
10901103
10911104 # Concatenate the new data payload.
10921105 data = self ._build_data ()
1106+ if data is None :
1107+ shape = [coord .shape [0 ] for coord , _dim in dim_coords_and_dims ]
1108+ else :
1109+ shape = None
10931110
10941111 # Build the new cube.
10951112 all_aux_coords_and_dims = aux_coords_and_dims + [
@@ -1098,6 +1115,7 @@ def concatenate(self):
10981115 kwargs = cube_signature .defn ._asdict ()
10991116 cube = iris .cube .Cube (
11001117 data ,
1118+ shape = shape ,
11011119 dim_coords_and_dims = dim_coords_and_dims ,
11021120 aux_coords_and_dims = all_aux_coords_and_dims ,
11031121 cell_measures_and_dims = cell_measures_and_dims ,
@@ -1268,7 +1286,11 @@ def check_coord_match(coord_type: str) -> tuple[bool, str]:
12681286
12691287 if match :
12701288 # Register the cube as a source-cube for this proto-cube.
1271- self ._add_skeleton (coord_signature , cube_signature .src_cube .lazy_data ())
1289+ self ._add_skeleton (
1290+ coord_signature ,
1291+ cube_signature .src_cube .lazy_data (),
1292+ shape = cube_signature .src_cube .shape ,
1293+ )
12721294 # Declare the nominated axis of concatenation.
12731295 self ._axis = candidate_axis
12741296 # If the protocube dimension order is constant (indicating it was
@@ -1284,7 +1306,7 @@ def check_coord_match(coord_type: str) -> tuple[bool, str]:
12841306
12851307 return match , mismatch_error_msg
12861308
1287- def _add_skeleton (self , coord_signature , data ):
1309+ def _add_skeleton (self , coord_signature , data , shape = None ):
12881310 """Create and add the source-cube skeleton to the :class:`_ProtoCube`.
12891311
12901312 Parameters
@@ -1298,7 +1320,7 @@ def _add_skeleton(self, coord_signature, data):
12981320 source-cube.
12991321
13001322 """
1301- skeleton = _SkeletonCube (coord_signature , data )
1323+ skeleton = _SkeletonCube (coord_signature , data , shape )
13021324 self ._skeletons .append (skeleton )
13031325
13041326 def _build_aux_coordinates (self ):
@@ -1534,9 +1556,22 @@ def _build_data(self):
15341556
15351557 """
15361558 skeletons = self ._skeletons
1537- data = [skeleton .data for skeleton in skeletons ]
15381559
1539- data = concatenate_arrays (data , self .axis )
1560+ if all (skeleton .data is None for skeleton in skeletons ):
1561+ data = None
1562+ else :
1563+ data = []
1564+ for skeleton in skeletons :
1565+ if skeleton .data is None :
1566+ skeleton_data = da .ma .masked_array (
1567+ data = da .zeros (skeleton .shape , dtype = np .int8 ),
1568+ mask = da .ones (skeleton .shape ),
1569+ )
1570+ else :
1571+ skeleton_data = skeleton .data
1572+ data .append (skeleton_data )
1573+
1574+ data = concatenate_arrays (data , self .axis )
15401575
15411576 return data
15421577
0 commit comments