|
4 | 4 | # See LICENSE in the root of the repository for full licensing details. |
5 | 5 | """Cube benchmark tests.""" |
6 | 6 |
|
7 | | -import numpy as np |
8 | | - |
9 | | -from iris import analysis, aux_factory, coords, cube |
10 | | - |
11 | | -from . import ARTIFICIAL_DIM_SIZE, disable_repeat_between_setup |
12 | | -from .generate_data.stock import sample_meshcoord |
13 | | - |
14 | | - |
15 | | -def setup(*params): |
16 | | - """General variables needed by multiple benchmark classes.""" |
17 | | - global data_1d |
18 | | - global data_2d |
19 | | - global general_cube |
20 | | - |
21 | | - data_2d = np.zeros((ARTIFICIAL_DIM_SIZE,) * 2) |
22 | | - data_1d = data_2d[0] |
23 | | - general_cube = cube.Cube(data_2d) |
24 | | - |
25 | | - |
26 | | -class ComponentCommon: |
27 | | - # TODO: once https://github.com/airspeed-velocity/asv/pull/828 is released: |
28 | | - # * make class an ABC |
29 | | - # * remove NotImplementedError |
30 | | - # * combine setup_common into setup |
31 | | - """Run a generalised suite of benchmarks for cubes. |
32 | | -
|
33 | | - A base class running a generalised suite of benchmarks for cubes that |
34 | | - include a specified component (e.g. Coord, CellMeasure etc.). Component to |
35 | | - be specified in a subclass. |
36 | | -
|
37 | | - ASV will run the benchmarks within this class for any subclasses. |
38 | | -
|
39 | | - Should only be instantiated within subclasses, but cannot enforce this |
40 | | - since ASV cannot handle classes that include abstract methods. |
41 | | - """ |
42 | | - |
43 | | - def setup(self): |
44 | | - """Prevent ASV instantiating (must therefore override setup() in any subclasses.).""" |
45 | | - raise NotImplementedError |
46 | | - |
47 | | - def create(self): |
48 | | - """Create a cube (generic). |
49 | | -
|
50 | | - cube_kwargs allow dynamic inclusion of different components; |
51 | | - specified in subclasses. |
52 | | - """ |
53 | | - return cube.Cube(data=data_2d, **self.cube_kwargs) |
54 | | - |
55 | | - def setup_common(self): |
56 | | - """Shared setup code that can be called by subclasses.""" |
57 | | - self.cube = self.create() |
58 | | - |
59 | | - def time_create(self): |
60 | | - """Create a cube that includes an instance of the benchmarked component.""" |
61 | | - self.create() |
62 | | - |
63 | | - def time_add(self): |
64 | | - """Add an instance of the benchmarked component to an existing cube.""" |
65 | | - # Unable to create the copy during setup since this needs to be re-done |
66 | | - # for every repeat of the test (some components disallow duplicates). |
67 | | - general_cube_copy = general_cube.copy(data=data_2d) |
68 | | - self.add_method(general_cube_copy, *self.add_args) |
69 | | - |
70 | | - |
71 | | -class Cube: |
72 | | - def time_basic(self): |
73 | | - cube.Cube(data_2d) |
74 | | - |
75 | | - def time_rename(self): |
76 | | - general_cube.name = "air_temperature" |
77 | | - |
78 | | - |
79 | | -class AuxCoord(ComponentCommon): |
80 | | - def setup(self): |
81 | | - self.coord_name = "test" |
82 | | - coord_bounds = np.array([data_1d - 1, data_1d + 1]).transpose() |
83 | | - aux_coord = coords.AuxCoord( |
84 | | - long_name=self.coord_name, |
85 | | - points=data_1d, |
86 | | - bounds=coord_bounds, |
87 | | - units="days since 1970-01-01", |
88 | | - climatological=True, |
| 7 | +from iris import coords |
| 8 | +from iris.cube import Cube |
| 9 | + |
| 10 | +from .generate_data.stock import realistic_4d_w_everything |
| 11 | + |
| 12 | + |
| 13 | +class CubeCreation: |
| 14 | + params = [[False, True], ["instantiate", "construct"]] |
| 15 | + param_names = ["Cube has mesh", "Cube creation strategy"] |
| 16 | + |
| 17 | + cube_kwargs: dict |
| 18 | + |
| 19 | + def setup(self, w_mesh: bool, _) -> None: |
| 20 | + # Loaded as two cubes due to the hybrid height. |
| 21 | + source_cube = realistic_4d_w_everything(w_mesh=w_mesh) |
| 22 | + |
| 23 | + def get_coords_and_dims( |
| 24 | + coords_tuple: tuple[coords._DimensionalMetadata, ...], |
| 25 | + ) -> list[tuple[coords._DimensionalMetadata, tuple[int, ...]]]: |
| 26 | + return [(c, c.cube_dims(source_cube)) for c in coords_tuple] |
| 27 | + |
| 28 | + self.cube_kwargs = dict( |
| 29 | + data=source_cube.data, |
| 30 | + standard_name=source_cube.standard_name, |
| 31 | + long_name=source_cube.long_name, |
| 32 | + var_name=source_cube.var_name, |
| 33 | + units=source_cube.units, |
| 34 | + attributes=source_cube.attributes, |
| 35 | + cell_methods=source_cube.cell_methods, |
| 36 | + dim_coords_and_dims=get_coords_and_dims(source_cube.dim_coords), |
| 37 | + aux_coords_and_dims=get_coords_and_dims(source_cube.aux_coords), |
| 38 | + aux_factories=source_cube.aux_factories, |
| 39 | + cell_measures_and_dims=get_coords_and_dims(source_cube.cell_measures()), |
| 40 | + ancillary_variables_and_dims=get_coords_and_dims( |
| 41 | + source_cube.ancillary_variables() |
| 42 | + ), |
89 | 43 | ) |
90 | 44 |
|
91 | | - # Variables needed by the ComponentCommon base class. |
92 | | - self.cube_kwargs = {"aux_coords_and_dims": [(aux_coord, 0)]} |
93 | | - self.add_method = cube.Cube.add_aux_coord |
94 | | - self.add_args = (aux_coord, (0)) |
95 | | - |
96 | | - self.setup_common() |
97 | | - |
98 | | - def time_return_coords(self): |
99 | | - self.cube.coords() |
100 | | - |
101 | | - def time_return_coord_dims(self): |
102 | | - self.cube.coord_dims(self.coord_name) |
103 | | - |
104 | | - |
105 | | -class AuxFactory(ComponentCommon): |
106 | | - def setup(self): |
107 | | - coord = coords.AuxCoord(points=data_1d, units="m") |
108 | | - self.hybrid_factory = aux_factory.HybridHeightFactory(delta=coord) |
109 | | - |
110 | | - # Variables needed by the ComponentCommon base class. |
111 | | - self.cube_kwargs = { |
112 | | - "aux_coords_and_dims": [(coord, 0)], |
113 | | - "aux_factories": [self.hybrid_factory], |
114 | | - } |
115 | | - |
116 | | - self.setup_common() |
117 | | - |
118 | | - # Variables needed by the overridden time_add benchmark in this subclass. |
119 | | - cube_w_coord = self.cube.copy() |
120 | | - [cube_w_coord.remove_aux_factory(i) for i in cube_w_coord.aux_factories] |
121 | | - self.cube_w_coord = cube_w_coord |
122 | | - |
123 | | - def time_add(self): |
124 | | - # Requires override from super().time_add because the cube needs an |
125 | | - # additional coord. |
126 | | - self.cube_w_coord.add_aux_factory(self.hybrid_factory) |
127 | | - |
128 | | - |
129 | | -class CellMeasure(ComponentCommon): |
130 | | - def setup(self): |
131 | | - cell_measure = coords.CellMeasure(data_1d) |
132 | | - |
133 | | - # Variables needed by the ComponentCommon base class. |
134 | | - self.cube_kwargs = {"cell_measures_and_dims": [(cell_measure, 0)]} |
135 | | - self.add_method = cube.Cube.add_cell_measure |
136 | | - self.add_args = (cell_measure, 0) |
137 | | - |
138 | | - self.setup_common() |
139 | | - |
140 | | - |
141 | | -class CellMethod(ComponentCommon): |
142 | | - def setup(self): |
143 | | - cell_method = coords.CellMethod("test") |
144 | | - |
145 | | - # Variables needed by the ComponentCommon base class. |
146 | | - self.cube_kwargs = {"cell_methods": [cell_method]} |
147 | | - self.add_method = cube.Cube.add_cell_method |
148 | | - self.add_args = [cell_method] |
149 | | - |
150 | | - self.setup_common() |
151 | | - |
152 | | - |
153 | | -class AncillaryVariable(ComponentCommon): |
154 | | - def setup(self): |
155 | | - ancillary_variable = coords.AncillaryVariable(data_1d) |
156 | | - |
157 | | - # Variables needed by the ComponentCommon base class. |
158 | | - self.cube_kwargs = {"ancillary_variables_and_dims": [(ancillary_variable, 0)]} |
159 | | - self.add_method = cube.Cube.add_ancillary_variable |
160 | | - self.add_args = (ancillary_variable, 0) |
161 | | - |
162 | | - self.setup_common() |
163 | | - |
164 | | - |
165 | | -class MeshCoord: |
| 45 | + def time_create(self, _, cube_creation_strategy: str) -> None: |
| 46 | + if cube_creation_strategy == "instantiate": |
| 47 | + _ = Cube(**self.cube_kwargs) |
| 48 | + |
| 49 | + elif cube_creation_strategy == "construct": |
| 50 | + new_cube = Cube(data=self.cube_kwargs["data"]) |
| 51 | + new_cube.standard_name = self.cube_kwargs["standard_name"] |
| 52 | + new_cube.long_name = self.cube_kwargs["long_name"] |
| 53 | + new_cube.var_name = self.cube_kwargs["var_name"] |
| 54 | + new_cube.units = self.cube_kwargs["units"] |
| 55 | + new_cube.attributes = self.cube_kwargs["attributes"] |
| 56 | + new_cube.cell_methods = self.cube_kwargs["cell_methods"] |
| 57 | + for coord, dims in self.cube_kwargs["dim_coords_and_dims"]: |
| 58 | + coord: coords.DimCoord # Type hint to help linters. |
| 59 | + new_cube.add_dim_coord(coord, dims) |
| 60 | + for coord, dims in self.cube_kwargs["aux_coords_and_dims"]: |
| 61 | + new_cube.add_aux_coord(coord, dims) |
| 62 | + for aux_factory in self.cube_kwargs["aux_factories"]: |
| 63 | + new_cube.add_aux_factory(aux_factory) |
| 64 | + for cell_measure, dims in self.cube_kwargs["cell_measures_and_dims"]: |
| 65 | + new_cube.add_cell_measure(cell_measure, dims) |
| 66 | + for ancillary_variable, dims in self.cube_kwargs[ |
| 67 | + "ancillary_variables_and_dims" |
| 68 | + ]: |
| 69 | + new_cube.add_ancillary_variable(ancillary_variable, dims) |
| 70 | + |
| 71 | + else: |
| 72 | + message = f"Unknown cube creation strategy: {cube_creation_strategy}" |
| 73 | + raise NotImplementedError(message) |
| 74 | + |
| 75 | + |
| 76 | +class CubeEquality: |
166 | 77 | params = [ |
167 | | - 6, # minimal cube-sphere |
168 | | - int(1e6), # realistic cube-sphere size |
169 | | - ARTIFICIAL_DIM_SIZE, # To match size in :class:`AuxCoord` |
| 78 | + [False, True], |
| 79 | + [False, True], |
| 80 | + ["metadata_inequality", "coord_inequality", "data_inequality", "all_equal"], |
170 | 81 | ] |
171 | | - param_names = ["number of faces"] |
172 | | - |
173 | | - def setup(self, n_faces): |
174 | | - mesh_kwargs = dict(n_nodes=n_faces + 2, n_edges=n_faces * 2, n_faces=n_faces) |
175 | | - |
176 | | - self.mesh_coord = sample_meshcoord(sample_mesh_kwargs=mesh_kwargs) |
177 | | - self.data = np.zeros(n_faces) |
178 | | - self.cube_blank = cube.Cube(data=self.data) |
179 | | - self.cube = self.create() |
180 | | - |
181 | | - def create(self): |
182 | | - return cube.Cube(data=self.data, aux_coords_and_dims=[(self.mesh_coord, 0)]) |
183 | | - |
184 | | - def time_create(self, n_faces): |
185 | | - _ = self.create() |
186 | | - |
187 | | - @disable_repeat_between_setup |
188 | | - def time_add(self, n_faces): |
189 | | - self.cube_blank.add_aux_coord(self.mesh_coord, 0) |
190 | | - |
191 | | - @disable_repeat_between_setup |
192 | | - def time_remove(self, n_faces): |
193 | | - self.cube.remove_coord(self.mesh_coord) |
194 | | - |
195 | | - |
196 | | -class Merge: |
197 | | - def setup(self): |
198 | | - self.cube_list = cube.CubeList() |
199 | | - for i in np.arange(2): |
200 | | - i_cube = general_cube.copy() |
201 | | - i_coord = coords.AuxCoord([i]) |
202 | | - i_cube.add_aux_coord(i_coord) |
203 | | - self.cube_list.append(i_cube) |
204 | | - |
205 | | - def time_merge(self): |
206 | | - self.cube_list.merge() |
207 | | - |
208 | | - |
209 | | -class Concatenate: |
210 | | - def setup(self): |
211 | | - dim_size = ARTIFICIAL_DIM_SIZE |
212 | | - self.cube_list = cube.CubeList() |
213 | | - for i in np.arange(dim_size * 2, step=dim_size): |
214 | | - i_cube = general_cube.copy() |
215 | | - i_coord = coords.DimCoord(np.arange(dim_size) + (i * dim_size)) |
216 | | - i_cube.add_dim_coord(i_coord, 0) |
217 | | - self.cube_list.append(i_cube) |
218 | | - |
219 | | - def time_concatenate(self): |
220 | | - self.cube_list.concatenate() |
221 | | - |
222 | | - |
223 | | -class Equality: |
224 | | - def setup(self): |
225 | | - self.cube_a = general_cube.copy() |
226 | | - self.cube_b = general_cube.copy() |
227 | | - |
228 | | - aux_coord = coords.AuxCoord(data_1d) |
229 | | - self.cube_a.add_aux_coord(aux_coord, 0) |
230 | | - self.cube_b.add_aux_coord(aux_coord, 1) |
231 | | - |
232 | | - def time_equality(self): |
233 | | - self.cube_a == self.cube_b |
234 | | - |
235 | | - |
236 | | -class Aggregation: |
237 | | - def setup(self): |
238 | | - repeat_number = 10 |
239 | | - repeat_range = range(int(ARTIFICIAL_DIM_SIZE / repeat_number)) |
240 | | - array_repeat = np.repeat(repeat_range, repeat_number) |
241 | | - array_unique = np.arange(len(array_repeat)) |
242 | | - |
243 | | - coord_repeat = coords.AuxCoord(points=array_repeat, long_name="repeat") |
244 | | - coord_unique = coords.DimCoord(points=array_unique, long_name="unique") |
245 | | - |
246 | | - local_cube = general_cube.copy() |
247 | | - local_cube.add_aux_coord(coord_repeat, 0) |
248 | | - local_cube.add_dim_coord(coord_unique, 0) |
249 | | - self.cube = local_cube |
250 | | - |
251 | | - def time_aggregated_by(self): |
252 | | - self.cube.aggregated_by("repeat", analysis.MEAN) |
| 82 | + param_names = ["Cubes are lazy", "Cubes have meshes", "Scenario"] |
| 83 | + |
| 84 | + cube_1: Cube |
| 85 | + cube_2: Cube |
| 86 | + coord_name = "surface_altitude" |
| 87 | + |
| 88 | + def setup(self, lazy: bool, w_mesh: bool, scenario: str) -> None: |
| 89 | + self.cube_1 = realistic_4d_w_everything(w_mesh=w_mesh, lazy=lazy) |
| 90 | + # Using Cube.copy() produces different results due to sharing of the |
| 91 | + # Mesh instance. |
| 92 | + self.cube_2 = realistic_4d_w_everything(w_mesh=w_mesh, lazy=lazy) |
| 93 | + |
| 94 | + match scenario: |
| 95 | + case "metadata_inequality": |
| 96 | + self.cube_2.long_name = "different" |
| 97 | + case "coord_inequality": |
| 98 | + coord = self.cube_2.coord(self.coord_name) |
| 99 | + coord.points = coord.core_points() * 2 |
| 100 | + case "data_inequality": |
| 101 | + self.cube_2.data = self.cube_2.core_data() * 2 |
| 102 | + case "all_equal": |
| 103 | + pass |
| 104 | + case _: |
| 105 | + message = f"Unknown scenario: {scenario}" |
| 106 | + raise NotImplementedError(message) |
| 107 | + |
| 108 | + def time_equality(self, lazy: bool, __, ___) -> None: |
| 109 | + _ = self.cube_1 == self.cube_2 |
| 110 | + if lazy: |
| 111 | + for cube in (self.cube_1, self.cube_2): |
| 112 | + # Confirm that this benchmark is safe for repetition. |
| 113 | + assert cube.coord(self.coord_name).has_lazy_points() |
| 114 | + assert cube.has_lazy_data() |
0 commit comments