2424
2525from CSET ._common import iter_maybe
2626from CSET .operators ._utils import is_time_aggregatable
27- from CSET .operators .aggregate import (
28- add_hour_coordinate ,
29- ensure_aggregatable_across_cases ,
30- )
27+ from CSET .operators .aggregate import add_hour_coordinate
3128
3229
3330def collapse (
@@ -46,15 +43,17 @@ def collapse(
4643 Arguments
4744 ---------
4845 cubes: iris.cube.Cube | iris.cube.CubeList
49- Cube or CubeList to collapse and iterate over one dimension
46+ Cube or CubeList to collapse and iterate over one dimension
5047 coordinate: str | list[str]
51- Coordinate(s) to collapse over e.g. 'time', 'longitude', 'latitude',
52- 'model_level_number', 'realization'. A list of multiple coordinates can
53- be given.
48+ Coordinate(s) to collapse over e.g. 'time', 'longitude', 'latitude',
49+ 'model_level_number', 'realization'. A list of multiple coordinates can
50+ be given.
5451 method: str
55- Type of collapse i.e. method: 'MEAN', 'MAX', 'MIN', 'MEDIAN',
56- 'PERCENTILE' getattr creates iris.analysis.MEAN, etc For PERCENTILE
57- YAML file requires i.e. method: 'PERCENTILE' additional_percent: 90
52+ Type of collapse i.e. method: 'MEAN', 'MAX', 'MIN', 'MEDIAN',
53+ 'PERCENTILE' getattr creates iris.analysis.MEAN, etc For PERCENTILE YAML
54+ file requires i.e. method: 'PERCENTILE' additional_percent: 90
55+ additional_percent: float, optional
56+ Required for the PERCENTILE method. This is a number between 0 and 100.
5857
5958 Returns
6059 -------
@@ -95,56 +94,8 @@ def collapse(
9594 return collapsed_cubes
9695
9796
98- def collapse_by_lead_time (
99- cube : iris .cube .Cube | iris .cube .CubeList ,
100- method : str ,
101- additional_percent : float = None ,
102- ** kwargs ,
103- ) -> iris .cube .Cube :
104- """Collapse a cube around lead time for multiple cases.
105-
106- First checks if the data can be aggregated by lead time easily. Then
107- collapses by lead time for a specified method using the collapse function.
108- This operator provides the average of all T+1, T+2, etc.
109-
110- Arguments
111- ---------
112- cube: iris.cube.Cube | iris.cube.CubeList
113- Cube to collapse by lead time or CubeList that will be converted
114- to a cube before collapsing by lead time.
115- method: str
116- Type of collapse i.e. method: 'MEAN', 'MAX', 'MIN', 'MEDIAN',
117- 'PERCENTILE'. For 'PERCENTILE' the additional_percent must be specified.
118-
119- Returns
120- -------
121- cube: iris.cube.Cube
122- Single variable collapsed by lead time based on chosen method.
123-
124- Raises
125- ------
126- ValueError
127- If additional_percent wasn't supplied while using PERCENTILE method.
128- """
129- if method == "PERCENTILE" and additional_percent is None :
130- raise ValueError ("Must specify additional_percent" )
131- # Ensure the cube can be aggregated over multiple cases.
132- cube_to_collapse = ensure_aggregatable_across_cases (cube )
133- # Collapse by lead time.
134- if method == "PERCENTILE" :
135- collapsed_cube = collapse (
136- cube_to_collapse ,
137- "forecast_reference_time" ,
138- method ,
139- additional_percent = additional_percent ,
140- )
141- else :
142- collapsed_cube = collapse (cube_to_collapse , "forecast_reference_time" , method )
143- return collapsed_cube
144-
145-
14697def collapse_by_hour_of_day (
147- cube : iris .cube .Cube | iris .cube .CubeList ,
98+ cubes : iris .cube .Cube | iris .cube .CubeList ,
14899 method : str ,
149100 additional_percent : float = None ,
150101 multi_case : bool = True ,
@@ -158,7 +109,7 @@ def collapse_by_hour_of_day(
158109
159110 Arguments
160111 ---------
161- cube : iris.cube.Cube | iris.cube.CubeList
112+ cubes : iris.cube.Cube | iris.cube.CubeList
162113 Cube to collapse and iterate over one dimension or CubeList to
163114 convert to a cube and then collapse prior to aggregating by hour.
164115 If a CubeList is provided multi_case must be set to True as the Cube List
@@ -209,60 +160,70 @@ def collapse_by_hour_of_day(
209160 """
210161 if method == "PERCENTILE" and additional_percent is None :
211162 raise ValueError ("Must specify additional_percent" )
212- elif (
213- isinstance (cube , iris .cube .Cube )
214- and is_time_aggregatable (cube )
215- and not multi_case
216- ):
217- raise TypeError (
218- "multi_case must be true for a cube containing two time dimensions"
219- )
220- elif (
221- isinstance (cube , iris .cube .Cube )
222- and not is_time_aggregatable (cube )
223- and multi_case
224- ):
225- raise TypeError (
226- "multi_case must be false for a cube containing one time dimension"
227- )
228- elif isinstance (cube , iris .cube .CubeList ) and not multi_case :
229- raise TypeError ("multi_case must be true for a CubeList" )
230163
231- if multi_case :
232- # Collapse by lead time to get a single time dimension.
233- cube = collapse_by_lead_time (
234- cube , method , additional_percent = additional_percent
235- )
236-
237- # Categorise the time coordinate by hour of the day.
238- cube = add_hour_coordinate (cube )
239- # Aggregate by the new category coordinate.
240- if method == "PERCENTILE" :
241- collapsed_cube = cube .aggregated_by (
242- "hour" , getattr (iris .analysis , method ), percent = additional_percent
243- )
164+ collapsed_cubes = iris .cube .CubeList ([])
165+ for cube in iter_maybe (cubes ):
166+ if (
167+ isinstance (cube , iris .cube .Cube )
168+ and is_time_aggregatable (cube )
169+ and not multi_case
170+ ):
171+ raise TypeError (
172+ "multi_case must be true for a cube containing two time dimensions"
173+ )
174+ elif (
175+ isinstance (cube , iris .cube .Cube )
176+ and not is_time_aggregatable (cube )
177+ and multi_case
178+ ):
179+ raise TypeError (
180+ "multi_case must be false for a cube containing one time dimension"
181+ )
182+ elif isinstance (cube , iris .cube .CubeList ) and not multi_case :
183+ raise TypeError ("multi_case must be true for a CubeList" )
184+
185+ if multi_case :
186+ # Collapse by lead time to get a single time dimension.
187+ cube = collapse (
188+ cube ,
189+ "forecast_reference_time" ,
190+ method ,
191+ additional_percent = additional_percent ,
192+ )
193+
194+ # Categorise the time coordinate by hour of the day.
195+ cube = add_hour_coordinate (cube )
196+ # Aggregate by the new category coordinate.
197+ if method == "PERCENTILE" :
198+ collapsed_cube = cube .aggregated_by (
199+ "hour" , getattr (iris .analysis , method ), percent = additional_percent
200+ )
201+ else :
202+ collapsed_cube = cube .aggregated_by ("hour" , getattr (iris .analysis , method ))
203+
204+ # Remove unnecessary time coordinates.
205+ collapsed_cube .remove_coord ("time" )
206+ collapsed_cube .remove_coord ("forecast_period" )
207+
208+ # Sort hour coordinate.
209+ collapsed_cube .coord ("hour" ).points .sort ()
210+
211+ # Remove forecast_reference_time if a single case, as collapse_by_lead_time
212+ # will have effectively done this if multi_case is True.
213+ if not multi_case :
214+ collapsed_cube .remove_coord ("forecast_reference_time" )
215+
216+ # Promote "hour" to dim_coord.
217+ iris .util .promote_aux_coord_to_dim_coord (collapsed_cube , "hour" )
218+ collapsed_cubes .append (collapsed_cube )
219+ if len (collapsed_cubes ) == 1 :
220+ return collapsed_cubes [0 ]
244221 else :
245- collapsed_cube = cube .aggregated_by ("hour" , getattr (iris .analysis , method ))
246-
247- # Remove unnecessary time coordinates.
248- collapsed_cube .remove_coord ("time" )
249- collapsed_cube .remove_coord ("forecast_period" )
250-
251- # Sort hour coordinate.
252- collapsed_cube .coord ("hour" ).points .sort ()
253-
254- # Remove forecast_reference_time if a single case, as collapse_by_lead_time
255- # will have effectively done this if multi_case is True.
256- if not multi_case :
257- collapsed_cube .remove_coord ("forecast_reference_time" )
258-
259- # Promote "hour" to dim_coord.
260- iris .util .promote_aux_coord_to_dim_coord (collapsed_cube , "hour" )
261- return collapsed_cube
222+ return collapsed_cubes
262223
263224
264225def collapse_by_validity_time (
265- cube : iris .cube .Cube | iris .cube .CubeList ,
226+ cubes : iris .cube .Cube | iris .cube .CubeList ,
266227 method : str ,
267228 additional_percent : float = None ,
268229 ** kwargs ,
@@ -277,7 +238,7 @@ def collapse_by_validity_time(
277238
278239 Arguments
279240 ---------
280- cube : iris.cube.Cube | iris.cube.CubeList
241+ cubes : iris.cube.Cube | iris.cube.CubeList
281242 Cube to collapse by validity time or CubeList that will be converted
282243 to a cube before collapsing by validity time.
283244 method: str
@@ -296,45 +257,50 @@ def collapse_by_validity_time(
296257 """
297258 if method == "PERCENTILE" and additional_percent is None :
298259 raise ValueError ("Must specify additional_percent" )
299- # Ensure the cube can be aggregated over multiple times.
300- cubes_to_collapse = ensure_aggregatable_across_cases (cube )
301- if len (cubes_to_collapse ) != 1 :
302- raise ValueError ("Cubes should be compatible when collapsing by validity time." )
303- # Convert to a cube that is split by validity time.
304- cube_to_collapse = cubes_to_collapse [0 ]
305- # Slice over cube by both time dimensions to create a CubeList.
306- new_cubelist = iris .cube .CubeList (
307- cube_to_collapse .slices_over (["forecast_period" , "forecast_reference_time" ])
308- )
309- # Remove forecast_period and forecast_reference_time coordinates.
310- for sub_cube in new_cubelist :
311- sub_cube .remove_coord ("forecast_period" )
312- sub_cube .remove_coord ("forecast_reference_time" )
313- # Create new CubeList by merging with unique = False to produce a validity
314- # time cube.
315- merged_list_1 = new_cubelist .merge (unique = False )
316- # Create a new "fake" coordinate and apply to each remaining cube to allow
317- # final merging to take place into a single cube.
318- equalised_validity_time = iris .coords .AuxCoord (
319- points = 0 , long_name = "equalised_validity_time" , units = "1"
320- )
321- for sub_cube , eq_valid_time in zip (
322- merged_list_1 , range (len (merged_list_1 )), strict = True
323- ):
324- sub_cube .add_aux_coord (equalised_validity_time .copy (points = eq_valid_time ))
325-
326- # Merge CubeList to create final cube.
327- final_cube = merged_list_1 .merge_cube ()
328- # Collapse over fake_time_coord to represent collapsing over validity time.
329- if method == "PERCENTILE" :
330- return collapse (
331- final_cube ,
332- "equalised_validity_time" ,
333- method ,
334- additional_percent = additional_percent ,
260+
261+ collapsed_cubes = iris .cube .CubeList ([])
262+ for cube in iter_maybe (cubes ):
263+ # Slice over cube by both time dimensions to create a CubeList.
264+ new_cubelist = iris .cube .CubeList (
265+ cube .slices_over (["forecast_period" , "forecast_reference_time" ])
335266 )
267+ # Remove forecast_period and forecast_reference_time coordinates.
268+ for sub_cube in new_cubelist :
269+ sub_cube .remove_coord ("forecast_period" )
270+ sub_cube .remove_coord ("forecast_reference_time" )
271+ # Create new CubeList by merging with unique = False to produce a validity
272+ # time cube.
273+ merged_list_1 = new_cubelist .merge (unique = False )
274+ # Create a new "fake" coordinate and apply to each remaining cube to allow
275+ # final merging to take place into a single cube.
276+ equalised_validity_time = iris .coords .AuxCoord (
277+ points = 0 , long_name = "equalised_validity_time" , units = "1"
278+ )
279+ for sub_cube , eq_valid_time in zip (
280+ merged_list_1 , range (len (merged_list_1 )), strict = True
281+ ):
282+ sub_cube .add_aux_coord (equalised_validity_time .copy (points = eq_valid_time ))
283+
284+ # Merge CubeList to create final cube.
285+ final_cube = merged_list_1 .merge_cube ()
286+ # Collapse over fake_time_coord to represent collapsing over validity time.
287+ if method == "PERCENTILE" :
288+ collapsed_cubes .append (
289+ collapse (
290+ final_cube ,
291+ "equalised_validity_time" ,
292+ method ,
293+ additional_percent = additional_percent ,
294+ )
295+ )
296+ else :
297+ collapsed_cubes .append (
298+ collapse (final_cube , "equalised_validity_time" , method )
299+ )
300+ if len (collapsed_cubes ) == 1 :
301+ return collapsed_cubes [0 ]
336302 else :
337- return collapse ( final_cube , "equalised_validity_time" , method )
303+ return collapsed_cubes
338304
339305
340306# TODO
0 commit comments