18 May 2016

These rules may be used for deciding whether or not two arbitrary CF field constructs may be aggregated into one, larger field construct. A field construct (hereafter a

Aggregation may be thought of as the combination of one field into
another to create a new field that occupies a larger **domain**. In
practice, this means combining two fields so that their **data
arrays** are concatenated along exactly one axis, as are
their **coordinate arrays** which span that axis, in such a way
that the aggregated field conforms to the CF data model.

These rules are based solely on the fields' metadata and more than two fields may ultimately be aggregated along one or more axes by repeated aggregations between pairs of fields.

- Both
**fields**have identical standard name**properties**.- The treatment of other properties, in terms of how they should match between the fields and whether or not they should be included in the aggregated field, is beyond the scope of these rules and is at the user's discretion. This is also the case for all other construct properties unnamed by these rules.

*Definition:*- A
*coordinate construct*is either a**dimension coordinate construct**or an**auxiliary coordinate construct**. *Definition:*- A pair of
*matching coordinate constructs*is a coordinate construct from each field of the same type (dimension or auxiliary), with equivalent calendar properties (if present) and identical standard names.

- Both fields have the same number of coordinate constructs, all
of which have a standard name property, and each coordinate
construct's standard name is unique within its field. Each
coordinate construct in one field forms a pair of matching
coordinate constructs with a unique coordinate construct in the
other field.
- In these rules, as in the CF data model, a CF-netCDF scalar coordinate variable is regarded as a size-one, 1D dimension coordinate construct.

*Definition:*- A
**domain axis's***associated coordinate constructs*are those coordinate constructs which span the domain axis.

- Each domain axis in both fields has at least one associated 1D
coordinate construct.
*Definition:*- A pair of
*matching axes*is a domain axis from each field chosen such that the two domain axes have associated coordinate constructs that are all matching pairs.

- Each domain axis in one field forms a pair of matching axes
with a unique domain axis in the other field.
- A consequence of this rule is that within each pair of matching coordinate constructs, the corresponding domain axes are matching pairs.

- There is exactly one pair of matching axes for which one or
more of the 1D matching coordinate constructs have different
values for their coordinate arrays and, if present,
their
**boundary coordinate arrays**.*Definition:*- The pair of matching axes for which one or more of the
1D matching coordinate constructs have different values
for their coordinate arrays and, if present, their
boundary coordinate arrays is called the pair
of
*aggregating axes*.

- A pair of matching non-aggregating axes must have identical sizes.
- The aggregating axis may have either the same size or a different size in the two fields.
- If the domain axes of the arrays of matching associated constructs are ordered differently or run in different senses then one of the arrays must be rearranged prior to comparison. This is true for all array comparisons in these rules.
- If neither field has an aggregating axis then the fields
are not aggregatable because their
**domains**are identical. - If either field has two or more domain axes which could qualify as an aggregating axis then the fields are not aggregatable because it is not possible to choose the single domain axis for array concatenation.

*Definition:*- A pair of
*matching cell measure constructs*is a**cell measure construct**from each field with equivalent units and corresponding domain axes that are matching pairs.

- Both fields have the same number of cell measure constructs,
all of which have a units property. Each cell measure construct in
either field forms a pair of matching cell measure constructs with a
unique cell measure construct in the other field.
- Each pair of matching coordinate constructs and matching cell
measure constructs that do not span their aggregating axes have
identical values for their coordinate arrays and, if present,
their boundary coordinate arrays.
- If the pair of matching aggregating axes has a pair of
associated dimension coordinate constructs, then there are no
common values in their coordinate arrays. If the matching
dimension coordinate constructs have boundary coordinate arrays
then no cells from one dimension coordinate construct lie entirely
within any cell of the other dimension coordinate construct.
- This does not preclude the coordinate arrays' ranges from overlapping.
- The condition on the boundary coordinate arrays prevents,
for example:
- A monthly mean being aggregated with a daily mean from the same month.

- The aggregation of overlapping running means.
- The aggregation of a monthly mean and a daily mean from a different month.

- If one field has a
**cell methods construct**then so does the other field, with the equivalent methods in the same order. Corresponding domain axes in each cell methods are matching pairs.*Definition:*- A pair of
*matching domain ancillary constructs*is a**domain ancillary construct**from each field for identical standard coordinate conversion terms and corresponding domain axes that are matching pairs.

- Both fields have the same number of domain ancillary
constructs. Each domain ancillary construct in either field forms
a pair of matching domain ancillary constructs with a unique
domain ancillary construct in the other field.
*Definition:*- A pair of
*matching field ancillary constructs*is a**field ancillary construct**from each field with identical standard names and corresponding domain axes that are matching pairs.

- Both fields have the same number of field ancillary
constructs. Each field ancillary construct in either field forms a
pair of matching field ancillary constructs with a unique field
ancillary construct in the other field.
- Both fields have the same number of
**coordinate reference constructs**. For each coordinate reference construct in one field there is a coordinate reference construct in the other field with identical name and the same set of terms, taking optional terms into account. Corresponding terms which are scalar or vector parameters are identical, taking into account equivalent units. Corresponding terms which are domain ancillary constructs form a pair of matching domain ancillary constructs.

`time`

axis. Note that the matching coordinates for
the aggregating axis comprise a CF-netCDF coordinate and a CF-netCDF
scalar coordinate.
Field 1:

dimensions: t = 12 ; // size 12 time dimension lat = 111 ; lon = 106 ; bounds = 2 ; variables: double t(t) ; // time dimension coordinate t:standard_name = "time" ; t:units = "hours since 2012-1-1" ; t:calendar = "standard" ; t:bounds = "time_bnds" ; double t_bnds(bounds) ; double lat(lat) ; lat:standard_name = "grid_latitude" ; lat:units = "degrees" ; double lon(lon) ; lon:standard_name = "grid_longitude" ; lon:units = "degrees" ; double latitude(grid_latitude, grid_longitude) ; latitude:standard_name = "latitude" ; latitude:units = "degrees_north" ; double longitude(grid_latitude, grid_longitude) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; char rotated_lat_lon ; rotated_lat_lon:grid_mapping_name = "rotated_latitude_longitude" ; rotated_lat_lon:grid_north_pole_latitude = 38.f ; rotated_lat_lon:grid_north_pole_longitude = 190.f ; float tas(lon, lat, t) ; tas:standard_name = "air_temperature" ; tas:units = "K" ; tas:cell_methods = "t: mean (interval: 1.0 day)" ; tas:grid_mapping = "rotated_lat_lon" ; data: t = 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, // 12 time values (in hours) 6.5, 7.5, 8.5, 9.5, 10.5, 11.5 ;Field 2:

dimensions: // no time dimension lat = 111 ; lon = 106 ; bounds = 2 ; variables: double time ; // time scalar coordinate // with a different netCDF // variable name time:standard_name = "time" ; // identical standard_name time:units = "days since 2011-12-1" ; // equivalent units time:calendar = "gregorian" ; // equivalent units time:bounds = "time_bnds" ; // bounds present double aux_time_bnds(time, bounds) ; double lat(lat) ; lat:standard_name = "grid_latitude" ; lat:units = "degrees" ; double lon(lon) ; lon:standard_name = "grid_longitude" ; lon:units = "degrees" ; double latitude(grid_longitude, grid_latitude) ; // different dimension order latitude:standard_name = "latitude" ; latitude:units = "degrees_north" ; double longitude(grid_latitude, grid_longitude) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; char rotated_lat_lon ; // identical grid mapping rotated_lat_lon:grid_mapping_name = "rotated_latitude_longitude" ; rotated_lat_lon:grid_north_pole_latitude = 38.f ; rotated_lat_lon:grid_north_pole_longitude = 190.f ; float tas(lat, lon) ; // different dimension order, // no time dimension tas:standard_name = "air_temperature" ; // identical standard_name tas:units = "C" ; // equivalent units tas:cell_methods = "time: mean (interval 24.0 hours)" ; // equivalent cell methods tas:coordinates = "time" ; // scalar coordinate tas:grid_mapping = "rotated_lat_lon" ; // grid mapping present data: time = 31.52083333; // 1 time value (in days)Aggregation:

dimensions: time = 13 ; // size 13 time dimension lat = 111 ; lon = 106 ; bounds = 2 ; variables: double time ; time:standard_name = "time" ; time:units = "hours since 2012-1-1" ; time:calendar = "standard" ; time:bounds = "time_bnds" ; double aux_time_bnds(time, bounds) ; double lat(lat) ; lat:standard_name = "grid_latitude" ; lat:units = "degrees" ; double lon(lon) ; lon:standard_name = "grid_longitude" ; lon:units = "degrees" ; double latitude(grid_latitude, grid_longitude) ; latitude:standard_name = "latitude" ; latitude:units = "degrees_north" ; double longitude(grid_latitude, grid_longitude) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; char rotated_lat_lon ; rotated_lat_lon:grid_mapping_name = "rotated_latitude_longitude" ; rotated_lat_lon:grid_north_pole_latitude = 38.f ; rotated_lat_lon:grid_north_pole_longitude = 190.f ; float tas(lon, lat, time) ; tas:standard_name = "air_temperature" ; tas:units = "K" ; tas:cell_methods = "time: mean (interval: 1.0 day)" ; tas:grid_mapping = "rotated_lat_lon" ; data: time = 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, // 13 time values (in hours) 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5 ;

`level`

dimension. Note that in this case, the pair
of aggregating axes has two pairs of matching coordinates
(`atmosphere_hybrid_sigma_pressure_coordinate`

and `model_level_number`

) and that the pair of matching
coordinates for the non-aggregating `time`

axis comprise a
dimension coordinate and a scalar coordinate.
Field 1:

dimensions: // no time dimension level = 9 ; latitude = 145 ; longitude = 192 ; bounds = 2 ; variables: double time ; // scalar coordinate time:standard_name = "time" ; time:units = "days since 1860-12-1" ; time:calendar = "gregorian" ; double time_bounds(bounds) ; double sigma(level) ; sigma:standard_name = "atmosphere_hybrid_sigma_pressure_coordinate" ; sigma:units = "1" ; sigma:positive = "down" ; float lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; float lon(lon) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; longitude:bounds = "lon_bounds" ; float lon_bounds(lon, bounds) ; int model_level_number(level) ; model_level_number:standard_name = "model_level_number" ; model_level_number:units = "1" ; float eastward_wind(level, lat, lon) ; // no time dimension x_wind:standard_name = "eastward_wind" ; x_wind:units = "m s-1" ; x_wind:coordinates = "time model_level_number" ; x_wind:cell_methods = "time: point" ; data: sigma = 0.2997, 0.2497, 0.1996, 0.14950, 0.0992, 0.0568, 0.02959, 0.0147, 0.0046 ; model_level_number = 11, 12, 13, 14, 15, 16, 17, 18, 19 ;Field 2:

dimensions: time = 1 // size 1 time dimension level = 10 ; latitude = 145 ; longitude = 192 ; bounds = 2 ; variables: double time(time) ; // coordinate time:standard_name = "time" ; time:units = "days since 1860-12-1" ; time:calendar = "gregorian" ; time:bounds = "time_bounds" ; double time_bounds(bounds) ; double sigma(level) ; sigma:standard_name = "atmosphere_hybrid_sigma_pressure_coordinate" ; sigma:units = "1" ; sigma:positive = "down" ; float lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; float lon(lon) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; longitude:bounds = "lon_bounds" ; float lon_bounds(lon, bounds) ; int model_level_number(level) ; // auxiliary coordinate // for level dimension model_level_number:standard_name = "model_level_number" ; model_level_number:units = "1" ; float eastward_wind(time, level, lat, lon) ; x_wind:standard_name = "eastward_wind" ; x_wind:units = "m s-1" ; x_wind:coordinates = "time model_level_number" ; x_wind:cell_methods = "time: point" ; data: sigma = 0.997, 0.9749, 0.9304, 0.8698, 0.7922, 0.6995, 0.5995, 0.5045, 0.4221, 0.3546 ; model_level_number = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ;Aggregation:

dimensions: level = 19 ; latitude = 145 ; longitude = 192 ; bounds = 2 ; variables: double time ; time:standard_name = "time" ; time:units = "days since 1860-12-1" ; time:calendar = "gregorian" ; double time_bounds(bounds) ; double sigma(level) ; sigma:standard_name = "atmosphere_hybrid_sigma_pressure_coordinate" ; sigma:units = "1" ; sigma:positive = "down" ; float lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; float lon(lon) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; longitude:bounds = "lon_bounds" ; float lon_bounds(lon, bounds) ; int model_level_number(level) ; model_level_number:standard_name = "model_level_number" ; model_level_number:units = "1" ; float eastward_wind(level, lat, lon) ; x_wind:standard_name = "eastward_wind" ; x_wind:units = "m s-1" ; x_wind:coordinates = "time model_level_number" ; x_wind:cell_methods = "time: point" ; data: sigma = 0.997, 0.9749, 0.9304, 0.8698, 0.7922, 0.6995, 0.5995, 0.5045, 0.4221, 0.3546, 0.2997, 0.2497, 0.1996, 0.14950, 0.0992, 0.0568, 0.02959, 0.0147, 0.0046 ; model_level_number = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ;

`region`

dimension. Note that in this case, the
aggregating axes have no associated dimension coordinate arrays, but
they do have a logically 1D auxiliary coordinates. Arrays which span
the aggregating axis are simply concatenated, as auxiliary coordinate
array values may be non-monotonic and non-unique.
Field 1:

dimensions: time = 12 ; region = 2 ; // no region coordinate depth = 40 ; lat = 180 ; strlen = 14 ; variables: double time(time) ; time:standard_name = "time" ; time:units = "days since 1860-1-1" ; time:calendar = "gregorian" ; double depth(depth) ; depth:standard_name = "depth" ; depth:units = "m" ; depth:positive = "down" ; double lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; char geo_region(region, strlen) ; geo_region:standard_name = "region" ; geo_region:long_name = "Ocean Basin" ; float stfmmc(time, region, depth, lat) ; stfmmc:standard_name = "ocean_meridional_overturning_streamfunction" ; stfmmc:units = "m3 s-1" ; stfmmc:coordinates = "geo_region" ; data: geo_region = "atlantic_ocean", "indian_ocean " ;Field 2:

dimensions: time = 12 ; region = 2 ; depth = 40 ; lat = 180 ; strlen = 14 ; variables: double time(time) ; time:standard_name = "time" ; time:units = "days since 1860-1-1" ; time:calendar = "gregorian" ; double depth(depth) ; depth:standard_name = "depth" ; depth:units = "m" ; depth:positive = "down" ; double lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; char geo_region(region, strlen) ; geo_region:standard_name = "region" ; geo_region:long_name = "Ocean Basin" ; float stfmmc(time, region, depth, lat) ; stfmmc:standard_name = "ocean_meridional_overturning_streamfunction" ; stfmmc:units = "m3 s-1" ; stfmmc:coordinates = "geo_region" ; data: geo_region = "pacific_ocean ", "global_ocean " ;Aggregation:

dimensions: time = 12 ; region = 4 ; depth = 40 ; lat = 180 ; strlen = 14 ; variables: double time(time) ; time:standard_name = "time" ; time:units = "days since 1860-1-1" ; time:calendar = "gregorian" ; double depth(depth) ; depth:standard_name = "depth" ; depth:units = "m" ; depth:positive = "down" ; double lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; char geo_region(region, strlen) ; geo_region:standard_name = "region" ; geo_region:long_name = "Ocean Basin" ; float stfmmc(time, region, depth, lat) ; stfmmc:standard_name = "ocean_meridional_overturning_streamfunction" ; stfmmc:units = "m3 s-1" ; stfmmc:coordinates = "geo_region" ; data: geo_region = "atlantic_ocean", "indian_ocean ", "pacific_ocean ", "global_ocean " ;

`forecast_reference_time`

auxiliary coordinate is only
present in one of the fields. If the first field did not have this
auxiliary coordinate then the two fields would be aggregatable.
Field 1:

dimensions: time = 12 ; latitude = 145 ; longitude = 192 ; variables: double time ; time:standard_name = "time" ; time:units = "days since 1860-12-1" ; time:calendar = "gregorian" ; double ref_time(time): sigma:standard_name = "forecast_reference_time" ; time:units = "days since 1860-12-1" ; time:calendar = "gregorian" ; float lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; float lon(lon) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; float eastward_wind(time, lat, lon) ; x_wind:standard_name = "eastward_wind" ; x_wind:units = "m s-1" ; x_wind:coordinates = "ref_time" ; data: time = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ;Field 2:

dimensions: time = 12 ; latitude = 145 ; longitude = 192 ; variables: double time ; time:standard_name = "time" ; time:units = "days since 1860-12-1" ; time:calendar = "gregorian" ; float lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; float lon(lon) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; float eastward_wind(time, lat, lon) ; // no auxiliary coordinate x_wind:standard_name = "eastward_wind" ; x_wind:units = "m s-1" ; data: time = 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ;

`time`

axes' dimension construct coordinate
arrays for share a value.
Field 1:

dimensions: time = 12 ; latitude = 145 ; longitude = 192 ; variables: double time ; time:standard_name = "time" ; time:units = "days since 1860-12-1" ; time:calendar = "gregorian" ; float lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; float lon(lon) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; float eastward_wind(time, lat, lon) ; x_wind:standard_name = "eastward_wind" ; x_wind:units = "m s-1" ; data: time = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ; // contains 11Field 2:

dimensions: time = 12 ; latitude = 145 ; longitude = 192 ; variables: double time ; time:standard_name = "time" ; time:units = "days since 1860-12-1" ; time:calendar = "gregorian" ; float lat(lat) ; lat:standard_name = "latitude" ; lat:units = "degrees_north" ; float lon(lon) ; longitude:standard_name = "longitude" ; longitude:units = "degrees_east" ; float eastward_wind(time, lat, lon) ; x_wind:standard_name = "eastward_wind" ; x_wind:units = "m s-1" ; data: time = 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, ; // contains 11