From e1a1b53e8e8767ae111653f1a38eb350b5d79cf3 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Mon, 8 Feb 2021 11:44:07 +0000 Subject: [PATCH 01/23] Adopt new-style Cube printout. --- lib/iris/_representation/__init__.py | 9 + lib/iris/_representation/cube_printout.py | 365 ++++++++++++++ .../cube_summary.py} | 0 lib/iris/cube.py | 45 ++ .../representation/cube_printout/__init__.py | 6 + .../cube_printout/test_CubePrintout.py | 464 ++++++++++++++++++ .../cube_printout/test_Table.py | 160 ++++++ .../representation/cube_summary/__init__.py | 6 + .../test_CubeSummary.py} | 29 +- 9 files changed, 1070 insertions(+), 14 deletions(-) create mode 100644 lib/iris/_representation/__init__.py create mode 100644 lib/iris/_representation/cube_printout.py rename lib/iris/{_representation.py => _representation/cube_summary.py} (100%) create mode 100644 lib/iris/tests/unit/representation/cube_printout/__init__.py create mode 100644 lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py create mode 100644 lib/iris/tests/unit/representation/cube_printout/test_Table.py create mode 100644 lib/iris/tests/unit/representation/cube_summary/__init__.py rename lib/iris/tests/unit/representation/{test_representation.py => cube_summary/test_CubeSummary.py} (93%) diff --git a/lib/iris/_representation/__init__.py b/lib/iris/_representation/__init__.py new file mode 100644 index 0000000000..f6c7fdf9b4 --- /dev/null +++ b/lib/iris/_representation/__init__.py @@ -0,0 +1,9 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Code to make printouts and other representations (e.g. html) of Iris objects. + +""" diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py new file mode 100644 index 0000000000..619ca540dd --- /dev/null +++ b/lib/iris/_representation/cube_printout.py @@ -0,0 +1,365 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Provides text printouts of Iris cubes. + +""" +from copy import deepcopy + +from iris._representation.cube_summary import CubeSummary + + +class Table: + """ + A container of text strings in rows + columns, that can format its content + into a string per row, with contents in columns of fixed width. + + Supports left- or right- aligned columns, alignment being set "per row". + A column may also be set, beyond which output is printed without further + formatting, and without affecting any subsequent column widths. + This is used as a crude alternative to column spanning. + + """ + + def __init__(self, rows=None, col_widths=None): + if rows is None: + rows = [] + self.rows = [deepcopy(row) for row in rows] + self.col_widths = col_widths + + def copy(self): + return Table(self.rows, col_widths=self.col_widths) + + @property + def n_columns(self): + if self.rows: + result = len(self.rows[0].cols) + else: + result = None + return result + + class Row: + """A set of column info, plus per-row formatting controls.""" + + def __init__(self, cols, aligns, i_col_unlimited=None): + assert len(cols) == len(aligns) + self.cols = cols + self.aligns = aligns + self.i_col_unlimited = i_col_unlimited + # This col + those after do not add to width + # - a crude alternative to proper column spanning + + def add_row(self, cols, aligns, i_col_unlimited=None): + """ + Create a new row at the bottom. + + Args: + * cols (list of string): + Per-column content. Length must match the other rows (if any). + * aligns (list of {'left', 'right'}): + Per-column alignments. Length must match 'cols'. + * i_col_unlimited (int or None): + Column beyond which content does not affect the column widths. + ( meaning contents will print without limit ). + + """ + n_cols = len(cols) + if len(aligns) != n_cols: + msg = ( + f"Number of aligns ({len(aligns)})" + f" != number of cols ({n_cols})" + ) + raise ValueError(msg) + if self.n_columns is not None: + # For now, all rows must have same number of columns + if n_cols != self.n_columns: + msg = ( + f"Number of columns ({n_cols})" + f" != existing table.n_columns ({self.n_columns})" + ) + raise ValueError(msg) + row = self.Row(cols, aligns, i_col_unlimited) + self.rows.append(row) + + def set_min_column_widths(self): + """Set all column widths to minimum required for current content.""" + if self.rows: + widths = [0] * self.n_columns + for row in self.rows: + cols, lim = row.cols, row.i_col_unlimited + if lim is not None: + cols = cols[:lim] # Ignore "unlimited" columns + for i_col, col in enumerate(cols): + widths[i_col] = max(widths[i_col], len(col)) + + self.col_widths = widths + + def formatted_as_strings(self): + """Return lines formatted to the set column widths.""" + if self.col_widths is None: + # If not set, calculate minimum widths. + self.set_min_column_widths() + result_lines = [] + for row in self.rows: + col_texts = [] + for col, align, width in zip( + row.cols, row.aligns, self.col_widths + ): + if align == "left": + col_text = col.ljust(width) + elif align == "right": + col_text = col.rjust(width) + else: + msg = ( + f'Unknown alignment "{align}" ' + 'not in ("left", "right")' + ) + raise ValueError(msg) + col_texts.append(col_text) + + row_line = " ".join(col_texts).rstrip() + result_lines.append(row_line) + return result_lines + + def __str__(self): + return "\n".join(self.formatted_as_strings()) + + +class CubePrinter: + """ + An object created from a + :class:`iris._representation.cube_or_summary.CubeSummary`, which provides + text printout of a :class:`iris.cube.Cube`. + + TODO: the cube :meth:`iris.cube.Cube.__str__` and + :meth:`iris.cube.Cube.__repr__` methods, and + :meth:`iris.cube.Cube.summary` with 'oneline=True', should use this to + produce cube summary strings. + + This class has no internal knowledge of :class:`iris.cube.Cube`, but only + of :class:`iris._representation.cube_or_summary.CubeSummary`. + + """ + + def __init__(self, cube_or_summary): + """ + An object that provides a printout of a cube. + + Args: + + * cube_or_summary (Cube or CubeSummary): + If a cube, first create a CubeSummary from it. + + + .. note:: + The CubePrinter is based on a digest of a CubeSummary, but does + not reference or store it. + + """ + # Create our internal table from the summary, to produce the printouts. + if isinstance(cube_or_summary, CubeSummary): + cube_summary = cube_or_summary + else: + cube_summary = CubeSummary(cube_or_summary) + self.table = self._ingest_summary(cube_summary) + + def _ingest_summary( + self, + cube_summary, + n_indent_section=4, + n_indent_item=4, + n_indent_extra=4, + ): + """Make a table of strings representing the cube-summary.""" + sect_indent = " " * n_indent_section + item_indent = sect_indent + " " * n_indent_item + item_to_extra_indent = " " * n_indent_extra + extra_indent = item_indent + item_to_extra_indent + summ = cube_summary + + fullheader = summ.header + nameunits_string = fullheader.nameunit + dimheader = fullheader.dimension_header + cube_is_scalar = dimheader.scalar + + cube_shape = dimheader.shape # may be empty + dim_names = dimheader.dim_names # may be empty + n_dims = len(dim_names) + assert len(cube_shape) == n_dims + + # First setup the columns + # - x1 @0 column-1 content : main title; headings; elements-names + # - x1 @1 "value" content (for scalar items) + # - OR x2n @1.. (name, length) for each of n dimensions + column_header_texts = [nameunits_string] # Note extra spacer here + + if cube_is_scalar: + # We will put this in the column-1 position (replacing the dim-map) + column_header_texts.append("(scalar cube)") + else: + for dim_name, length in zip(dim_names, cube_shape): + column_header_texts.append(f"{dim_name}:") + column_header_texts.append(f"{length:d}") + + n_cols = len(column_header_texts) + + # Create a table : a (n_rows) list of (n_cols) strings + + table = Table() + + # Code for adding a row, with control options. + scalar_column_aligns = ["left"] * n_cols + vector_column_aligns = deepcopy(scalar_column_aligns) + if cube_is_scalar: + vector_column_aligns[1] = "left" + else: + vector_column_aligns[1:] = n_dims * ["right", "left"] + + def add_row(col_texts, scalar=False): + aligns = scalar_column_aligns if scalar else vector_column_aligns + i_col_unlimited = 1 if scalar else None + n_missing = n_cols - len(col_texts) + col_texts += [" "] * n_missing + table.add_row(col_texts, aligns, i_col_unlimited=i_col_unlimited) + + # Start with the header line + add_row(column_header_texts) + + # Add rows from all the vector sections + for sect in summ.vector_sections.values(): + if sect.contents: + sect_name = sect.title + column_texts = [sect_indent + sect_name] + add_row(column_texts) + for vec_summary in sect.contents: + element_name = vec_summary.name + dim_chars = vec_summary.dim_chars + extra_string = vec_summary.extra + column_texts = [item_indent + element_name] + for dim_char in dim_chars: + column_texts += [dim_char, ""] + add_row(column_texts) + if extra_string: + column_texts = [extra_indent + extra_string] + add_row(column_texts) + + # Similar for scalar sections + for sect in summ.scalar_sections.values(): + if sect.contents: + # Add a row for the "section title" text. + sect_name = sect.title + add_row([sect_indent + sect_name]) + + def add_scalar_row(name, value=""): + column_texts = [item_indent + name, value] + add_row(column_texts, scalar=True) + + # Add a row for each item + # NOTE: different section types need different handling + title = sect_name.lower() + if "scalar coordinate" in title: + for item in sect.contents: + add_scalar_row(item.name, ": " + item.content) + if item.extra: + add_scalar_row(item_to_extra_indent + item.extra) + elif "attribute" in title: + for title, value in zip(sect.names, sect.values): + add_scalar_row(title, ": " + value) + elif "scalar cell measure" in title or "cell method" in title: + # These are just strings: nothing in the 'value' column. + for name in sect.contents: + add_scalar_row(name) + else: + msg = f"Unknown section type : {type(sect)}" + raise ValueError(msg) + + return table + + @staticmethod + def _decorated_table(table, name_padding=None): + """ + Return a modified table with added characters in the header. + + Note: 'name_padding' sets a minimum width for the name column (#0). + + """ + + # Copy the input table + extract the header + its columns. + table = table.copy() + header = table.rows[0] + cols = header.cols + + if name_padding: + # Extend header column#0 to a given minimum width. + cols[0] = cols[0].ljust(name_padding) + + # Add parentheses around the dim column texts. + # -- unless already present, e.g. "(scalar cube)". + if len(cols) > 1 and not cols[1].startswith("("): + # Add parentheses around the dim columns + cols[1] = "(" + cols[1] + cols[-1] = cols[-1] + ")" + + # Add semicolons as dim column spacers + for i_col in range(2, len(cols) - 1, 2): + cols[i_col] += ";" + + # Modify the new table to be returned, invalidate any stored widths. + header.cols = cols + table.rows[0] = header + + # Recalc widths + table.set_min_column_widths() + + return table + + def _oneline_string(self): + """Produce a one-line summary string.""" + # Copy existing content -- just the header line. + table = Table(rows=[self.table.rows[0]]) + # Note: by excluding other columns, we get a minimum-width result. + + # Add standard decorations. + table = self._decorated_table(table, name_padding=0) + + # Format (with no extra spacing) --> one-line result + (oneline_result,) = table.formatted_as_strings() + return oneline_result + + def _multiline_summary(self, name_padding): + """Produce a multi-line summary string.""" + # Get a derived table with standard 'decorations' added. + table = self._decorated_table(self.table, name_padding=name_padding) + result_lines = table.formatted_as_strings() + result = "\n".join(result_lines) + return result + + def to_string(self, oneline=False, name_padding=35): + """ + Produce a printable summary. + + Args: + * oneline (bool): + If set, produce a one-line summary (without any extra spacings). + Default is False = produce full (multiline) summary. + * name_padding (int): + The minimum width for the "name" (#0) column. + Used for multiline output only. + + Returns: + result (string) + + """ + if oneline: + result = self._oneline_string() + else: + result = self._multiline_summary(name_padding) + + return result + + def __str__(self): + """Printout of self, as a full multiline string.""" + return self.to_string() diff --git a/lib/iris/_representation.py b/lib/iris/_representation/cube_summary.py similarity index 100% rename from lib/iris/_representation.py rename to lib/iris/_representation/cube_summary.py diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 7d4f1dc33d..fca9230a5a 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -727,6 +727,50 @@ def _is_single_item(testee): return isinstance(testee, str) or not isinstance(testee, Iterable) +def _cubesummary_tolerant_lines(text): + """ + Produce a regularised version of a summary string, as a list of lines. + + Split at '\n' into lines. + Add spaces around all ':' characters (for attributes formatting). + Replace whitespace with single spaces, and remove leading+trailing. + + """ + lines = text.split("\n") + lines = [line.replace(":", " : ") for line in lines] + stripped_lines = [] + for line in lines: + line = line.strip() + while " " in line: + line = line.replace(" ", " ") + stripped_lines.append(line) + return stripped_lines + + +def _cubesummary_traditional_lines(cube): + # Produce a list of lines from an old-method (extended) cube summary. + return _cubesummary_tolerant_lines(cube.summary()) + + +def _cubesummary_newstyle_lines(cube): + # Produce a list of lines from a new-method (extended) cube summary. + from iris._representation.cube_printout import CubePrinter + + return _cubesummary_tolerant_lines(CubePrinter(cube).to_string()) + + +def _cubesummary_newold_compare(cube): + """ + A temporary routine to compare old + new output for all cube summary + printouts. + + """ + + oldstyle = _cubesummary_traditional_lines(cube) + newstyle = _cubesummary_newstyle_lines(cube) + assert oldstyle == newstyle + + class Cube(CFVariableMixin): """ A single Iris cube of data and metadata. @@ -2663,6 +2707,7 @@ def vector_summary( return summary def __str__(self): + _cubesummary_newold_compare(self) return self.summary() def __repr__(self): diff --git a/lib/iris/tests/unit/representation/cube_printout/__init__.py b/lib/iris/tests/unit/representation/cube_printout/__init__.py new file mode 100644 index 0000000000..50ab3f8e45 --- /dev/null +++ b/lib/iris/tests/unit/representation/cube_printout/__init__.py @@ -0,0 +1,6 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :mod:`iris._representation.cube_printout` module.""" diff --git a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py new file mode 100644 index 0000000000..0b592e9b6c --- /dev/null +++ b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py @@ -0,0 +1,464 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for :class:`iris._representation.cube_printout.CubePrintout`.""" +import iris.tests as tests + +import numpy as np + +from iris.cube import Cube +from iris.coords import ( + AuxCoord, + DimCoord, + AncillaryVariable, + CellMeasure, + CellMethod, +) +from iris._representation.cube_summary import CubeSummary + +from iris._representation.cube_printout import CubePrinter + + +class TestCubePrintout___str__(tests.IrisTest): + def test_str(self): + # Just check that its str representation is the 'to_string' result. + cube = Cube(0) + printer = CubePrinter(CubeSummary(cube)) + result = str(printer) + self.assertEqual(result, printer.to_string()) + + +def cube_replines(cube, **kwargs): + return CubePrinter(cube).to_string(**kwargs).split("\n") + + +class TestCubePrintout__to_string(tests.IrisTest): + def test_empty(self): + cube = Cube([0]) + rep = cube_replines(cube) + self.assertEqual(rep, ["unknown / (unknown) (-- : 1)"]) + rep = cube_replines(cube, oneline=True) + self.assertEqual(rep, ["unknown / (unknown) (-- : 1)"]) + + def test_scalar_cube_summaries(self): + cube = Cube(0) + rep = cube_replines(cube) + self.assertEqual( + rep, ["unknown / (unknown) (scalar cube)"] + ) + rep = cube_replines(cube, oneline=True) + self.assertEqual(rep, ["unknown / (unknown) (scalar cube)"]) + + def test_name_padding(self): + cube = Cube([1, 2], long_name="cube_accel", units="ms-2") + rep = cube_replines(cube) + self.assertEqual(rep, ["cube_accel / (ms-2) (-- : 2)"]) + rep = cube_replines(cube, name_padding=0) + self.assertEqual(rep, ["cube_accel / (ms-2) (-- : 2)"]) + rep = cube_replines(cube, name_padding=25) + self.assertEqual(rep, ["cube_accel / (ms-2) (-- : 2)"]) + + def test_columns_long_coordname(self): + cube = Cube([0], long_name="short", units=1) + coord = AuxCoord( + [0], long_name="very_very_very_very_very_long_coord_name" + ) + cube.add_aux_coord(coord, 0) + rep = cube_replines(cube) + expected = [ + "short / (1) (-- : 1)", + " Auxiliary coordinates:", + " very_very_very_very_very_long_coord_name x", + ] + self.assertEqual(rep, expected) + rep = cube_replines(cube, oneline=True) + self.assertEqual(rep, ["short / (1) (-- : 1)"]) + + def test_columns_long_attribute(self): + cube = Cube([0], long_name="short", units=1) + cube.attributes[ + "very_very_very_very_very_long_name" + ] = "longish string extends beyond dim columns" + rep = cube_replines(cube) + expected = [ + "short / (1) (-- : 1)", + " Attributes:", + ( + " very_very_very_very_very_long_name " + ": longish string extends beyond dim columns" + ), + ] + self.assertEqual(rep, expected) + + def test_coord_distinguishing_attributes(self): + # Printout of differing attributes to differentiate same-named coords. + # include : vector + scalar + cube = Cube([0, 1], long_name="name", units=1) + # Add a pair of vector coords with same name but different attributes. + cube.add_aux_coord( + AuxCoord([0, 1], long_name="co1", attributes=dict(a=1)), 0 + ) + cube.add_aux_coord( + AuxCoord([0, 1], long_name="co1", attributes=dict(a=2)), 0 + ) + # Likewise for scalar coords with same name but different attributes. + cube.add_aux_coord( + AuxCoord([0], long_name="co2", attributes=dict(a=10, b=12)) + ) + cube.add_aux_coord( + AuxCoord([1], long_name="co2", attributes=dict(a=10, b=11)) + ) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 2)", + " Auxiliary coordinates:", + " co1 x", + " a=1", + " co1 x", + " a=2", + " Scalar coordinates:", + " co2 : 0", + " b=12", + " co2 : 1", + " b=11", + ] + self.assertEqual(rep, expected) + + def test_coord_extra_attributes__array(self): + cube = Cube(0, long_name="name", units=1) + # Add a pair of vector coords with same name but different attributes. + array1 = np.arange(0, 3) + array2 = np.arange(10, 13) + cube.add_aux_coord( + AuxCoord([1.2], long_name="co1", attributes=dict(a=1, arr=array1)) + ) + cube.add_aux_coord( + AuxCoord([3.4], long_name="co1", attributes=dict(a=1, arr=array2)) + ) + + rep = cube_replines(cube) + expected = [ + "name / (1) (scalar cube)", + " Scalar coordinates:", + " co1 : 1.2", + " arr=array([0, 1, 2])", + " co1 : 3.4", + " arr=array([10, 11, 12])", + ] + self.assertEqual(rep, expected) + + def test_coord_extra_attributes__array__long(self): + # Also test with a long array representation. + # NOTE: this also pushes the dimension map right-wards. + array = 10 + np.arange(24.0).reshape((2, 3, 4)) + cube = Cube(0, long_name="name", units=1) + cube.add_aux_coord(AuxCoord([1], long_name="co")) + cube.add_aux_coord( + AuxCoord([2], long_name="co", attributes=dict(a=array + 1.0)) + ) + + rep = cube_replines(cube) + expected = [ + ( + "name / (1) " + " (scalar cube)" + ), + " Scalar coordinates:", + ( + " co " + " : 1" + ), + ( + " co " + " : 2" + ), + ( + " a=array([[[11., 12., 13., 14.], [15., 16., 17.," + " 18.], [19., 20., 21., 22.]],..." + ), + ] + self.assertEqual(rep, expected) + + def test_coord_extra_attributes__string(self): + cube = Cube(0, long_name="name", units=1) + cube.add_aux_coord(AuxCoord([1], long_name="co")) + cube.add_aux_coord( + AuxCoord( + [2], long_name="co", attributes=dict(note="string content") + ) + ) + rep = cube_replines(cube) + expected = [ + "name / (1) (scalar cube)", + " Scalar coordinates:", + " co : 1", + " co : 2", + " note='string content'", + ] + self.assertEqual(rep, expected) + + def test_coord_extra_attributes__string_escaped(self): + cube = Cube(0, long_name="name", units=1) + cube.add_aux_coord(AuxCoord([1], long_name="co")) + cube.add_aux_coord( + AuxCoord( + [2], + long_name="co", + attributes=dict(note="line 1\nline 2\tends."), + ) + ) + rep = cube_replines(cube) + expected = [ + "name / (1) (scalar cube)", + " Scalar coordinates:", + " co : 1", + " co : 2", + " note='line 1\\nline 2\\tends.'", + ] + self.assertEqual(rep, expected) + + def test_coord_extra_attributes__string_overlong(self): + cube = Cube(0, long_name="name", units=1) + cube.add_aux_coord(AuxCoord([1], long_name="co")) + long_string = ( + "this is very very very very very very very " + "very very very very very very very long." + ) + cube.add_aux_coord( + AuxCoord([2], long_name="co", attributes=dict(note=long_string)) + ) + rep = cube_replines(cube) + expected = [ + ( + "name / (1) " + " (scalar cube)" + ), + " Scalar coordinates:", + ( + " co " + " : 1" + ), + ( + " co " + " : 2" + ), + ( + " note='this is very very very very " + "very very very very very very very very..." + ), + ] + self.assertEqual(rep, expected) + + def test_section_vector_dimcoords(self): + cube = Cube(np.zeros((2, 3)), long_name="name", units=1) + cube.add_dim_coord(DimCoord([0, 1], long_name="y"), 0) + cube.add_dim_coord(DimCoord([0, 1, 2], long_name="x"), 1) + + rep = cube_replines(cube) + expected = [ + "name / (1) (y: 2; x: 3)", + " Dimension coordinates:", + " y x -", + " x - x", + ] + self.assertEqual(rep, expected) + + def test_section_vector_auxcoords(self): + cube = Cube(np.zeros((2, 3)), long_name="name", units=1) + cube.add_aux_coord(DimCoord([0, 1], long_name="y"), 0) + cube.add_aux_coord(DimCoord([0, 1, 2], long_name="x"), 1) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 2; -- : 3)", + " Auxiliary coordinates:", + " y x -", + " x - x", + ] + self.assertEqual(rep, expected) + + def test_section_vector_ancils(self): + cube = Cube(np.zeros((2, 3)), long_name="name", units=1) + cube.add_ancillary_variable( + AncillaryVariable([0, 1], long_name="av1"), 0 + ) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 2; -- : 3)", + " Ancillary variables:", + " av1 x -", + ] + self.assertEqual(rep, expected) + + def test_section_vector_cell_measures(self): + cube = Cube(np.zeros((2, 3)), long_name="name", units=1) + cube.add_cell_measure(CellMeasure([0, 1, 2], long_name="cm"), 1) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 2; -- : 3)", + " Cell measures:", + " cm - x", + ] + self.assertEqual(rep, expected) + + def test_section_scalar_coords(self): + # incl points + bounds + # TODO: ought to incorporate coord-based summary + # - which would allow for special printout of time values + cube = Cube([0], long_name="name", units=1) + cube.add_aux_coord(DimCoord([0.0], long_name="unbounded")) + cube.add_aux_coord(DimCoord([0], bounds=[[0, 7]], long_name="bounded")) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 1)", + " Scalar coordinates:", + " bounded : 0, bound=(0, 7)", + " unbounded : 0.0", + ] + self.assertEqual(rep, expected) + + def test_section_scalar_coords__string(self): + # incl a newline-escaped one + # incl a long (clipped) one + # CHECK THAT CLIPPED+ESCAPED WORKS (don't lose final quote) + cube = Cube([0], long_name="name", units=1) + cube.add_aux_coord(AuxCoord(["string-value"], long_name="text")) + long_string = ( + "A string value which is very very very very very very " + "very very very very very very very very long." + ) + cube.add_aux_coord( + AuxCoord([long_string], long_name="very_long_string") + ) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 1)", + " Scalar coordinates:", + " text : string-value", + ( + " very_long_string : A string value which is " + "very very very very very very very very very very..." + ), + ] + self.assertEqual(rep, expected) + + def test_section_scalar_cell_measures(self): + cube = Cube(np.zeros((2, 3)), long_name="name", units=1) + cube.add_cell_measure(CellMeasure([0], long_name="cm")) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 2; -- : 3)", + " Scalar cell measures:", + " cm", + ] + self.assertEqual(rep, expected) + + def test_section_scalar_ancillaries(self): + # There *is* no section for this. But there probably ought to be. + cube = Cube(np.zeros((2, 3)), long_name="name", units=1) + cube.add_ancillary_variable(AncillaryVariable([0], long_name="av")) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 2; -- : 3)", + " Ancillary variables:", + " av - -", + ] + self.assertEqual(rep, expected) + + def test_section_cube_attributes(self): + cube = Cube([0], long_name="name", units=1) + cube.attributes["number"] = 1.2 + cube.attributes["list"] = [3] + cube.attributes["string"] = "four five in a string" + cube.attributes["z_tupular"] = (6, (7, 8)) + rep = cube_replines(cube) + # NOTE: 'list' before 'number', as it uses "sorted(attrs.items())" + expected = [ + "name / (1) (-- : 1)", + " Attributes:", + " list : [3]", + " number : 1.2", + " string : four five in a string", + " z_tupular : (6, (7, 8))", + ] + self.assertEqual(rep, expected) + + def test_section_cube_attributes__string_extras(self): + cube = Cube([0], long_name="name", units=1) + # Overlong strings are truncated (with iris.util.clip_string). + long_string = ( + "this is very very very very very very very " + "very very very very very very very long." + ) + # Strings with embedded newlines or quotes are printed in quoted form. + cube.attributes["escaped"] = "escaped\tstring" + cube.attributes["long"] = long_string + cube.attributes["long_multi"] = "multi\nline, " + long_string + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 1)", + " Attributes:", + " escaped : 'escaped\\tstring'", + ( + " long : this is very very very " + "very very very very very very very very very very..." + ), + ( + " long_multi : 'multi\\nline, " + "this is very very very very very very very very very very..." + ), + ] + self.assertEqual(rep, expected) + + def test_section_cube_attributes__array(self): + # Including a long one, which gets a truncated representation. + cube = Cube([0], long_name="name", units=1) + small_array = np.array([1.2, 3.4]) + large_array = np.arange(36).reshape((18, 2)) + cube.attributes["array"] = small_array + cube.attributes["bigarray"] = large_array + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 1)", + " Attributes:", + " array : array([1.2, 3.4])", + ( + " bigarray : array([[ 0, 1], [ 2, 3], " + "[ 4, 5], [ 6, 7], [ 8, 9], [10, 11], [12, 13],..." + ), + ] + self.assertEqual(rep, expected) + + def test_section_cell_methods(self): + cube = Cube([0], long_name="name", units=1) + cube.add_cell_method(CellMethod("stdev", "area")) + cube.add_cell_method( + CellMethod( + method="mean", + coords=["y", "time"], + intervals=["10m", "3min"], + comments=["vertical", "=duration"], + ) + ) + rep = cube_replines(cube) + # Note: not alphabetical -- provided order is significant + expected = [ + "name / (1) (-- : 1)", + " Cell methods:", + " stdev: area", + " mean: y (10m, vertical), time (3min, =duration)", + ] + self.assertEqual(rep, expected) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/representation/cube_printout/test_Table.py b/lib/iris/tests/unit/representation/cube_printout/test_Table.py new file mode 100644 index 0000000000..89734ab878 --- /dev/null +++ b/lib/iris/tests/unit/representation/cube_printout/test_Table.py @@ -0,0 +1,160 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for :class:`iris._representation.cube_printout.Table`.""" +import iris.tests as tests + +from iris._representation.cube_printout import Table + + +class TestTable(tests.IrisTest): + # Note: this is just barely an independent definition, not *strictly* part + # of CubePrinter, but effectively more-or-less so. + def setUp(self): + table = Table() + table.add_row(["one", "b", "three"], aligns=["left", "right", "left"]) + table.add_row(["a", "two", "c"], aligns=["right", "left", "right"]) + self.simple_table = table + + def test_empty(self): + table = Table() + self.assertIsNone(table.n_columns) + self.assertEqual(len(table.rows), 0) + self.assertIsNone(table.col_widths) + # Check other methods : should be ok but do nothing. + table.set_min_column_widths() # Ok but does nothing. + self.assertIsNone(table.col_widths) + self.assertEqual(table.formatted_as_strings(), []) + self.assertEqual(str(table), "") + + def test_basic_content(self): + # Mirror the above 'empty' tests on a small basic table. + table = self.simple_table + self.assertEqual(table.n_columns, 3) + self.assertEqual(len(table.rows), 2) + self.assertIsNone(table.col_widths) + table.set_min_column_widths() # Ok but does nothing. + self.assertEqual(table.col_widths, [3, 3, 5]) + self.assertEqual( + table.formatted_as_strings(), ["one b three", " a two c"] + ) + self.assertEqual(str(table), "one b three\n a two c") + + def test_copy(self): + table = self.simple_table + # Add some detail information + table.rows[1].i_col_unlimited = 77 # Doesn't actually affect anything + table.col_widths = [10, 15, 12] + # Make the copy + table2 = table.copy() + self.assertIsNot(table2, table) + self.assertNotEqual(table2, table) # Note: equality is not implemented + # Check the parts match the original. + self.assertEqual(len(table2.rows), len(table.rows)) + for row2, row in zip(table2.rows, table.rows): + self.assertEqual(row2.cols, row.cols) + self.assertEqual(row2.aligns, row.aligns) + self.assertEqual(row2.i_col_unlimited, row.i_col_unlimited) + + def test_add_row(self): + table = Table() + self.assertEqual(table.n_columns, None) + # Add onw row. + table.add_row(["one", "two", "three"], aligns=["left", "left", "left"]) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.n_columns, 3) + self.assertIsNone(table.rows[0].i_col_unlimited) + # Second row ok. + table.add_row( + ["x", "y", "z"], + aligns=["right", "right", "right"], + i_col_unlimited=199, + ) + self.assertEqual(len(table.rows), 2) + self.assertEqual(table.rows[-1].i_col_unlimited, 199) + + # Fails with bad number of columns + regex = "columns.*!=.*existing" + with self.assertRaisesRegex(ValueError, regex): + table.add_row(["1", "2"], ["left", "right"]) + + # Fails with bad number of aligns + regex = "aligns.*!=.*col" + with self.assertRaisesRegex(ValueError, regex): + table.add_row(["1", "2", "3"], ["left", "left", "left", "left"]) + + def test_formatted_as_strings(self): + # Test simple self-print is same as + table = Table() + aligns = ["left", "right", "left"] + table.add_row(["1", "266", "32"], aligns) + table.add_row(["123", "2", "3"], aligns) + + # Check that printing calculates default column widths, and result.. + self.assertEqual(table.col_widths, None) + result = table.formatted_as_strings() + self.assertEqual(result, ["1 266 32", "123 2 3"]) + self.assertEqual(table.col_widths, [3, 3, 2]) + + def test_fail_bad_alignments(self): + # Invalid 'aligns' content : only detected when printed + table = Table() + table.add_row(["1", "2", "3"], ["left", "right", "BAD"]) + regex = 'Unknown alignment "BAD"' + with self.assertRaisesRegex(ValueError, regex): + str(table) + + def test_table_set_width(self): + # Check that changes do *not* affect pre-existing widths. + table = Table() + aligns = ["left", "right", "left"] + table.col_widths = [3, 3, 2] + table.add_row(["333", "333", "22"], aligns) + table.add_row(["a", "b", "c"], aligns) + table.add_row(["12345", "12345", "12345"], aligns) + result = table.formatted_as_strings() + self.assertEqual(table.col_widths, [3, 3, 2]) + self.assertEqual( + result, + [ + "333 333 22", + "a b c", + "12345 12345 12345", # These are exceeding the given widths. + ], + ) + + def test_unlimited_column(self): + table = Table() + aligns = ["left", "right", "left"] + table.add_row(["a", "beee", "c"], aligns) + table.add_row( + ["abcd", "any-longer-stuff", "this"], aligns, i_col_unlimited=1 + ) + table.add_row(["12", "x", "yy"], aligns) + result = table.formatted_as_strings() + self.assertEqual( + result, + [ + "a beee c", + "abcd any-longer-stuff this", + # NOTE: the widths-calc is ignoring cols 1-2, but + # entry#0 *is* extending the width of col#0 + "12 x yy", + ], + ) + + def test_str(self): + # Check that str returns the formatted_as_strings() output. + table = Table() + aligns = ["left", "left", "left"] + table.add_row(["one", "two", "three"], aligns=aligns) + table.add_row(["1", "2", "3"], aligns=aligns) + expected = "\n".join(table.formatted_as_strings()) + result = str(table) + self.assertEqual(result, expected) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/representation/cube_summary/__init__.py b/lib/iris/tests/unit/representation/cube_summary/__init__.py new file mode 100644 index 0000000000..c20a621ba2 --- /dev/null +++ b/lib/iris/tests/unit/representation/cube_summary/__init__.py @@ -0,0 +1,6 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :mod:`iris._representation.cube_summary` module.""" diff --git a/lib/iris/tests/unit/representation/test_representation.py b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py similarity index 93% rename from lib/iris/tests/unit/representation/test_representation.py rename to lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py index 238772a10c..9794cc7f28 100644 --- a/lib/iris/tests/unit/representation/test_representation.py +++ b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py @@ -3,7 +3,7 @@ # This file is part of Iris and is released under the LGPL license. # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. -"""Unit tests for the :mod:`iris._representation` module.""" +"""Unit tests for :class:`iris._representation.cube_summary.CubeSummary`.""" # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -11,7 +11,6 @@ import numpy as np -import iris._representation from iris.coords import ( AncillaryVariable, AuxCoord, @@ -21,6 +20,8 @@ ) from iris.cube import Cube +from iris._representation.cube_summary import CubeSummary + def example_cube(): cube = Cube( @@ -40,7 +41,7 @@ def setUp(self): self.cube = example_cube() def test_header(self): - rep = iris._representation.CubeSummary(self.cube) + rep = CubeSummary(self.cube) header_left = rep.header.nameunit header_right = rep.header.dimension_header.contents @@ -49,7 +50,7 @@ def test_header(self): def test_blank_cube(self): cube = Cube([1, 2]) - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) self.assertEqual(rep.header.nameunit, "unknown / (unknown)") self.assertEqual(rep.header.dimension_header.contents, ["-- : 2"]) @@ -85,7 +86,7 @@ def test_blank_cube(self): self.assertTrue(scalar_section.is_empty()) def test_vector_coord(self): - rep = iris._representation.CubeSummary(self.cube) + rep = CubeSummary(self.cube) dim_section = rep.vector_sections["Dimension coordinates:"] self.assertEqual(len(dim_section.contents), 1) @@ -119,7 +120,7 @@ def test_scalar_coord(self): cube.add_aux_coord(scalar_coord_with_bounds) cube.add_aux_coord(scalar_coord_simple_text) cube.add_aux_coord(scalar_coord_awkward_text) - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) scalar_section = rep.scalar_sections["Scalar coordinates:"] @@ -152,7 +153,7 @@ def test_cell_measure(self): cube = self.cube cell_measure = CellMeasure([1, 2, 3], long_name="foo") cube.add_cell_measure(cell_measure, 0) - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) cm_section = rep.vector_sections["Cell measures:"] self.assertEqual(len(cm_section.contents), 1) @@ -165,7 +166,7 @@ def test_ancillary_variable(self): cube = self.cube cell_measure = AncillaryVariable([1, 2, 3], long_name="foo") cube.add_ancillary_variable(cell_measure, 0) - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) av_section = rep.vector_sections["Ancillary variables:"] self.assertEqual(len(av_section.contents), 1) @@ -177,7 +178,7 @@ def test_ancillary_variable(self): def test_attributes(self): cube = self.cube cube.attributes = {"a": 1, "b": "two", "c": " this \n that\tand."} - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) attribute_section = rep.scalar_sections["Attributes:"] attribute_contents = attribute_section.contents @@ -196,7 +197,7 @@ def test_cell_methods(self): cube.add_cell_method(cell_method_xy) cube.add_cell_method(cell_method_x) - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) cell_method_section = rep.scalar_sections["Cell methods:"] expected_contents = ["mean: x, y", "mean: x"] self.assertEqual(cell_method_section.contents, expected_contents) @@ -205,7 +206,7 @@ def test_scalar_cube(self): cube = self.cube while cube.ndim > 0: cube = cube[0] - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) self.assertEqual(rep.header.nameunit, "air_temperature / (K)") self.assertTrue(rep.header.dimension_header.scalar) self.assertEqual(rep.header.dimension_header.dim_names, []) @@ -232,7 +233,7 @@ def test_coord_attributes(self): co2 = co1.copy() co2.attributes.update(dict(a=7, z=77, text="ok", text2="multi\nline")) cube.add_aux_coord(co2, cube.coord_dims(co1)) - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) co1_summ = rep.vector_sections["Dimension coordinates:"].contents[0] co2_summ = rep.vector_sections["Auxiliary coordinates:"].contents[0] # Notes: 'b' is same so does not appear; sorted order; quoted strings. @@ -248,7 +249,7 @@ def test_array_attributes(self): co2 = co1.copy() co2.attributes.update(dict(b=2, array=np.array([3.2, 1]))) cube.add_aux_coord(co2, cube.coord_dims(co1)) - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) co1_summ = rep.vector_sections["Dimension coordinates:"].contents[0] co2_summ = rep.vector_sections["Auxiliary coordinates:"].contents[0] self.assertEqual(co1_summ.extra, "array=array([1.2, 3. ])") @@ -291,7 +292,7 @@ def test_attributes_subtle_differences(self): for co in (co3a, co3b): cube.add_aux_coord(co) - rep = iris._representation.CubeSummary(cube) + rep = CubeSummary(cube) co_summs = rep.scalar_sections["Scalar coordinates:"].contents co1a_summ, co1b_summ = co_summs[0:2] self.assertEqual(co1a_summ.extra, "arr2=array([1, 2])") From b3861fa4a3dd30e977bc29a64565d4d206b6cf97 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 23 Jun 2021 11:39:49 +0100 Subject: [PATCH 02/23] Fix some import orderings. --- .../cube_printout/test_CubePrintout.py | 13 ++++++------- .../unit/representation/cube_printout/test_Table.py | 3 +-- .../representation/cube_summary/test_CubeSummary.py | 3 +-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py index 0b592e9b6c..ab7426a447 100644 --- a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py +++ b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py @@ -4,21 +4,20 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """Unit tests for :class:`iris._representation.cube_printout.CubePrintout`.""" -import iris.tests as tests +import iris.tests as tests # isort:skip import numpy as np -from iris.cube import Cube +from iris._representation.cube_printout import CubePrinter +from iris._representation.cube_summary import CubeSummary from iris.coords import ( - AuxCoord, - DimCoord, AncillaryVariable, + AuxCoord, CellMeasure, CellMethod, + DimCoord, ) -from iris._representation.cube_summary import CubeSummary - -from iris._representation.cube_printout import CubePrinter +from iris.cube import Cube class TestCubePrintout___str__(tests.IrisTest): diff --git a/lib/iris/tests/unit/representation/cube_printout/test_Table.py b/lib/iris/tests/unit/representation/cube_printout/test_Table.py index 89734ab878..2ff6738998 100644 --- a/lib/iris/tests/unit/representation/cube_printout/test_Table.py +++ b/lib/iris/tests/unit/representation/cube_printout/test_Table.py @@ -4,9 +4,8 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """Unit tests for :class:`iris._representation.cube_printout.Table`.""" -import iris.tests as tests - from iris._representation.cube_printout import Table +import iris.tests as tests class TestTable(tests.IrisTest): diff --git a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py index 9794cc7f28..e3b7c69338 100644 --- a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py +++ b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py @@ -11,6 +11,7 @@ import numpy as np +from iris._representation.cube_summary import CubeSummary from iris.coords import ( AncillaryVariable, AuxCoord, @@ -20,8 +21,6 @@ ) from iris.cube import Cube -from iris._representation.cube_summary import CubeSummary - def example_cube(): cube = Cube( From 8ca658a95e24b8b0825f26c62747a9b0e7dbfa0a Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 23 Jun 2021 12:59:16 +0100 Subject: [PATCH 03/23] Replace old cube summary code with new CubeSummary-based implementation. --- lib/iris/cube.py | 472 ++--------------------------------------------- 1 file changed, 14 insertions(+), 458 deletions(-) diff --git a/lib/iris/cube.py b/lib/iris/cube.py index fca9230a5a..bf30fc81c0 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -727,50 +727,6 @@ def _is_single_item(testee): return isinstance(testee, str) or not isinstance(testee, Iterable) -def _cubesummary_tolerant_lines(text): - """ - Produce a regularised version of a summary string, as a list of lines. - - Split at '\n' into lines. - Add spaces around all ':' characters (for attributes formatting). - Replace whitespace with single spaces, and remove leading+trailing. - - """ - lines = text.split("\n") - lines = [line.replace(":", " : ") for line in lines] - stripped_lines = [] - for line in lines: - line = line.strip() - while " " in line: - line = line.replace(" ", " ") - stripped_lines.append(line) - return stripped_lines - - -def _cubesummary_traditional_lines(cube): - # Produce a list of lines from an old-method (extended) cube summary. - return _cubesummary_tolerant_lines(cube.summary()) - - -def _cubesummary_newstyle_lines(cube): - # Produce a list of lines from a new-method (extended) cube summary. - from iris._representation.cube_printout import CubePrinter - - return _cubesummary_tolerant_lines(CubePrinter(cube).to_string()) - - -def _cubesummary_newold_compare(cube): - """ - A temporary routine to compare old + new output for all cube summary - printouts. - - """ - - oldstyle = _cubesummary_traditional_lines(cube) - newstyle = _cubesummary_newstyle_lines(cube) - assert oldstyle == newstyle - - class Cube(CFVariableMixin): """ A single Iris cube of data and metadata. @@ -2286,428 +2242,28 @@ def aux_factories(self): """Return a tuple of all the coordinate factories.""" return tuple(self._aux_factories) - def _summary_coord_extra(self, coord, indent): - # Returns the text needed to ensure this coordinate can be - # distinguished from all others with the same name. - extra = "" - similar_coords = self.coords(coord.name()) - if len(similar_coords) > 1: - similar_coords.remove(coord) - # Look for any attributes that vary. - vary = set() - for key, value in coord.attributes.items(): - for similar_coord in similar_coords: - if key not in similar_coord.attributes: - vary.add(key) - break - if not np.array_equal( - similar_coord.attributes[key], value - ): - vary.add(key) - break - keys = sorted(vary) - bits = [ - "{}={!r}".format(key, coord.attributes[key]) for key in keys - ] - if bits: - extra = indent + ", ".join(bits) - return extra - - def _summary_extra(self, coords, summary, indent): - # Where necessary, inserts extra lines into the summary to ensure - # coordinates can be distinguished. - new_summary = [] - for coord, summary in zip(coords, summary): - new_summary.append(summary) - extra = self._summary_coord_extra(coord, indent) - if extra: - new_summary.append(extra) - return new_summary - def summary(self, shorten=False, name_padding=35): """ - Unicode string summary of the Cube with name, a list of dim coord names - versus length and optionally relevant coordinate information. - - """ - # Create a set to contain the axis names for each data dimension. - dim_names = [set() for dim in range(len(self.shape))] - - # Add the dim_coord names that participate in the associated data - # dimensions. - for dim in range(len(self.shape)): - dim_coords = self.coords(contains_dimension=dim, dim_coords=True) - if dim_coords: - dim_names[dim].add(dim_coords[0].name()) - else: - dim_names[dim].add("-- ") - - # Convert axes sets to lists and sort. - dim_names = [sorted(names, key=sorted_axes) for names in dim_names] - - # Generate textual summary of the cube dimensionality. - if self.shape == (): - dimension_header = "scalar cube" - else: - dimension_header = "; ".join( - [ - ", ".join(dim_names[dim]) + ": %d" % dim_shape - for dim, dim_shape in enumerate(self.shape) - ] - ) - - nameunit = "{name} / ({units})".format( - name=self.name(), units=self.units - ) - cube_header = "{nameunit!s:{length}} ({dimension})".format( - length=name_padding, nameunit=nameunit, dimension=dimension_header - ) - summary = "" - - # Generate full cube textual summary. - if not shorten: - indent = 10 - extra_indent = " " * 13 - - # Cache the derived coords so we can rely on consistent - # object IDs. - derived_coords = self.derived_coords - # Determine the cube coordinates that are scalar (single-valued) - # AND non-dimensioned. - dim_coords = self.dim_coords - aux_coords = self.aux_coords - all_coords = dim_coords + aux_coords + derived_coords - scalar_coords = [ - coord - for coord in all_coords - if not self.coord_dims(coord) and coord.shape == (1,) - ] - # Determine the cube coordinates that are not scalar BUT - # dimensioned. - scalar_coord_ids = set(map(id, scalar_coords)) - vector_dim_coords = [ - coord - for coord in dim_coords - if id(coord) not in scalar_coord_ids - ] - vector_aux_coords = [ - coord - for coord in aux_coords - if id(coord) not in scalar_coord_ids - ] - vector_derived_coords = [ - coord - for coord in derived_coords - if id(coord) not in scalar_coord_ids - ] - - # cell measures - vector_cell_measures = [ - cm for cm in self.cell_measures() if cm.shape != (1,) - ] - - # Ancillary Variables - vector_ancillary_variables = [ - av for av in self.ancillary_variables() - ] - - # Sort scalar coordinates by name. - scalar_coords.sort(key=lambda coord: coord.name()) - # Sort vector coordinates by data dimension and name. - vector_dim_coords.sort( - key=lambda coord: (self.coord_dims(coord), coord.name()) - ) - vector_aux_coords.sort( - key=lambda coord: (self.coord_dims(coord), coord.name()) - ) - vector_derived_coords.sort( - key=lambda coord: (self.coord_dims(coord), coord.name()) - ) - - # - # Generate textual summary of cube vector coordinates. - # - def vector_summary( - vector_coords, - cube_header, - max_line_offset, - cell_measures=None, - ancillary_variables=None, - ): - """ - Generates a list of suitably aligned strings containing coord - names and dimensions indicated by one or more 'x' symbols. - - .. note:: - - The function may need to update the cube header so this is - returned with the list of strings. - - """ - if cell_measures is None: - cell_measures = [] - if ancillary_variables is None: - ancillary_variables = [] - vector_summary = [] - vectors = [] - - # Identify offsets for each dimension text marker. - alignment = np.array( - [ - index - for index, value in enumerate(cube_header) - if value == ":" - ] - ) - - # Generate basic textual summary for each vector coordinate - # - WITHOUT dimension markers. - for dim_meta in ( - vector_coords + cell_measures + ancillary_variables - ): - vector_summary.append( - "%*s%s" - % (indent, " ", iris.util.clip_string(dim_meta.name())) - ) - min_alignment = min(alignment) - - # Determine whether the cube header requires realignment - # due to one or more longer vector coordinate summaries. - if max_line_offset >= min_alignment: - delta = max_line_offset - min_alignment + 5 - cube_header = "%-*s (%s)" % ( - int(name_padding + delta), - nameunit, - dimension_header, - ) - alignment += delta - - if vector_coords: - # Generate full textual summary for each vector coordinate - # - WITH dimension markers. - for index, coord in enumerate(vector_coords): - dims = self.coord_dims(coord) - - for dim in range(len(self.shape)): - width = alignment[dim] - len(vector_summary[index]) - char = "x" if dim in dims else "-" - line = "{pad:{width}}{char}".format( - pad=" ", width=width, char=char - ) - vector_summary[index] += line - vectors = vectors + vector_coords - if cell_measures: - # Generate full textual summary for each vector cell - # measure - WITH dimension markers. - for index, cell_measure in enumerate(cell_measures): - dims = self.cell_measure_dims(cell_measure) - - for dim in range(len(self.shape)): - width = alignment[dim] - len(vector_summary[index]) - char = "x" if dim in dims else "-" - line = "{pad:{width}}{char}".format( - pad=" ", width=width, char=char - ) - vector_summary[index] += line - vectors = vectors + cell_measures - if ancillary_variables: - # Generate full textual summary for each vector ancillary - # variable - WITH dimension markers. - for index, av in enumerate(ancillary_variables): - dims = self.ancillary_variable_dims(av) - - for dim in range(len(self.shape)): - width = alignment[dim] - len(vector_summary[index]) - char = "x" if dim in dims else "-" - line = "{pad:{width}}{char}".format( - pad=" ", width=width, char=char - ) - vector_summary[index] += line - vectors = vectors + ancillary_variables - # Interleave any extra lines that are needed to distinguish - # the coordinates. - vector_summary = self._summary_extra( - vectors, vector_summary, extra_indent - ) - - return vector_summary, cube_header - - # Calculate the maximum line offset. - max_line_offset = 0 - for coord in ( - list(all_coords) - + self.ancillary_variables() - + self.cell_measures() - ): - max_line_offset = max( - max_line_offset, - len( - "%*s%s" - % ( - indent, - " ", - iris.util.clip_string(str(coord.name())), - ) - ), - ) - - if vector_dim_coords: - dim_coord_summary, cube_header = vector_summary( - vector_dim_coords, cube_header, max_line_offset - ) - summary += "\n Dimension coordinates:\n" + "\n".join( - dim_coord_summary - ) - - if vector_aux_coords: - aux_coord_summary, cube_header = vector_summary( - vector_aux_coords, cube_header, max_line_offset - ) - summary += "\n Auxiliary coordinates:\n" + "\n".join( - aux_coord_summary - ) - - if vector_derived_coords: - derived_coord_summary, cube_header = vector_summary( - vector_derived_coords, cube_header, max_line_offset - ) - summary += "\n Derived coordinates:\n" + "\n".join( - derived_coord_summary - ) + String summary of the Cube with name+units, a list of dim coord names + versus length and, optionally, a summary of all other components. - # - # Generate summary of cube cell measures attribute - # - if vector_cell_measures: - cell_measure_summary, cube_header = vector_summary( - [], - cube_header, - max_line_offset, - cell_measures=vector_cell_measures, - ) - summary += "\n Cell measures:\n" - summary += "\n".join(cell_measure_summary) - - # - # Generate summary of cube ancillary variables attribute - # - if vector_ancillary_variables: - ancillary_variable_summary, cube_header = vector_summary( - [], - cube_header, - max_line_offset, - ancillary_variables=vector_ancillary_variables, - ) - summary += "\n Ancillary variables:\n" - summary += "\n".join(ancillary_variable_summary) - - # - # Generate textual summary of cube scalar coordinates. - # - scalar_summary = [] - - if scalar_coords: - for coord in scalar_coords: - if ( - coord.units in ["1", "no_unit", "unknown"] - or coord.units.is_time_reference() - ): - unit = "" - else: - unit = " {!s}".format(coord.units) - - # Format cell depending on type of point and whether it - # has a bound. - coord_cell = coord.cell(0) - if isinstance(coord_cell.point, str): - # Indent string type coordinates - coord_cell_split = [ - iris.util.clip_string(str(item)) - for item in coord_cell.point.split("\n") - ] - line_sep = "\n{pad:{width}}".format( - pad=" ", width=indent + len(coord.name()) + 2 - ) - coord_cell_str = line_sep.join(coord_cell_split) + unit - else: - coord_cell_cpoint = coord_cell.point - coord_cell_cbound = coord_cell.bound - - coord_cell_str = "{!s}{}".format( - coord_cell_cpoint, unit - ) - if coord_cell_cbound is not None: - bound = "({})".format( - ", ".join( - str(val) for val in coord_cell_cbound - ) - ) - coord_cell_str += ", bound={}{}".format( - bound, unit - ) - - scalar_summary.append( - "{pad:{width}}{name}: {cell}".format( - pad=" ", - width=indent, - name=coord.name(), - cell=coord_cell_str, - ) - ) - - # Interleave any extra lines that are needed to distinguish - # the coordinates. - scalar_summary = self._summary_extra( - scalar_coords, scalar_summary, extra_indent - ) - - summary += "\n Scalar coordinates:\n" + "\n".join( - scalar_summary - ) - - # cell measures - scalar_cell_measures = [ - cm for cm in self.cell_measures() if cm.shape == (1,) - ] - if scalar_cell_measures: - summary += "\n Scalar cell measures:\n" - scalar_cms = [ - " {}".format(cm.name()) - for cm in scalar_cell_measures - ] - summary += "\n".join(scalar_cms) - - # - # Generate summary of cube attributes. - # - if self.attributes: - attribute_lines = [] - for name, value in sorted(self.attributes.items()): - value = iris.util.clip_string(str(value)) - line = "{pad:{width}}{name}: {value}".format( - pad=" ", width=indent, name=name, value=value - ) - attribute_lines.append(line) - summary += "\n Attributes:\n" + "\n".join(attribute_lines) - - # - # Generate summary of cube cell methods - # - if self.cell_methods: - summary += "\n Cell methods:\n" - cm_lines = [] - - for cm in self.cell_methods: - cm_lines.append("%*s%s" % (indent, " ", str(cm))) - summary += "\n".join(cm_lines) + Kwargs: + * shorten (bool): + If set, produce a one-line summary of minimal width, showing only + the cube name, units and dimensions. + When not set (default), produces a full multi-line summary string. + * name_padding (int): + Control the minimum width of cube name+units, before the dimension + map section. Only applies when `shorten`=False. - # Construct the final cube summary. - summary = cube_header + summary + """ + from iris._representation.cube_printout import CubePrinter + printer = CubePrinter(self) + summary = printer.to_string(oneline=shorten, name_padding=name_padding) return summary def __str__(self): - _cubesummary_newold_compare(self) return self.summary() def __repr__(self): From d1e43cffa69972b36a07be194311a921aae2987c Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 23 Jun 2021 13:49:12 +0100 Subject: [PATCH 04/23] Removed additional colons. --- lib/iris/_representation/cube_printout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index 619ca540dd..8cf747bb4e 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -262,12 +262,12 @@ def add_scalar_row(name, value=""): title = sect_name.lower() if "scalar coordinate" in title: for item in sect.contents: - add_scalar_row(item.name, ": " + item.content) + add_scalar_row(item.name, item.content) if item.extra: add_scalar_row(item_to_extra_indent + item.extra) elif "attribute" in title: for title, value in zip(sect.names, sect.values): - add_scalar_row(title, ": " + value) + add_scalar_row(title, value) elif "scalar cell measure" in title or "cell method" in title: # These are just strings: nothing in the 'value' column. for name in sect.contents: From d2c86d192a581b31bdc03094ff039dd6d6c131f0 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 23 Jun 2021 21:00:17 +0100 Subject: [PATCH 05/23] Slightly altered behaviour + specific test fixes. --- lib/iris/_representation/cube_printout.py | 11 +- .../cube_printout/test_CubePrintout.py | 123 +++++++++++++----- 2 files changed, 94 insertions(+), 40 deletions(-) diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index 8cf747bb4e..c2c0316e7d 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -316,14 +316,14 @@ def _decorated_table(table, name_padding=None): return table - def _oneline_string(self): + def _oneline_string(self, name_padding): """Produce a one-line summary string.""" # Copy existing content -- just the header line. table = Table(rows=[self.table.rows[0]]) # Note: by excluding other columns, we get a minimum-width result. # Add standard decorations. - table = self._decorated_table(table, name_padding=0) + table = self._decorated_table(table, name_padding=name_padding) # Format (with no extra spacing) --> one-line result (oneline_result,) = table.formatted_as_strings() @@ -343,18 +343,17 @@ def to_string(self, oneline=False, name_padding=35): Args: * oneline (bool): - If set, produce a one-line summary (without any extra spacings). - Default is False = produce full (multiline) summary. + If set, produce a one-line summary. + Default is False = produce full (multiline) summary. * name_padding (int): The minimum width for the "name" (#0) column. - Used for multiline output only. Returns: result (string) """ if oneline: - result = self._oneline_string() + result = self._oneline_string(name_padding) else: result = self._multiline_summary(name_padding) diff --git a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py index ab7426a447..434ae7cd8e 100644 --- a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py +++ b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py @@ -37,18 +37,71 @@ class TestCubePrintout__to_string(tests.IrisTest): def test_empty(self): cube = Cube([0]) rep = cube_replines(cube) - self.assertEqual(rep, ["unknown / (unknown) (-- : 1)"]) + expect = ["unknown / (unknown) (-- : 1)"] + self.assertEqual(expect, rep) + + def test_shortform__default(self): + cube = Cube([0]) + expect = ["unknown / (unknown) (-- : 1)"] + # In this case, default one-line is the same. + rep = cube_replines(cube, oneline=True) + self.assertEqual(expect, rep) + + def test_shortform__compressed(self): + cube = Cube([0]) + rep = cube_replines(cube, oneline=True, name_padding=0) + expect = ["unknown / (unknown) (-- : 1)"] + self.assertEqual(rep, expect) + + def _sample_wide_cube(self): + cube = Cube([0, 1]) + cube.add_aux_coord( + AuxCoord( + [0, 1], + long_name='long long long long long long long long name' + ), + 0 + ) + return cube + + def test_wide_cube(self): + # For comparison with the shortform and padding-controlled cases. + cube = self._sample_wide_cube() + rep = cube_replines(cube) + expect_full = [ + 'unknown / (unknown) (-- : 2)', + ' Auxiliary coordinates:', + ' long long long long long long long long name x' + ] + self.assertEqual(expect_full, rep) + + def test_shortform__wide__default(self): + cube = self._sample_wide_cube() rep = cube_replines(cube, oneline=True) - self.assertEqual(rep, ["unknown / (unknown) (-- : 1)"]) + # *default* one-line is shorter than full header, but not minimal. + expect = ["unknown / (unknown) (-- : 2)"] + self.assertEqual(rep, expect) + + def test_shortform__wide__compressed(self): + cube = self._sample_wide_cube() + rep = cube_replines(cube, oneline=True, name_padding=0) + expect = ["unknown / (unknown) (-- : 2)"] + self.assertEqual(rep, expect) + + def test_shortform__wide__intermediate(self): + cube = self._sample_wide_cube() + rep = cube_replines(cube, oneline=True, name_padding=25) + expect = ['unknown / (unknown) (-- : 2)'] + self.assertEqual(expect, rep) def test_scalar_cube_summaries(self): cube = Cube(0) + expect = ["unknown / (unknown) (scalar cube)"] rep = cube_replines(cube) - self.assertEqual( - rep, ["unknown / (unknown) (scalar cube)"] - ) + self.assertEqual(expect, rep) + # Shortform is the same. rep = cube_replines(cube, oneline=True) - self.assertEqual(rep, ["unknown / (unknown) (scalar cube)"]) + self.assertEqual(expect, rep) def test_name_padding(self): cube = Cube([1, 2], long_name="cube_accel", units="ms-2") @@ -71,9 +124,11 @@ def test_columns_long_coordname(self): " Auxiliary coordinates:", " very_very_very_very_very_long_coord_name x", ] - self.assertEqual(rep, expected) + self.assertEqual(expected, rep) rep = cube_replines(cube, oneline=True) - self.assertEqual(rep, ["short / (1) (-- : 1)"]) + # Note: the default short-form is short-ER, but not minimal. + short_expected = ["short / (1) (-- : 1)"] + self.assertEqual(short_expected, rep) def test_columns_long_attribute(self): cube = Cube([0], long_name="short", units=1) @@ -86,7 +141,7 @@ def test_columns_long_attribute(self): " Attributes:", ( " very_very_very_very_very_long_name " - ": longish string extends beyond dim columns" + "longish string extends beyond dim columns" ), ] self.assertEqual(rep, expected) @@ -119,9 +174,9 @@ def test_coord_distinguishing_attributes(self): " co1 x", " a=2", " Scalar coordinates:", - " co2 : 0", + " co2 0", " b=12", - " co2 : 1", + " co2 1", " b=11", ] self.assertEqual(rep, expected) @@ -142,9 +197,9 @@ def test_coord_extra_attributes__array(self): expected = [ "name / (1) (scalar cube)", " Scalar coordinates:", - " co1 : 1.2", + " co1 1.2", " arr=array([0, 1, 2])", - " co1 : 3.4", + " co1 3.4", " arr=array([10, 11, 12])", ] self.assertEqual(rep, expected) @@ -168,11 +223,11 @@ def test_coord_extra_attributes__array__long(self): " Scalar coordinates:", ( " co " - " : 1" + " 1" ), ( " co " - " : 2" + " 2" ), ( " a=array([[[11., 12., 13., 14.], [15., 16., 17.," @@ -193,8 +248,8 @@ def test_coord_extra_attributes__string(self): expected = [ "name / (1) (scalar cube)", " Scalar coordinates:", - " co : 1", - " co : 2", + " co 1", + " co 2", " note='string content'", ] self.assertEqual(rep, expected) @@ -213,8 +268,8 @@ def test_coord_extra_attributes__string_escaped(self): expected = [ "name / (1) (scalar cube)", " Scalar coordinates:", - " co : 1", - " co : 2", + " co 1", + " co 2", " note='line 1\\nline 2\\tends.'", ] self.assertEqual(rep, expected) @@ -238,11 +293,11 @@ def test_coord_extra_attributes__string_overlong(self): " Scalar coordinates:", ( " co " - " : 1" + " 1" ), ( " co " - " : 2" + " 2" ), ( " note='this is very very very very " @@ -317,8 +372,8 @@ def test_section_scalar_coords(self): expected = [ "name / (1) (-- : 1)", " Scalar coordinates:", - " bounded : 0, bound=(0, 7)", - " unbounded : 0.0", + " bounded 0, bound=(0, 7)", + " unbounded 0.0", ] self.assertEqual(rep, expected) @@ -340,9 +395,9 @@ def test_section_scalar_coords__string(self): expected = [ "name / (1) (-- : 1)", " Scalar coordinates:", - " text : string-value", + " text string-value", ( - " very_long_string : A string value which is " + " very_long_string A string value which is " "very very very very very very very very very very..." ), ] @@ -384,10 +439,10 @@ def test_section_cube_attributes(self): expected = [ "name / (1) (-- : 1)", " Attributes:", - " list : [3]", - " number : 1.2", - " string : four five in a string", - " z_tupular : (6, (7, 8))", + " list [3]", + " number 1.2", + " string four five in a string", + " z_tupular (6, (7, 8))", ] self.assertEqual(rep, expected) @@ -406,13 +461,13 @@ def test_section_cube_attributes__string_extras(self): expected = [ "name / (1) (-- : 1)", " Attributes:", - " escaped : 'escaped\\tstring'", + " escaped 'escaped\\tstring'", ( - " long : this is very very very " + " long this is very very very " "very very very very very very very very very very..." ), ( - " long_multi : 'multi\\nline, " + " long_multi 'multi\\nline, " "this is very very very very very very very very very very..." ), ] @@ -429,9 +484,9 @@ def test_section_cube_attributes__array(self): expected = [ "name / (1) (-- : 1)", " Attributes:", - " array : array([1.2, 3.4])", + " array array([1.2, 3.4])", ( - " bigarray : array([[ 0, 1], [ 2, 3], " + " bigarray array([[ 0, 1], [ 2, 3], " "[ 4, 5], [ 6, 7], [ 8, 9], [10, 11], [12, 13],..." ), ] From bfb797010422a7880bd2cb88c5fcceac505addd2 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 23 Jun 2021 21:01:00 +0100 Subject: [PATCH 06/23] Test result changes. --- lib/iris/tests/integration/test_netcdf.py | 10 +++--- .../0d_str.txt | 24 ++++++------- .../1d_str.txt | 30 ++++++++-------- .../2d_str.txt | 30 ++++++++-------- .../3d_str.txt | 30 ++++++++-------- .../4d_str.txt | 30 ++++++++-------- .../results/cdm/str_repr/0d_cube.__str__.txt | 20 +++++------ .../cdm/str_repr/0d_cube.__unicode__.txt | 20 +++++------ .../cdm/str_repr/cell_methods.__str__.txt | 34 +++++++++--------- .../cdm/str_repr/missing_coords_cube.str.txt | 26 +++++++------- .../cdm/str_repr/multi_dim_coord.__str__.txt | 16 ++++----- .../results/cdm/str_repr/similar.__str__.txt | 36 +++++++++---------- .../results/cdm/str_repr/simple.__str__.txt | 12 +++---- .../unicode_attribute.__str__.ascii.txt | 5 --- .../unicode_attribute.__str__.utf8.txt | 5 --- .../unicode_attribute.__unicode__.txt | 8 ++--- .../tests/results/derived/no_orog.__str__.txt | 30 ++++++++-------- .../results/derived/removed_orog.__str__.txt | 28 +++++++-------- .../results/derived/removed_sigma.__str__.txt | 28 +++++++-------- 19 files changed, 206 insertions(+), 216 deletions(-) delete mode 100644 lib/iris/tests/results/cdm/str_repr/unicode_attribute.__str__.ascii.txt delete mode 100644 lib/iris/tests/results/cdm/str_repr/unicode_attribute.__str__.utf8.txt diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py index 3ff5bbb19d..e5bb80d54c 100644 --- a/lib/iris/tests/integration/test_netcdf.py +++ b/lib/iris/tests/integration/test_netcdf.py @@ -300,13 +300,13 @@ def test_round_trip(self): def test_print(self): cube = iris.load_cube(self.fname) printed = cube.__str__() - self.assertTrue( + self.assertIn( ( - "\n Cell measures:\n cell_area" - " - - " + "Cell measures:\n" + " cell_area - - " " x x" - ) - in printed + ), + printed ) diff --git a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/0d_str.txt b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/0d_str.txt index e79f060898..a6738e654f 100644 --- a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/0d_str.txt +++ b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/0d_str.txt @@ -1,13 +1,13 @@ air_potential_temperature / (K) (scalar cube) - Scalar coordinates: - altitude: 418.69836 m, bound=(413.93686, 426.63434) m - forecast_period: 0.0 hours - grid_latitude: -0.1278 degrees, bound=(-0.12825, -0.12735) degrees - grid_longitude: 359.5796 degrees, bound=(359.57916, 359.58005) degrees - level_height: 5.0 m, bound=(0.0, 13.333332) m - model_level_number: 1 - sigma: 0.9994238, bound=(1.0, 0.99846387) - surface_altitude: 413.93686 m - time: 2009-09-09 17:10:00 - Attributes: - source: Iris test case \ No newline at end of file + Scalar coordinates: + altitude 418.69836 m, bound=(413.93686, 426.63434) m + forecast_period 0.0 hours + grid_latitude -0.1278 degrees, bound=(-0.12825, -0.12735) degrees + grid_longitude 359.5796 degrees, bound=(359.57916, 359.58005) degrees + level_height 5.0 m, bound=(0.0, 13.333332) m + model_level_number 1 + sigma 0.9994238, bound=(1.0, 0.99846387) + surface_altitude 413.93686 m + time 2009-09-09 17:10:00 + Attributes: + source Iris test case \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/1d_str.txt b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/1d_str.txt index 204df1af1c..95f7e7b57e 100644 --- a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/1d_str.txt +++ b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/1d_str.txt @@ -1,16 +1,16 @@ air_potential_temperature / (K) (grid_longitude: 100) - Dimension coordinates: - grid_longitude x - Auxiliary coordinates: - surface_altitude x - Derived coordinates: - altitude x - Scalar coordinates: - forecast_period: 0.0 hours - grid_latitude: -0.1278 degrees, bound=(-0.12825, -0.12735) degrees - level_height: 5.0 m, bound=(0.0, 13.333332) m - model_level_number: 1 - sigma: 0.9994238, bound=(1.0, 0.99846387) - time: 2009-09-09 17:10:00 - Attributes: - source: Iris test case \ No newline at end of file + Dimension coordinates: + grid_longitude x + Auxiliary coordinates: + surface_altitude x + Derived coordinates: + altitude x + Scalar coordinates: + forecast_period 0.0 hours + grid_latitude -0.1278 degrees, bound=(-0.12825, -0.12735) degrees + level_height 5.0 m, bound=(0.0, 13.333332) m + model_level_number 1 + sigma 0.9994238, bound=(1.0, 0.99846387) + time 2009-09-09 17:10:00 + Attributes: + source Iris test case \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/2d_str.txt b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/2d_str.txt index a440a28e04..c4184d199a 100644 --- a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/2d_str.txt +++ b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/2d_str.txt @@ -1,16 +1,16 @@ air_potential_temperature / (K) (grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - grid_latitude x - - grid_longitude - x - Auxiliary coordinates: - surface_altitude x x - Derived coordinates: - altitude x x - Scalar coordinates: - forecast_period: 0.0 hours - level_height: 5.0 m, bound=(0.0, 13.333332) m - model_level_number: 1 - sigma: 0.9994238, bound=(1.0, 0.99846387) - time: 2009-09-09 17:10:00 - Attributes: - source: Iris test case \ No newline at end of file + Dimension coordinates: + grid_latitude x - + grid_longitude - x + Auxiliary coordinates: + surface_altitude x x + Derived coordinates: + altitude x x + Scalar coordinates: + forecast_period 0.0 hours + level_height 5.0 m, bound=(0.0, 13.333332) m + model_level_number 1 + sigma 0.9994238, bound=(1.0, 0.99846387) + time 2009-09-09 17:10:00 + Attributes: + source Iris test case \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/3d_str.txt b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/3d_str.txt index e171d0c513..af81d4e991 100644 --- a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/3d_str.txt +++ b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/3d_str.txt @@ -1,16 +1,16 @@ air_potential_temperature / (K) (model_level_number: 70; grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - model_level_number x - - - grid_latitude - x - - grid_longitude - - x - Auxiliary coordinates: - level_height x - - - sigma x - - - surface_altitude - x x - Derived coordinates: - altitude x x x - Scalar coordinates: - forecast_period: 0.0 hours - time: 2009-09-09 17:10:00 - Attributes: - source: Iris test case \ No newline at end of file + Dimension coordinates: + model_level_number x - - + grid_latitude - x - + grid_longitude - - x + Auxiliary coordinates: + level_height x - - + sigma x - - + surface_altitude - x x + Derived coordinates: + altitude x x x + Scalar coordinates: + forecast_period 0.0 hours + time 2009-09-09 17:10:00 + Attributes: + source Iris test case \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/4d_str.txt b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/4d_str.txt index 053e9473f0..afcdedf100 100644 --- a/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/4d_str.txt +++ b/lib/iris/tests/results/cdm/TestStockCubeStringRepresentations/4d_str.txt @@ -1,16 +1,16 @@ air_potential_temperature / (K) (time: 6; model_level_number: 70; grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - time x - - - - model_level_number - x - - - grid_latitude - - x - - grid_longitude - - - x - Auxiliary coordinates: - level_height - x - - - sigma - x - - - surface_altitude - - x x - Derived coordinates: - altitude - x x x - Scalar coordinates: - forecast_period: 0.0 hours - Attributes: - source: Iris test case \ No newline at end of file + Dimension coordinates: + time x - - - + model_level_number - x - - + grid_latitude - - x - + grid_longitude - - - x + Auxiliary coordinates: + level_height - x - - + sigma - x - - + surface_altitude - - x x + Derived coordinates: + altitude - x x x + Scalar coordinates: + forecast_period 0.0 hours + Attributes: + source Iris test case \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/0d_cube.__str__.txt b/lib/iris/tests/results/cdm/str_repr/0d_cube.__str__.txt index afe329f38d..6a3276d861 100644 --- a/lib/iris/tests/results/cdm/str_repr/0d_cube.__str__.txt +++ b/lib/iris/tests/results/cdm/str_repr/0d_cube.__str__.txt @@ -1,11 +1,11 @@ air_temperature / (K) (scalar cube) - Scalar coordinates: - forecast_period: 6477.0 hours - forecast_reference_time: 1998-03-06 03:00:00 - latitude: 89.999985 degrees - longitude: 0.0 degrees - pressure: 1000.0 hPa - time: 1998-12-01 00:00:00 - Attributes: - STASH: m01s16i203 - source: Data from Met Office Unified Model \ No newline at end of file + Scalar coordinates: + forecast_period 6477.0 hours + forecast_reference_time 1998-03-06 03:00:00 + latitude 89.999985 degrees + longitude 0.0 degrees + pressure 1000.0 hPa + time 1998-12-01 00:00:00 + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/0d_cube.__unicode__.txt b/lib/iris/tests/results/cdm/str_repr/0d_cube.__unicode__.txt index afe329f38d..6a3276d861 100644 --- a/lib/iris/tests/results/cdm/str_repr/0d_cube.__unicode__.txt +++ b/lib/iris/tests/results/cdm/str_repr/0d_cube.__unicode__.txt @@ -1,11 +1,11 @@ air_temperature / (K) (scalar cube) - Scalar coordinates: - forecast_period: 6477.0 hours - forecast_reference_time: 1998-03-06 03:00:00 - latitude: 89.999985 degrees - longitude: 0.0 degrees - pressure: 1000.0 hPa - time: 1998-12-01 00:00:00 - Attributes: - STASH: m01s16i203 - source: Data from Met Office Unified Model \ No newline at end of file + Scalar coordinates: + forecast_period 6477.0 hours + forecast_reference_time 1998-03-06 03:00:00 + latitude 89.999985 degrees + longitude 0.0 degrees + pressure 1000.0 hPa + time 1998-12-01 00:00:00 + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt b/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt index d932b03a35..c01c801a21 100644 --- a/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt +++ b/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt @@ -1,17 +1,17 @@ -air_temperature / (K) (latitude: 73; longitude: 96) - Dimension coordinates: - latitude x - - longitude - x - Scalar coordinates: - forecast_period: 6477.0 hours - forecast_reference_time: 1998-03-06 03:00:00 - pressure: 1000.0 hPa - time: 1998-12-01 00:00:00 - Attributes: - STASH: m01s16i203 - source: Data from Met Office Unified Model - Cell methods: - mean: longitude (6 minutes, This is a test comment), latitude (12 minutes) - average: longitude (6 minutes, This is another test comment), latitude (15 minutes, This is another comment) - average: longitude, latitude - percentile: longitude (6 minutes, This is another test comment) \ No newline at end of file +air_temperature / (K) (latitude: 73; longitude: 96) + Dimension coordinates: + latitude x - + longitude - x + Scalar coordinates: + forecast_period 6477.0 hours + forecast_reference_time 1998-03-06 03:00:00 + pressure 1000.0 hPa + time 1998-12-01 00:00:00 + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model + Cell methods: + mean: longitude (6 minutes, This is a test comment), latitude (12 minutes) + average: longitude (6 minutes, This is another test comment), latitude (15 minutes, This is another comment) + average: longitude, latitude + percentile: longitude (6 minutes, This is another test comment) \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/missing_coords_cube.str.txt b/lib/iris/tests/results/cdm/str_repr/missing_coords_cube.str.txt index 84af287efa..1b86bd6597 100644 --- a/lib/iris/tests/results/cdm/str_repr/missing_coords_cube.str.txt +++ b/lib/iris/tests/results/cdm/str_repr/missing_coords_cube.str.txt @@ -1,14 +1,14 @@ air_potential_temperature / (K) (-- : 6; -- : 70; grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - grid_latitude - - x - - grid_longitude - - - x - Auxiliary coordinates: - level_height - x - - - sigma - x - - - surface_altitude - - x x - Derived coordinates: - altitude - x x x - Scalar coordinates: - forecast_period: 0.0 hours - Attributes: - source: Iris test case \ No newline at end of file + Dimension coordinates: + grid_latitude - - x - + grid_longitude - - - x + Auxiliary coordinates: + level_height - x - - + sigma - x - - + surface_altitude - - x x + Derived coordinates: + altitude - x x x + Scalar coordinates: + forecast_period 0.0 hours + Attributes: + source Iris test case \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/multi_dim_coord.__str__.txt b/lib/iris/tests/results/cdm/str_repr/multi_dim_coord.__str__.txt index 17a75f7b8b..8fd35aa78d 100644 --- a/lib/iris/tests/results/cdm/str_repr/multi_dim_coord.__str__.txt +++ b/lib/iris/tests/results/cdm/str_repr/multi_dim_coord.__str__.txt @@ -1,9 +1,9 @@ test 2d dimensional cube / (meters) (dim1: 5; dim2: 10) - Dimension coordinates: - dim1 x - - dim2 - x - Auxiliary coordinates: - my_multi_dim_coord x x - Scalar coordinates: - air_temperature: 23.3 K - an_other: 3.0 meters \ No newline at end of file + Dimension coordinates: + dim1 x - + dim2 - x + Auxiliary coordinates: + my_multi_dim_coord x x + Scalar coordinates: + air_temperature 23.3 K + an_other 3.0 meters \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/similar.__str__.txt b/lib/iris/tests/results/cdm/str_repr/similar.__str__.txt index 45be83a424..fc274ed4c1 100644 --- a/lib/iris/tests/results/cdm/str_repr/similar.__str__.txt +++ b/lib/iris/tests/results/cdm/str_repr/similar.__str__.txt @@ -1,18 +1,18 @@ -air_temperature / (K) (latitude: 73; longitude: 96) - Dimension coordinates: - latitude x - - longitude - x - sensor_id=808, status=2 - Auxiliary coordinates: - latitude x - - test='True' - longitude - x - ref='A8T-22', sensor_id=810 - Scalar coordinates: - forecast_period: 6477.0 hours - forecast_reference_time: 1998-03-06 03:00:00 - pressure: 1000.0 hPa - time: 1998-12-01 00:00:00 - Attributes: - STASH: m01s16i203 - source: Data from Met Office Unified Model \ No newline at end of file +air_temperature / (K) (latitude: 73; longitude: 96) + Dimension coordinates: + latitude x - + longitude - x + sensor_id=808, status=2 + Auxiliary coordinates: + latitude x - + test='True' + longitude - x + ref='A8T-22', sensor_id=810 + Scalar coordinates: + forecast_period 6477.0 hours + forecast_reference_time 1998-03-06 03:00:00 + pressure 1000.0 hPa + time 1998-12-01 00:00:00 + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/simple.__str__.txt b/lib/iris/tests/results/cdm/str_repr/simple.__str__.txt index 59e3aba236..f1a6d5bae2 100644 --- a/lib/iris/tests/results/cdm/str_repr/simple.__str__.txt +++ b/lib/iris/tests/results/cdm/str_repr/simple.__str__.txt @@ -1,6 +1,6 @@ -thingness / (1) (foo: 11) - Dimension coordinates: - foo x - Auxiliary coordinates: - This is a really, really, really, really long long_name that must be clipped... x - This is a short long_name x \ No newline at end of file +thingness / (1) (foo: 11) + Dimension coordinates: + foo x + Auxiliary coordinates: + This is a really, really, really, really long long_name that must be clipped... x + This is a short long_name x \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__str__.ascii.txt b/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__str__.ascii.txt deleted file mode 100644 index e72c28732d..0000000000 --- a/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__str__.ascii.txt +++ /dev/null @@ -1,5 +0,0 @@ -thingness / (1) (foo: 11) - Dimension coordinates: - foo x - Attributes: - source: ?abcd? \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__str__.utf8.txt b/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__str__.utf8.txt deleted file mode 100644 index 12b5a21ef9..0000000000 --- a/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__str__.utf8.txt +++ /dev/null @@ -1,5 +0,0 @@ -thingness / (1) (foo: 11) - Dimension coordinates: - foo x - Attributes: - source: ꀀabcd޴ \ No newline at end of file diff --git a/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__unicode__.txt b/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__unicode__.txt index 12b5a21ef9..29c181345c 100644 --- a/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__unicode__.txt +++ b/lib/iris/tests/results/cdm/str_repr/unicode_attribute.__unicode__.txt @@ -1,5 +1,5 @@ thingness / (1) (foo: 11) - Dimension coordinates: - foo x - Attributes: - source: ꀀabcd޴ \ No newline at end of file + Dimension coordinates: + foo x + Attributes: + source ꀀabcd޴ \ No newline at end of file diff --git a/lib/iris/tests/results/derived/no_orog.__str__.txt b/lib/iris/tests/results/derived/no_orog.__str__.txt index 1433586a11..e277b5d276 100644 --- a/lib/iris/tests/results/derived/no_orog.__str__.txt +++ b/lib/iris/tests/results/derived/no_orog.__str__.txt @@ -1,16 +1,16 @@ air_potential_temperature / (K) (time: 6; model_level_number: 70; grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - time x - - - - model_level_number - x - - - grid_latitude - - x - - grid_longitude - - - x - Auxiliary coordinates: - level_height - x - - - sigma - x - - - surface_altitude - - x x - Derived coordinates: - altitude - x - - - Scalar coordinates: - forecast_period: 0.0 hours - Attributes: - source: Iris test case \ No newline at end of file + Dimension coordinates: + time x - - - + model_level_number - x - - + grid_latitude - - x - + grid_longitude - - - x + Auxiliary coordinates: + level_height - x - - + sigma - x - - + surface_altitude - - x x + Derived coordinates: + altitude - x - - + Scalar coordinates: + forecast_period 0.0 hours + Attributes: + source Iris test case \ No newline at end of file diff --git a/lib/iris/tests/results/derived/removed_orog.__str__.txt b/lib/iris/tests/results/derived/removed_orog.__str__.txt index 22ec52213d..0c24cded80 100644 --- a/lib/iris/tests/results/derived/removed_orog.__str__.txt +++ b/lib/iris/tests/results/derived/removed_orog.__str__.txt @@ -1,15 +1,15 @@ air_potential_temperature / (K) (time: 6; model_level_number: 70; grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - time x - - - - model_level_number - x - - - grid_latitude - - x - - grid_longitude - - - x - Auxiliary coordinates: - level_height - x - - - sigma - x - - - Derived coordinates: - altitude - x - - - Scalar coordinates: - forecast_period: 0.0 hours - Attributes: - source: Iris test case \ No newline at end of file + Dimension coordinates: + time x - - - + model_level_number - x - - + grid_latitude - - x - + grid_longitude - - - x + Auxiliary coordinates: + level_height - x - - + sigma - x - - + Derived coordinates: + altitude - x - - + Scalar coordinates: + forecast_period 0.0 hours + Attributes: + source Iris test case \ No newline at end of file diff --git a/lib/iris/tests/results/derived/removed_sigma.__str__.txt b/lib/iris/tests/results/derived/removed_sigma.__str__.txt index ffba49bf8d..94e850ec62 100644 --- a/lib/iris/tests/results/derived/removed_sigma.__str__.txt +++ b/lib/iris/tests/results/derived/removed_sigma.__str__.txt @@ -1,15 +1,15 @@ air_potential_temperature / (K) (time: 6; model_level_number: 70; grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - time x - - - - model_level_number - x - - - grid_latitude - - x - - grid_longitude - - - x - Auxiliary coordinates: - level_height - x - - - surface_altitude - - x x - Derived coordinates: - altitude - x x x - Scalar coordinates: - forecast_period: 0.0 hours - Attributes: - source: Iris test case \ No newline at end of file + Dimension coordinates: + time x - - - + model_level_number - x - - + grid_latitude - - x - + grid_longitude - - - x + Auxiliary coordinates: + level_height - x - - + surface_altitude - - x x + Derived coordinates: + altitude - x x x + Scalar coordinates: + forecast_period 0.0 hours + Attributes: + source Iris test case \ No newline at end of file From 07151fdf7b19dd75751f81d98c3a7afd11eb6ec7 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 23 Jun 2021 21:10:53 +0100 Subject: [PATCH 07/23] Code standard fixes. --- lib/iris/tests/integration/test_netcdf.py | 2 +- .../cube_printout/test_CubePrintout.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py index e5bb80d54c..3d4e972c57 100644 --- a/lib/iris/tests/integration/test_netcdf.py +++ b/lib/iris/tests/integration/test_netcdf.py @@ -306,7 +306,7 @@ def test_print(self): " cell_area - - " " x x" ), - printed + printed, ) diff --git a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py index 434ae7cd8e..08d66ea8ce 100644 --- a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py +++ b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py @@ -58,9 +58,9 @@ def _sample_wide_cube(self): cube.add_aux_coord( AuxCoord( [0, 1], - long_name='long long long long long long long long name' + long_name="long long long long long long long long name", ), - 0 + 0, ) return cube @@ -69,9 +69,9 @@ def test_wide_cube(self): cube = self._sample_wide_cube() rep = cube_replines(cube) expect_full = [ - 'unknown / (unknown) (-- : 2)', - ' Auxiliary coordinates:', - ' long long long long long long long long name x' + "unknown / (unknown) (-- : 2)", + " Auxiliary coordinates:", + " long long long long long long long long name x", ] self.assertEqual(expect_full, rep) @@ -91,7 +91,7 @@ def test_shortform__wide__compressed(self): def test_shortform__wide__intermediate(self): cube = self._sample_wide_cube() rep = cube_replines(cube, oneline=True, name_padding=25) - expect = ['unknown / (unknown) (-- : 2)'] + expect = ["unknown / (unknown) (-- : 2)"] self.assertEqual(expect, rep) def test_scalar_cube_summaries(self): From e1d46556430835e5bb3e32c2d6d211c61544c0c1 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 23 Jun 2021 21:43:52 +0100 Subject: [PATCH 08/23] More test fixes. --- lib/iris/tests/unit/cube/test_Cube.py | 6 +++--- .../representation/test_CubeRepresentation.py | 8 -------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index e6e973c58a..a9becb6dc4 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -579,10 +579,10 @@ def test_ancillary_variable(self): cube.add_ancillary_variable(av, 0) expected_summary = ( "unknown / (unknown) (-- : 2; -- : 3)\n" - " Ancillary variables:\n" - " status_flag x -" + " Ancillary variables:\n" + " status_flag x -" ) - self.assertEqual(cube.summary(), expected_summary) + self.assertEqual(expected_summary, cube.summary()) def test_similar_coords(self): coord1 = AuxCoord( diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 008acf954b..80f0118fab 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -354,14 +354,6 @@ def test_not_included(self): for heading in not_included: self.assertNotIn(heading, self.result) - def test_handle_newline(self): - cube = self.cube - cube.attributes["lines"] = "first\nsecond" - representer = CubeRepresentation(cube) - representer._get_bits(representer._get_lines()) - result = representer._make_content() - self.assertIn("first
second", result) - @tests.skip_data class Test_repr_html(tests.IrisTest): From df784a86e78552e327d24ce24be0cbcb8ddefed2 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 24 Jun 2021 00:26:36 +0100 Subject: [PATCH 09/23] Doctest fixes. --- docs/src/further_topics/lenient_maths.rst | 126 +++++----- docs/src/further_topics/metadata.rst | 36 +-- docs/src/userguide/cube_maths.rst | 20 +- docs/src/userguide/cube_statistics.rst | 148 ++++++------ .../interpolation_and_regridding.rst | 54 ++--- docs/src/userguide/iris_cubes.rst | 36 +-- docs/src/userguide/loading_iris_cubes.rst | 36 +-- docs/src/userguide/merge_and_concat.rst | 36 +-- docs/src/userguide/navigating_a_cube.rst | 46 ++-- docs/src/userguide/subsetting_a_cube.rst | 56 ++--- lib/iris/common/resolve.py | 216 +++++++++--------- lib/iris/cube.py | 186 +++++++-------- 12 files changed, 501 insertions(+), 495 deletions(-) diff --git a/docs/src/further_topics/lenient_maths.rst b/docs/src/further_topics/lenient_maths.rst index 4aad721780..643bd37e76 100644 --- a/docs/src/further_topics/lenient_maths.rst +++ b/docs/src/further_topics/lenient_maths.rst @@ -68,26 +68,26 @@ represents the output of an low-resolution global atmospheric ``experiment``, .. doctest:: lenient-example >>> print(experiment) - air_potential_temperature / (K) (model_level_number: 15; grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - model_level_number x - - - grid_latitude - x - - grid_longitude - - x - Auxiliary coordinates: - atmosphere_hybrid_height_coordinate x - - - sigma x - - - surface_altitude - x x - Derived coordinates: - altitude x x x - Scalar coordinates: - forecast_period: 0.0 hours - forecast_reference_time: 2009-09-09 17:10:00 - time: 2009-09-09 17:10:00 - Attributes: - Conventions: CF-1.5 - STASH: m01s00i004 - experiment-id: RT3 50 - source: Data from Met Office Unified Model 7.04 + air_potential_temperature / (K) (model_level_number: 15; grid_latitude: 100; grid_longitude: 100) + Dimension coordinates: + model_level_number x - - + grid_latitude - x - + grid_longitude - - x + Auxiliary coordinates: + atmosphere_hybrid_height_coordinate x - - + sigma x - - + surface_altitude - x x + Derived coordinates: + altitude x x x + Scalar coordinates: + forecast_period 0.0 hours + forecast_reference_time 2009-09-09 17:10:00 + time 2009-09-09 17:10:00 + Attributes: + Conventions CF-1.5 + STASH m01s00i004 + experiment-id RT3 50 + source Data from Met Office Unified Model 7.04 Consider also the following :class:`~iris.cube.Cube`, which has the same global spatial extent, and acts as a ``control``, @@ -96,16 +96,16 @@ spatial extent, and acts as a ``control``, >>> print(control) air_potential_temperature / (K) (grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - grid_latitude x - - grid_longitude - x - Scalar coordinates: - model_level_number: 1 - time: 2009-09-09 17:10:00 - Attributes: - Conventions: CF-1.7 - STASH: m01s00i004 - source: Data from Met Office Unified Model 7.04 + Dimension coordinates: + grid_latitude x - + grid_longitude - x + Scalar coordinates: + model_level_number 1 + time 2009-09-09 17:10:00 + Attributes: + Conventions CF-1.7 + STASH m01s00i004 + source Data from Met Office Unified Model 7.04 Now let's subtract these cubes in order to calculate a simple ``difference``, @@ -113,24 +113,24 @@ Now let's subtract these cubes in order to calculate a simple ``difference``, >>> difference = experiment - control >>> print(difference) - unknown / (K) (model_level_number: 15; grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - model_level_number x - - - grid_latitude - x - - grid_longitude - - x - Auxiliary coordinates: - atmosphere_hybrid_height_coordinate x - - - sigma x - - - surface_altitude - x x - Derived coordinates: - altitude x x x - Scalar coordinates: - forecast_period: 0.0 hours - forecast_reference_time: 2009-09-09 17:10:00 - time: 2009-09-09 17:10:00 - Attributes: - experiment-id: RT3 50 - source: Data from Met Office Unified Model 7.04 + unknown / (K) (model_level_number: 15; grid_latitude: 100; grid_longitude: 100) + Dimension coordinates: + model_level_number x - - + grid_latitude - x - + grid_longitude - - x + Auxiliary coordinates: + atmosphere_hybrid_height_coordinate x - - + sigma x - - + surface_altitude - x x + Derived coordinates: + altitude x x x + Scalar coordinates: + forecast_period 0.0 hours + forecast_reference_time 2009-09-09 17:10:00 + time 2009-09-09 17:10:00 + Attributes: + experiment-id RT3 50 + source Data from Met Office Unified Model 7.04 Note that, cube maths automatically takes care of broadcasting the dimensionality of the ``control`` up to that of the ``experiment``, in order to @@ -204,21 +204,21 @@ time perform **strict** cube maths instead, ... difference = experiment - control ... >>> print(difference) - unknown / (K) (model_level_number: 15; grid_latitude: 100; grid_longitude: 100) - Dimension coordinates: - model_level_number x - - - grid_latitude - x - - grid_longitude - - x - Auxiliary coordinates: - atmosphere_hybrid_height_coordinate x - - - sigma x - - - surface_altitude - x x - Derived coordinates: - altitude x x x - Scalar coordinates: - time: 2009-09-09 17:10:00 - Attributes: - source: Data from Met Office Unified Model 7.04 + unknown / (K) (model_level_number: 15; grid_latitude: 100; grid_longitude: 100) + Dimension coordinates: + model_level_number x - - + grid_latitude - x - + grid_longitude - - x + Auxiliary coordinates: + atmosphere_hybrid_height_coordinate x - - + sigma x - - + surface_altitude - x x + Derived coordinates: + altitude x x x + Scalar coordinates: + time 2009-09-09 17:10:00 + Attributes: + source Data from Met Office Unified Model 7.04 Although the numerical result of this strict cube maths operation is identical, it is not as rich in metadata as the :ref:`lenient alternative `. diff --git a/docs/src/further_topics/metadata.rst b/docs/src/further_topics/metadata.rst index ab6a6450b4..1870c6a04a 100644 --- a/docs/src/further_topics/metadata.rst +++ b/docs/src/further_topics/metadata.rst @@ -108,22 +108,22 @@ For example, given the following :class:`~iris.cube.Cube`, >>> print(cube) air_temperature / (K) (time: 240; latitude: 37; longitude: 49) - Dimension coordinates: - time x - - - latitude - x - - longitude - - x - Auxiliary coordinates: - forecast_period x - - - Scalar coordinates: - forecast_reference_time: 1859-09-01 06:00:00 - height: 1.5 m - Attributes: - Conventions: CF-1.5 - Model scenario: A1B - STASH: m01s03i236 - source: Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) + Dimension coordinates: + time x - - + latitude - x - + longitude - - x + Auxiliary coordinates: + forecast_period x - - + Scalar coordinates: + forecast_reference_time 1859-09-01 06:00:00 + height 1.5 m + Attributes: + Conventions CF-1.5 + Model scenario A1B + STASH m01s03i236 + source Data from Met Office Unified Model 6.05 + Cell methods: + mean: time (6 hour) We can easily get all of the associated metadata of the :class:`~iris.cube.Cube` using the ``metadata`` property: @@ -263,7 +263,7 @@ using the `namedtuple._asdict`_ method. This can be particularly handy when a standard Python built-in container is required to represent your ``metadata``, >>> metadata._asdict() - {'standard_name': 'longitude', 'long_name': None, 'var_name': 'longitude', 'units': Unit('degrees'), 'attributes': {'grinning face': '🙃'}, 'coord_system': GeogCS(6371229.0), 'climatological': False, 'circular': False} + OrderedDict([('standard_name', 'longitude'), ('long_name', None), ('var_name', 'longitude'), ('units', Unit('degrees')), ('attributes', {'grinning face': '🙃'}), ('coord_system', GeogCS(6371229.0)), ('climatological', False), ('circular', False)]) Using the `namedtuple._replace`_ method allows you to create a new metadata class instance, but replacing specified members with **new** associated values, @@ -943,7 +943,7 @@ such as a `dict`_, >>> mapping = latitude.metadata._asdict() >>> mapping - {'standard_name': 'latitude', 'long_name': None, 'var_name': 'latitude', 'units': Unit('degrees'), 'attributes': {}, 'coord_system': GeogCS(6371229.0), 'climatological': False, 'circular': False} + OrderedDict([('standard_name', 'latitude'), ('long_name', None), ('var_name', 'latitude'), ('units', Unit('degrees')), ('attributes', {}), ('coord_system', GeogCS(6371229.0)), ('climatological', False), ('circular', False)]) >>> longitude.metadata = mapping >>> longitude.metadata DimCoordMetadata(standard_name='latitude', long_name=None, var_name='latitude', units=Unit('degrees'), attributes={}, coord_system=GeogCS(6371229.0), climatological=False, circular=False) diff --git a/docs/src/userguide/cube_maths.rst b/docs/src/userguide/cube_maths.rst index 35e672eaf6..78490cd749 100644 --- a/docs/src/userguide/cube_maths.rst +++ b/docs/src/userguide/cube_maths.rst @@ -56,16 +56,16 @@ but with the data representing their difference: >>> print(t_last - t_first) unknown / (K) (latitude: 37; longitude: 49) - Dimension coordinates: - latitude x - - longitude - x - Scalar coordinates: - forecast_reference_time: 1859-09-01 06:00:00 - height: 1.5 m - Attributes: - Conventions: CF-1.5 - Model scenario: E1 - source: Data from Met Office Unified Model 6.05 + Dimension coordinates: + latitude x - + longitude - x + Scalar coordinates: + forecast_reference_time 1859-09-01 06:00:00 + height 1.5 m + Attributes: + Conventions CF-1.5 + Model scenario E1 + source Data from Met Office Unified Model 6.05 .. note:: diff --git a/docs/src/userguide/cube_statistics.rst b/docs/src/userguide/cube_statistics.rst index 4eb016078e..b7b1fef171 100644 --- a/docs/src/userguide/cube_statistics.rst +++ b/docs/src/userguide/cube_statistics.rst @@ -37,24 +37,24 @@ For instance, suppose we have a cube: >>> cube = iris.load_cube(filename, 'air_potential_temperature') >>> print(cube) air_potential_temperature / (K) (time: 3; model_level_number: 7; grid_latitude: 204; grid_longitude: 187) - Dimension coordinates: - time x - - - - model_level_number - x - - - grid_latitude - - x - - grid_longitude - - - x - Auxiliary coordinates: - forecast_period x - - - - level_height - x - - - sigma - x - - - surface_altitude - - x x - Derived coordinates: - altitude - x x x - Scalar coordinates: - forecast_reference_time: 2009-11-19 04:00:00 - Attributes: - STASH: m01s00i004 - source: Data from Met Office Unified Model - um_version: 7.3 + Dimension coordinates: + time x - - - + model_level_number - x - - + grid_latitude - - x - + grid_longitude - - - x + Auxiliary coordinates: + forecast_period x - - - + level_height - x - - + sigma - x - - + surface_altitude - - x x + Derived coordinates: + altitude - x x x + Scalar coordinates: + forecast_reference_time 2009-11-19 04:00:00 + Attributes: + STASH m01s00i004 + source Data from Met Office Unified Model + um_version 7.3 In this case we have a 4 dimensional cube; @@ -66,26 +66,26 @@ we can pass the coordinate name and the aggregation definition to the >>> vertical_mean = cube.collapsed('model_level_number', iris.analysis.MEAN) >>> print(vertical_mean) air_potential_temperature / (K) (time: 3; grid_latitude: 204; grid_longitude: 187) - Dimension coordinates: - time x - - - grid_latitude - x - - grid_longitude - - x - Auxiliary coordinates: - forecast_period x - - - surface_altitude - x x - Derived coordinates: - altitude - x x - Scalar coordinates: - forecast_reference_time: 2009-11-19 04:00:00 - level_height: 696.6666 m, bound=(0.0, 1393.3333) m - model_level_number: 10, bound=(1, 19) - sigma: 0.92292976, bound=(0.8458596, 1.0) - Attributes: - STASH: m01s00i004 - source: Data from Met Office Unified Model - um_version: 7.3 - Cell methods: - mean: model_level_number + Dimension coordinates: + time x - - + grid_latitude - x - + grid_longitude - - x + Auxiliary coordinates: + forecast_period x - - + surface_altitude - x x + Derived coordinates: + altitude - x x + Scalar coordinates: + forecast_reference_time 2009-11-19 04:00:00 + level_height 696.6666 m, bound=(0.0, 1393.3333) m + model_level_number 10, bound=(1, 19) + sigma 0.92292976, bound=(0.8458596, 1.0) + Attributes: + STASH m01s00i004 + source Data from Met Office Unified Model + um_version 7.3 + Cell methods: + mean: model_level_number Similarly other analysis operators such as ``MAX``, ``MIN`` and ``STD_DEV`` @@ -124,27 +124,27 @@ These areas can now be passed to the ``collapsed`` method as weights: >>> new_cube = cube.collapsed(['grid_longitude', 'grid_latitude'], iris.analysis.MEAN, weights=grid_areas) >>> print(new_cube) - air_potential_temperature / (K) (time: 3; model_level_number: 7) - Dimension coordinates: - time x - - model_level_number - x - Auxiliary coordinates: - forecast_period x - - level_height - x - sigma - x - Derived coordinates: - altitude - x - Scalar coordinates: - forecast_reference_time: 2009-11-19 04:00:00 - grid_latitude: 1.5145501 degrees, bound=(0.14430022, 2.8848) degrees - grid_longitude: 358.74948 degrees, bound=(357.494, 360.00497) degrees - surface_altitude: 399.625 m, bound=(-14.0, 813.25) m - Attributes: - STASH: m01s00i004 - source: Data from Met Office Unified Model - um_version: 7.3 - Cell methods: - mean: grid_longitude, grid_latitude + air_potential_temperature / (K) (time: 3; model_level_number: 7) + Dimension coordinates: + time x - + model_level_number - x + Auxiliary coordinates: + forecast_period x - + level_height - x + sigma - x + Derived coordinates: + altitude - x + Scalar coordinates: + forecast_reference_time 2009-11-19 04:00:00 + grid_latitude 1.5145501 degrees, bound=(0.14430022, 2.8848) degrees + grid_longitude 358.74948 degrees, bound=(357.494, 360.00497) degrees + surface_altitude 399.625 m, bound=(-14.0, 813.25) m + Attributes: + STASH m01s00i004 + source Data from Met Office Unified Model + um_version 7.3 + Cell methods: + mean: grid_longitude, grid_latitude Several examples of area averaging exist in the gallery which may be of interest, including an example on taking a :ref:`global area-weighted mean @@ -216,21 +216,21 @@ Printing this cube now shows that two extra coordinates exist on the cube: >>> print(cube) surface_temperature / (K) (time: 54; latitude: 18; longitude: 432) - Dimension coordinates: - time x - - - latitude - x - - longitude - - x - Auxiliary coordinates: - clim_season x - - - forecast_reference_time x - - - season_year x - - - Scalar coordinates: - forecast_period: 0 hours - Attributes: - Conventions: CF-1.5 - STASH: m01s00i024 - Cell methods: - mean: month, year + Dimension coordinates: + time x - - + latitude - x - + longitude - - x + Auxiliary coordinates: + clim_season x - - + forecast_reference_time x - - + season_year x - - + Scalar coordinates: + forecast_period 0 hours + Attributes: + Conventions CF-1.5 + STASH m01s00i024 + Cell methods: + mean: month, year These two coordinates can now be used to aggregate by season and climate-year: diff --git a/docs/src/userguide/interpolation_and_regridding.rst b/docs/src/userguide/interpolation_and_regridding.rst index 5a5a985ccb..f55ee60665 100644 --- a/docs/src/userguide/interpolation_and_regridding.rst +++ b/docs/src/userguide/interpolation_and_regridding.rst @@ -66,39 +66,39 @@ Let's take the air temperature cube we've seen previously: >>> air_temp = iris.load_cube(iris.sample_data_path('air_temp.pp')) >>> print(air_temp) air_temperature / (K) (latitude: 73; longitude: 96) - Dimension coordinates: - latitude x - - longitude - x - Scalar coordinates: - forecast_period: 6477 hours, bound=(-28083.0, 6477.0) hours - forecast_reference_time: 1998-03-01 03:00:00 - pressure: 1000.0 hPa - time: 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) - Attributes: - STASH: m01s16i203 - source: Data from Met Office Unified Model - Cell methods: - mean within years: time - mean over years: time + Dimension coordinates: + latitude x - + longitude - x + Scalar coordinates: + forecast_period 6477 hours, bound=(-28083.0, 6477.0) hours + forecast_reference_time 1998-03-01 03:00:00 + pressure 1000.0 hPa + time 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model + Cell methods: + mean within years: time + mean over years: time We can interpolate specific values from the coordinates of the cube: >>> sample_points = [('latitude', 51.48), ('longitude', 0)] >>> print(air_temp.interpolate(sample_points, iris.analysis.Linear())) air_temperature / (K) (scalar cube) - Scalar coordinates: - forecast_period: 6477 hours, bound=(-28083.0, 6477.0) hours - forecast_reference_time: 1998-03-01 03:00:00 - latitude: 51.48 degrees - longitude: 0 degrees - pressure: 1000.0 hPa - time: 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) - Attributes: - STASH: m01s16i203 - source: Data from Met Office Unified Model - Cell methods: - mean within years: time - mean over years: time + Scalar coordinates: + forecast_period 6477 hours, bound=(-28083.0, 6477.0) hours + forecast_reference_time 1998-03-01 03:00:00 + latitude 51.48 degrees + longitude 0 degrees + pressure 1000.0 hPa + time 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model + Cell methods: + mean within years: time + mean over years: time As we can see, the resulting cube is scalar and has longitude and latitude coordinates with the values defined in our sample points. diff --git a/docs/src/userguide/iris_cubes.rst b/docs/src/userguide/iris_cubes.rst index de206486d3..64a9bfd822 100644 --- a/docs/src/userguide/iris_cubes.rst +++ b/docs/src/userguide/iris_cubes.rst @@ -156,24 +156,24 @@ output as this is the quickest way of inspecting the contents of a cube. Here is .. testoutput:: air_potential_temperature / (K) (time: 3; model_level_number: 7; grid_latitude: 204; grid_longitude: 187) - Dimension coordinates: - time x - - - - model_level_number - x - - - grid_latitude - - x - - grid_longitude - - - x - Auxiliary coordinates: - forecast_period x - - - - level_height - x - - - sigma - x - - - surface_altitude - - x x - Derived coordinates: - altitude - x x x - Scalar coordinates: - forecast_reference_time: 2009-11-19 04:00:00 - Attributes: - STASH: m01s00i004 - source: Data from Met Office Unified Model - um_version: 7.3 + Dimension coordinates: + time x - - - + model_level_number - x - - + grid_latitude - - x - + grid_longitude - - - x + Auxiliary coordinates: + forecast_period x - - - + level_height - x - - + sigma - x - - + surface_altitude - - x x + Derived coordinates: + altitude - x x x + Scalar coordinates: + forecast_reference_time 2009-11-19 04:00:00 + Attributes: + STASH m01s00i004 + source Data from Met Office Unified Model + um_version 7.3 Using this output we can deduce that: diff --git a/docs/src/userguide/loading_iris_cubes.rst b/docs/src/userguide/loading_iris_cubes.rst index 659c28420a..28e2755df7 100644 --- a/docs/src/userguide/loading_iris_cubes.rst +++ b/docs/src/userguide/loading_iris_cubes.rst @@ -85,24 +85,24 @@ list indexing can be used: >>> air_potential_temperature = cubes[0] >>> print(air_potential_temperature) air_potential_temperature / (K) (time: 3; model_level_number: 7; grid_latitude: 204; grid_longitude: 187) - Dimension coordinates: - time x - - - - model_level_number - x - - - grid_latitude - - x - - grid_longitude - - - x - Auxiliary coordinates: - forecast_period x - - - - level_height - x - - - sigma - x - - - surface_altitude - - x x - Derived coordinates: - altitude - x x x - Scalar coordinates: - forecast_reference_time: 2009-11-19 04:00:00 - Attributes: - STASH: m01s00i004 - source: Data from Met Office Unified Model - um_version: 7.3 + Dimension coordinates: + time x - - - + model_level_number - x - - + grid_latitude - - x - + grid_longitude - - - x + Auxiliary coordinates: + forecast_period x - - - + level_height - x - - + sigma - x - - + surface_altitude - - x x + Derived coordinates: + altitude - x x x + Scalar coordinates: + forecast_reference_time 2009-11-19 04:00:00 + Attributes: + STASH m01s00i004 + source Data from Met Office Unified Model + um_version 7.3 Notice that the result of printing a **cube** is a little more verbose than it was when printing a **list of cubes**. In addition to the very short summary diff --git a/docs/src/userguide/merge_and_concat.rst b/docs/src/userguide/merge_and_concat.rst index f98685398b..cc619fb7fd 100644 --- a/docs/src/userguide/merge_and_concat.rst +++ b/docs/src/userguide/merge_and_concat.rst @@ -108,18 +108,20 @@ make a new ``z`` dimension coordinate: >>> print(cubes[0]) air_temperature / (kelvin) (y: 4; x: 5) ... - Scalar coordinates: - z: 1 meters + Scalar coordinates: + z 1 meters >>> print(cubes[1]) air_temperature / (kelvin) (y: 4; x: 5) ... - Scalar coordinates: - z: 2 meters + Scalar coordinates: + z 2 meters >>> print(cubes[2]) air_temperature / (kelvin) (y: 4; x: 5) - ... - Scalar coordinates: - z: 3 meters + Dimension coordinates: + y x - + x - x + Scalar coordinates: + z 3 meters >>> print(cubes.merge()) 0: air_temperature / (kelvin) (z: 3; y: 4; x: 5) @@ -553,18 +555,18 @@ combine your cubes:: >>> print(cubes[0]) air_temperature / (kelvin) (y: 4; x: 5) - Dimension coordinates: - x x - - y - x - Scalar coordinates: - z: 1 + Dimension coordinates: + y x - + x - x + Scalar coordinates: + z 1 meters >>> print(cubes[1]) air_temperature / (kelvin) (y: 4; x: 5) - Dimension coordinates: - x x - - y - x - Scalar coordinates: - z: 2 + Dimension coordinates: + y x - + x - x + Scalar coordinates: + z 2 meters If your cubes are similar to those below (the single value ``z`` coordinate is diff --git a/docs/src/userguide/navigating_a_cube.rst b/docs/src/userguide/navigating_a_cube.rst index df18c032c1..74b47b258e 100644 --- a/docs/src/userguide/navigating_a_cube.rst +++ b/docs/src/userguide/navigating_a_cube.rst @@ -25,17 +25,17 @@ We have already seen a basic string representation of a cube when printing: >>> cube = iris.load_cube(filename) >>> print(cube) air_pressure_at_sea_level / (Pa) (grid_latitude: 22; grid_longitude: 36) - Dimension coordinates: - grid_latitude x - - grid_longitude - x - Scalar coordinates: - forecast_period: 0.0 hours - forecast_reference_time: 2006-06-15 00:00:00 - time: 2006-06-15 00:00:00 - Attributes: - Conventions: CF-1.5 - STASH: m01s16i222 - source: Data from Met Office Unified Model 6.01 + Dimension coordinates: + grid_latitude x - + grid_longitude - x + Scalar coordinates: + forecast_period 0.0 hours + forecast_reference_time 2006-06-15 00:00:00 + time 2006-06-15 00:00:00 + Attributes: + Conventions CF-1.5 + STASH m01s16i222 + source Data from Met Office Unified Model 6.01 This representation is equivalent to passing the cube to the :func:`str` function. This function can be used on @@ -160,18 +160,18 @@ We can add and remove coordinates via :func:`Cube.add_dim_coord>> cube.add_aux_coord(new_coord) >>> print(cube) air_pressure_at_sea_level / (Pa) (grid_latitude: 22; grid_longitude: 36) - Dimension coordinates: - grid_latitude x - - grid_longitude - x - Scalar coordinates: - forecast_period: 0.0 hours - forecast_reference_time: 2006-06-15 00:00:00 - my_custom_coordinate: 1 - time: 2006-06-15 00:00:00 - Attributes: - Conventions: CF-1.5 - STASH: m01s16i222 - source: Data from Met Office Unified Model 6.01 + Dimension coordinates: + grid_latitude x - + grid_longitude - x + Scalar coordinates: + forecast_period 0.0 hours + forecast_reference_time 2006-06-15 00:00:00 + my_custom_coordinate 1 + time 2006-06-15 00:00:00 + Attributes: + Conventions CF-1.5 + STASH m01s16i222 + source Data from Met Office Unified Model 6.01 The coordinate ``my_custom_coordinate`` now exists on the cube and is listed under the non-dimensioned single valued scalar coordinates. diff --git a/docs/src/userguide/subsetting_a_cube.rst b/docs/src/userguide/subsetting_a_cube.rst index 02cf1645a1..1c68cafb8d 100644 --- a/docs/src/userguide/subsetting_a_cube.rst +++ b/docs/src/userguide/subsetting_a_cube.rst @@ -21,16 +21,16 @@ A subset of a cube can be "extracted" from a multi-dimensional cube in order to >>> equator_slice = cube.extract(iris.Constraint(grid_latitude=0)) >>> print(equator_slice) electron density / (1E11 e/m^3) (height: 29; grid_longitude: 31) - Dimension coordinates: - height x - - grid_longitude - x - Auxiliary coordinates: - latitude - x - longitude - x - Scalar coordinates: - grid_latitude: 0.0 degrees - Attributes: - Conventions: CF-1.5 + Dimension coordinates: + height x - + grid_longitude - x + Auxiliary coordinates: + latitude - x + longitude - x + Scalar coordinates: + grid_latitude 0.0 degrees + Attributes: + Conventions CF-1.5 In this example we start with a 3 dimensional cube, with dimensions of ``height``, ``grid_latitude`` and ``grid_longitude``, @@ -81,24 +81,24 @@ same way as loading with constraints: 0: air_potential_temperature / (K) (grid_latitude: 204; grid_longitude: 187) >>> print(cubes[0]) air_potential_temperature / (K) (grid_latitude: 204; grid_longitude: 187) - Dimension coordinates: - grid_latitude x - - grid_longitude - x - Auxiliary coordinates: - surface_altitude x x - Derived coordinates: - altitude x x - Scalar coordinates: - forecast_period: 6.0 hours - forecast_reference_time: 2009-11-19 04:00:00 - level_height: 395.0 m, bound=(360.0, 433.3332) m - model_level_number: 10 - sigma: 0.9549927, bound=(0.9589389, 0.95068014) - time: 2009-11-19 10:00:00 - Attributes: - STASH: m01s00i004 - source: Data from Met Office Unified Model - um_version: 7.3 + Dimension coordinates: + grid_latitude x - + grid_longitude - x + Auxiliary coordinates: + surface_altitude x x + Derived coordinates: + altitude x x + Scalar coordinates: + forecast_period 6.0 hours + forecast_reference_time 2009-11-19 04:00:00 + level_height 395.0 m, bound=(360.0, 433.3332) m + model_level_number 10 + sigma 0.9549927, bound=(0.9589389, 0.95068014) + time 2009-11-19 10:00:00 + Attributes: + STASH m01s00i004 + source Data from Met Office Unified Model + um_version 7.3 Cube Iteration diff --git a/lib/iris/common/resolve.py b/lib/iris/common/resolve.py index 635298e1ac..28800c2d2e 100644 --- a/lib/iris/common/resolve.py +++ b/lib/iris/common/resolve.py @@ -100,40 +100,40 @@ class Resolve: >>> print(cube1) air_temperature / (K) (time: 240; latitude: 37; longitude: 49) - Dimension coordinates: - time x - - - latitude - x - - longitude - - x - Auxiliary coordinates: - forecast_period x - - - Scalar coordinates: - forecast_reference_time: 1859-09-01 06:00:00 - height: 1.5 m - Attributes: - Conventions: CF-1.5 - Model scenario: A1B - STASH: m01s03i236 - source: Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) + Dimension coordinates: + time x - - + latitude - x - + longitude - - x + Auxiliary coordinates: + forecast_period x - - + Scalar coordinates: + forecast_reference_time 1859-09-01 06:00:00 + height 1.5 m + Attributes: + Conventions CF-1.5 + Model scenario A1B + STASH m01s03i236 + source Data from Met Office Unified Model 6.05 + Cell methods: + mean: time (6 hour) >>> print(cube2) air_temperature / (K) (longitude: 49; latitude: 37) - Dimension coordinates: - longitude x - - latitude - x - Scalar coordinates: - forecast_period: 10794 hours - forecast_reference_time: 1859-09-01 06:00:00 - height: 1.5 m - time: 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) - Attributes: - Conventions: CF-1.5 - Model scenario: E1 - STASH: m01s03i236 - source: Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) + Dimension coordinates: + longitude x - + latitude - x + Scalar coordinates: + forecast_period 10794 hours + forecast_reference_time 1859-09-01 06:00:00 + height 1.5 m + time 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) + Attributes: + Conventions CF-1.5 + Model scenario E1 + STASH m01s03i236 + source Data from Met Office Unified Model 6.05 + Cell methods: + mean: time (6 hour) >>> print(data.shape) (240, 37, 49) @@ -141,21 +141,21 @@ class Resolve: >>> result = resolver.cube(data) >>> print(result) air_temperature / (K) (time: 240; latitude: 37; longitude: 49) - Dimension coordinates: - time x - - - latitude - x - - longitude - - x - Auxiliary coordinates: - forecast_period x - - - Scalar coordinates: - forecast_reference_time: 1859-09-01 06:00:00 - height: 1.5 m - Attributes: - Conventions: CF-1.5 - STASH: m01s03i236 - source: Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) + Dimension coordinates: + time x - - + latitude - x - + longitude - - x + Auxiliary coordinates: + forecast_period x - - + Scalar coordinates: + forecast_reference_time 1859-09-01 06:00:00 + height 1.5 m + Attributes: + Conventions CF-1.5 + STASH m01s03i236 + source Data from Met Office Unified Model 6.05 + Cell methods: + mean: time (6 hour) Secondly, creating an *empty* ``resolver`` instance, that may be called *multiple* times with *different* :class:`~iris.cube.Cube` operands and *different* ``data``, @@ -2401,39 +2401,39 @@ def mapped(self): >>> print(cube1) air_temperature / (K) (time: 240; latitude: 37; longitude: 49) - Dimension coordinates: - time x - - - latitude - x - - longitude - - x - Auxiliary coordinates: - forecast_period x - - - Scalar coordinates: - forecast_reference_time: 1859-09-01 06:00:00 - height: 1.5 m - Attributes: - Conventions: CF-1.5 - Model scenario: A1B - STASH: m01s03i236 - source: Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) + Dimension coordinates: + time x - - + latitude - x - + longitude - - x + Auxiliary coordinates: + forecast_period x - - + Scalar coordinates: + forecast_reference_time 1859-09-01 06:00:00 + height 1.5 m + Attributes: + Conventions CF-1.5 + Model scenario A1B + STASH m01s03i236 + source Data from Met Office Unified Model 6.05 + Cell methods: + mean: time (6 hour) >>> print(cube2) air_temperature / (K) (longitude: 49; latitude: 37) - Dimension coordinates: - longitude x - - latitude - x - Scalar coordinates: - forecast_period: 10794 hours - forecast_reference_time: 1859-09-01 06:00:00 - height: 1.5 m - time: 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) - Attributes: - Conventions: CF-1.5 - Model scenario: E1 - STASH: m01s03i236 - source: Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) + Dimension coordinates: + longitude x - + latitude - x + Scalar coordinates: + forecast_period 10794 hours + forecast_reference_time 1859-09-01 06:00:00 + height 1.5 m + time 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) + Attributes: + Conventions CF-1.5 + Model scenario E1 + STASH m01s03i236 + source Data from Met Office Unified Model 6.05 + Cell methods: + mean: time (6 hour) >>> Resolve().mapped is None True >>> resolver = Resolve(cube1, cube2) @@ -2469,39 +2469,39 @@ def shape(self): >>> print(cube1) air_temperature / (K) (time: 240; latitude: 37; longitude: 49) - Dimension coordinates: - time x - - - latitude - x - - longitude - - x - Auxiliary coordinates: - forecast_period x - - - Scalar coordinates: - forecast_reference_time: 1859-09-01 06:00:00 - height: 1.5 m - Attributes: - Conventions: CF-1.5 - Model scenario: A1B - STASH: m01s03i236 - source: Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) + Dimension coordinates: + time x - - + latitude - x - + longitude - - x + Auxiliary coordinates: + forecast_period x - - + Scalar coordinates: + forecast_reference_time 1859-09-01 06:00:00 + height 1.5 m + Attributes: + Conventions CF-1.5 + Model scenario A1B + STASH m01s03i236 + source Data from Met Office Unified Model 6.05 + Cell methods: + mean: time (6 hour) >>> print(cube2) air_temperature / (K) (longitude: 49; latitude: 37) - Dimension coordinates: - longitude x - - latitude - x - Scalar coordinates: - forecast_period: 10794 hours - forecast_reference_time: 1859-09-01 06:00:00 - height: 1.5 m - time: 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) - Attributes: - Conventions: CF-1.5 - Model scenario: E1 - STASH: m01s03i236 - source: Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) + Dimension coordinates: + longitude x - + latitude - x + Scalar coordinates: + forecast_period 10794 hours + forecast_reference_time 1859-09-01 06:00:00 + height 1.5 m + time 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) + Attributes: + Conventions CF-1.5 + Model scenario E1 + STASH m01s03i236 + source Data from Met Office Unified Model 6.05 + Cell methods: + mean: time (6 hour) >>> Resolve().shape is None True >>> Resolve(cube1, cube2).shape diff --git a/lib/iris/cube.py b/lib/iris/cube.py index bf30fc81c0..d6f441d0f6 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -739,21 +739,22 @@ class Cube(CFVariableMixin): >>> cube = iris.load_cube(iris.sample_data_path('air_temp.pp')) >>> print(cube) air_temperature / (K) (latitude: 73; longitude: 96) - Dimension coordinates: - latitude x - - longitude - x - Scalar coordinates: - forecast_period: 6477 hours, bound=(-28083.0, 6477.0) hours - forecast_reference_time: 1998-03-01 03:00:00 - pressure: 1000.0 hPa - time: 1998-12-01 00:00:00, \ -bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) - Attributes: - STASH: m01s16i203 - source: Data from Met Office Unified Model - Cell methods: - mean within years: time - mean over years: time + Dimension coordinates: + latitude x - + longitude - x + Scalar coordinates: + forecast_period \ +6477 hours, bound=(-28083.0, 6477.0) hours + forecast_reference_time 1998-03-01 03:00:00 + pressure 1000.0 hPa + time \ +1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model + Cell methods: + mean within years: time + mean over years: time See the :doc:`user guide` for more information. @@ -2248,13 +2249,14 @@ def summary(self, shorten=False, name_padding=35): versus length and, optionally, a summary of all other components. Kwargs: + * shorten (bool): If set, produce a one-line summary of minimal width, showing only the cube name, units and dimensions. When not set (default), produces a full multi-line summary string. * name_padding (int): - Control the minimum width of cube name+units, before the dimension - map section. Only applies when `shorten`=False. + Control the *minimum* width of the cube name + units, + i.e. the indent of the dimension map section. """ from iris._representation.cube_printout import CubePrinter @@ -3462,20 +3464,21 @@ def collapsed(self, coords, aggregator, **kwargs): >>> new_cube = cube.collapsed('longitude', iris.analysis.MEAN) >>> print(new_cube) surface_temperature / (K) (time: 54; latitude: 18) - Dimension coordinates: - time x - - latitude - x - Auxiliary coordinates: - forecast_reference_time x - - Scalar coordinates: - forecast_period: 0 hours - longitude: 180.0 degrees, bound=(0.0, 360.0) degrees - Attributes: - Conventions: CF-1.5 - STASH: m01s00i024 - Cell methods: - mean: month, year - mean: longitude + Dimension coordinates: + time x - + latitude - x + Auxiliary coordinates: + forecast_reference_time x - + Scalar coordinates: + forecast_period 0 hours + longitude \ +180.0 degrees, bound=(0.0, 360.0) degrees + Attributes: + Conventions CF-1.5 + STASH m01s00i024 + Cell methods: + mean: month, year + mean: longitude .. note:: @@ -3691,26 +3694,26 @@ def aggregated_by(self, coords, aggregator, **kwargs): >>> print(new_cube) surface_temperature / (K) \ (time: 5; latitude: 18; longitude: 432) - Dimension coordinates: - time \ - x - - - latitude \ - - x - - longitude \ - - - x - Auxiliary coordinates: - forecast_reference_time \ - x - - - year \ - x - - - Scalar coordinates: - forecast_period: 0 hours - Attributes: - Conventions: CF-1.5 - STASH: m01s00i024 - Cell methods: - mean: month, year - mean: year + Dimension coordinates: + time \ +x - - + latitude \ +- x - + longitude \ +- - x + Auxiliary coordinates: + forecast_reference_time \ +x - - + year \ +x - - + Scalar coordinates: + forecast_period 0 hours + Attributes: + Conventions CF-1.5 + STASH m01s00i024 + Cell methods: + mean: month, year + mean: year """ groupby_coords = [] @@ -3896,51 +3899,52 @@ def rolling_window(self, coord, aggregator, window, **kwargs): >>> print(air_press) surface_temperature / (K) \ (time: 6; latitude: 145; longitude: 192) - Dimension coordinates: - time \ - x - - - latitude \ - - x - - longitude \ - - - x - Auxiliary coordinates: - forecast_period \ - x - - - Scalar coordinates: - forecast_reference_time: 2011-07-23 00:00:00 - realization: 10 - Attributes: - STASH: m01s00i024 - source: Data from Met Office Unified Model - um_version: 7.6 - Cell methods: - mean: time (1 hour) + Dimension coordinates: + time \ +x - - + latitude \ +- x - + longitude \ +- - x + Auxiliary coordinates: + forecast_period \ +x - - + Scalar coordinates: + forecast_reference_time 2011-07-23 00:00:00 + realization 10 + Attributes: + STASH m01s00i024 + source \ +Data from Met Office Unified Model + um_version 7.6 + Cell methods: + mean: time (1 hour) >>> print(air_press.rolling_window('time', iris.analysis.MEAN, 3)) surface_temperature / (K) \ (time: 4; latitude: 145; longitude: 192) - Dimension coordinates: - time \ - x - - - latitude \ - - x - - longitude \ - - - x - Auxiliary coordinates: - forecast_period \ - x - - - Scalar coordinates: - forecast_reference_time: 2011-07-23 00:00:00 - realization: 10 - Attributes: - STASH: m01s00i024 - source: Data from Met Office Unified Model - um_version: 7.6 - Cell methods: - mean: time (1 hour) - mean: time - + Dimension coordinates: + time \ +x - - + latitude \ +- x - + longitude \ +- - x + Auxiliary coordinates: + forecast_period \ +x - - + Scalar coordinates: + forecast_reference_time 2011-07-23 00:00:00 + realization 10 + Attributes: + STASH m01s00i024 + source \ +Data from Met Office Unified Model + um_version 7.6 + Cell methods: + mean: time (1 hour) + mean: time Notice that the forecast_period dimension now represents the 4 possible windows of size 3 from the original cube. From 7045e7ee37d4f1f45239263a3a37f3b40d53b844 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 24 Jun 2021 11:23:41 +0100 Subject: [PATCH 10/23] Fix doctest output for python 3.8. --- docs/src/further_topics/metadata.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/further_topics/metadata.rst b/docs/src/further_topics/metadata.rst index 1870c6a04a..dac0baeb1c 100644 --- a/docs/src/further_topics/metadata.rst +++ b/docs/src/further_topics/metadata.rst @@ -263,7 +263,7 @@ using the `namedtuple._asdict`_ method. This can be particularly handy when a standard Python built-in container is required to represent your ``metadata``, >>> metadata._asdict() - OrderedDict([('standard_name', 'longitude'), ('long_name', None), ('var_name', 'longitude'), ('units', Unit('degrees')), ('attributes', {'grinning face': '🙃'}), ('coord_system', GeogCS(6371229.0)), ('climatological', False), ('circular', False)]) + {'standard_name': 'longitude', 'long_name': None, 'var_name': 'longitude', 'units': Unit('degrees'), 'attributes': {'grinning face': '🙃'}, 'coord_system': GeogCS(6371229.0), 'climatological': False, 'circular': False} Using the `namedtuple._replace`_ method allows you to create a new metadata class instance, but replacing specified members with **new** associated values, @@ -943,7 +943,7 @@ such as a `dict`_, >>> mapping = latitude.metadata._asdict() >>> mapping - OrderedDict([('standard_name', 'latitude'), ('long_name', None), ('var_name', 'latitude'), ('units', Unit('degrees')), ('attributes', {}), ('coord_system', GeogCS(6371229.0)), ('climatological', False), ('circular', False)]) + {'standard_name': 'latitude', 'long_name': None, 'var_name': 'latitude', 'units': Unit('degrees'), 'attributes': {}, 'coord_system': GeogCS(6371229.0), 'climatological': False, 'circular': False} >>> longitude.metadata = mapping >>> longitude.metadata DimCoordMetadata(standard_name='latitude', long_name=None, var_name='latitude', units=Unit('degrees'), attributes={}, coord_system=GeogCS(6371229.0), climatological=False, circular=False) From 7be7520330f3ea330091567ff1d53ee2e9075cb6 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Tue, 29 Jun 2021 16:27:52 +0100 Subject: [PATCH 11/23] Fix merged docs test. --- lib/iris/util.py | 56 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/iris/util.py b/lib/iris/util.py index 9cadf85f0f..54e18548c5 100644 --- a/lib/iris/util.py +++ b/lib/iris/util.py @@ -1540,23 +1540,23 @@ def promote_aux_coord_to_dim_coord(cube, name_or_coord): >>> print(cube) air_temperature / (K) (time: 240; latitude: 37; longitude: 49) - Dimension coordinates: - time x - - - latitude - x - - longitude - - x - Auxiliary coordinates: - forecast_period x - - - year x - - + Dimension coordinates: + time x - - + latitude - x - + longitude - - x + Auxiliary coordinates: + forecast_period x - - + year x - - >>> promote_aux_coord_to_dim_coord(cube, "year") >>> print(cube) air_temperature / (K) (year: 240; latitude: 37; longitude: 49) - Dimension coordinates: - year x - - - latitude - x - - longitude - - x - Auxiliary coordinates: - forecast_period x - - - time x - - + Dimension coordinates: + year x - - + latitude - x - + longitude - - x + Auxiliary coordinates: + forecast_period x - - + time x - - """ from iris.coords import Coord, DimCoord @@ -1666,23 +1666,23 @@ def demote_dim_coord_to_aux_coord(cube, name_or_coord): >>> print(cube) air_temperature / (K) (time: 240; latitude: 37; longitude: 49) - Dimension coordinates: - time x - - - latitude - x - - longitude - - x - Auxiliary coordinates: - forecast_period x - - - year x - - + Dimension coordinates: + time x - - + latitude - x - + longitude - - x + Auxiliary coordinates: + forecast_period x - - + year x - - >>> demote_dim_coord_to_aux_coord(cube, "time") >>> print(cube) air_temperature / (K) (-- : 240; latitude: 37; longitude: 49) - Dimension coordinates: - latitude - x - - longitude - - x - Auxiliary coordinates: - forecast_period x - - - time x - - - year x - - + Dimension coordinates: + latitude - x - + longitude - - x + Auxiliary coordinates: + forecast_period x - - + time x - - + year x - - """ from iris.coords import Coord From 552a45ef4d8baf13358f04ee0352aa2a022fb049 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 22 Jul 2021 12:16:56 +0100 Subject: [PATCH 12/23] Make cube-summary list attributes last. --- lib/iris/_representation/cube_summary.py | 2 +- .../tests/unit/representation/cube_summary/test_CubeSummary.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/iris/_representation/cube_summary.py b/lib/iris/_representation/cube_summary.py index e8cb1effd5..6d0e6e2896 100644 --- a/lib/iris/_representation/cube_summary.py +++ b/lib/iris/_representation/cube_summary.py @@ -311,7 +311,7 @@ def add_scalar_section(section_class, title, *args): "Scalar cell measures:", scalar_cell_measures, ) - add_scalar_section(AttributeSection, "Attributes:", cube.attributes) add_scalar_section( CellMethodSection, "Cell methods:", cube.cell_methods ) + add_scalar_section(AttributeSection, "Attributes:", cube.attributes) diff --git a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py index e3b7c69338..79baf65c8b 100644 --- a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py +++ b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py @@ -72,8 +72,8 @@ def test_blank_cube(self): expected_scalar_sections = [ "Scalar coordinates:", "Scalar cell measures:", - "Attributes:", "Cell methods:", + "Attributes:", ] self.assertEqual( From bcb689df475a0cf975d3be76182effe9eb7e3e33 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 22 Jul 2021 16:58:27 +0100 Subject: [PATCH 13/23] Fix affected tests. --- docs/src/further_topics/metadata.rst | 4 +-- docs/src/userguide/cube_statistics.rst | 12 ++++---- .../interpolation_and_regridding.rst | 12 ++++---- lib/iris/common/resolve.py | 28 +++++++++---------- lib/iris/cube.py | 28 +++++++++---------- .../cdm/str_repr/cell_methods.__str__.txt | 8 +++--- 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/docs/src/further_topics/metadata.rst b/docs/src/further_topics/metadata.rst index dac0baeb1c..1d1e4171da 100644 --- a/docs/src/further_topics/metadata.rst +++ b/docs/src/further_topics/metadata.rst @@ -117,13 +117,13 @@ For example, given the following :class:`~iris.cube.Cube`, Scalar coordinates: forecast_reference_time 1859-09-01 06:00:00 height 1.5 m + Cell methods: + mean: time (6 hour) Attributes: Conventions CF-1.5 Model scenario A1B STASH m01s03i236 source Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) We can easily get all of the associated metadata of the :class:`~iris.cube.Cube` using the ``metadata`` property: diff --git a/docs/src/userguide/cube_statistics.rst b/docs/src/userguide/cube_statistics.rst index b7b1fef171..e976555512 100644 --- a/docs/src/userguide/cube_statistics.rst +++ b/docs/src/userguide/cube_statistics.rst @@ -80,12 +80,12 @@ we can pass the coordinate name and the aggregation definition to the level_height 696.6666 m, bound=(0.0, 1393.3333) m model_level_number 10, bound=(1, 19) sigma 0.92292976, bound=(0.8458596, 1.0) + Cell methods: + mean: model_level_number Attributes: STASH m01s00i004 source Data from Met Office Unified Model um_version 7.3 - Cell methods: - mean: model_level_number Similarly other analysis operators such as ``MAX``, ``MIN`` and ``STD_DEV`` @@ -139,12 +139,12 @@ These areas can now be passed to the ``collapsed`` method as weights: grid_latitude 1.5145501 degrees, bound=(0.14430022, 2.8848) degrees grid_longitude 358.74948 degrees, bound=(357.494, 360.00497) degrees surface_altitude 399.625 m, bound=(-14.0, 813.25) m + Cell methods: + mean: grid_longitude, grid_latitude Attributes: STASH m01s00i004 source Data from Met Office Unified Model um_version 7.3 - Cell methods: - mean: grid_longitude, grid_latitude Several examples of area averaging exist in the gallery which may be of interest, including an example on taking a :ref:`global area-weighted mean @@ -226,11 +226,11 @@ Printing this cube now shows that two extra coordinates exist on the cube: season_year x - - Scalar coordinates: forecast_period 0 hours + Cell methods: + mean: month, year Attributes: Conventions CF-1.5 STASH m01s00i024 - Cell methods: - mean: month, year These two coordinates can now be used to aggregate by season and climate-year: diff --git a/docs/src/userguide/interpolation_and_regridding.rst b/docs/src/userguide/interpolation_and_regridding.rst index f55ee60665..a88b4095e7 100644 --- a/docs/src/userguide/interpolation_and_regridding.rst +++ b/docs/src/userguide/interpolation_and_regridding.rst @@ -74,12 +74,12 @@ Let's take the air temperature cube we've seen previously: forecast_reference_time 1998-03-01 03:00:00 pressure 1000.0 hPa time 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) - Attributes: - STASH m01s16i203 - source Data from Met Office Unified Model Cell methods: mean within years: time mean over years: time + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model We can interpolate specific values from the coordinates of the cube: @@ -93,12 +93,12 @@ We can interpolate specific values from the coordinates of the cube: longitude 0 degrees pressure 1000.0 hPa time 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) - Attributes: - STASH m01s16i203 - source Data from Met Office Unified Model Cell methods: mean within years: time mean over years: time + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model As we can see, the resulting cube is scalar and has longitude and latitude coordinates with the values defined in our sample points. diff --git a/lib/iris/common/resolve.py b/lib/iris/common/resolve.py index 28800c2d2e..3ba604a80c 100644 --- a/lib/iris/common/resolve.py +++ b/lib/iris/common/resolve.py @@ -109,13 +109,13 @@ class Resolve: Scalar coordinates: forecast_reference_time 1859-09-01 06:00:00 height 1.5 m + Cell methods: + mean: time (6 hour) Attributes: Conventions CF-1.5 Model scenario A1B STASH m01s03i236 source Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) >>> print(cube2) air_temperature / (K) (longitude: 49; latitude: 37) @@ -127,13 +127,13 @@ class Resolve: forecast_reference_time 1859-09-01 06:00:00 height 1.5 m time 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) + Cell methods: + mean: time (6 hour) Attributes: Conventions CF-1.5 Model scenario E1 STASH m01s03i236 source Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) >>> print(data.shape) (240, 37, 49) @@ -150,12 +150,12 @@ class Resolve: Scalar coordinates: forecast_reference_time 1859-09-01 06:00:00 height 1.5 m + Cell methods: + mean: time (6 hour) Attributes: Conventions CF-1.5 STASH m01s03i236 source Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) Secondly, creating an *empty* ``resolver`` instance, that may be called *multiple* times with *different* :class:`~iris.cube.Cube` operands and *different* ``data``, @@ -2410,13 +2410,13 @@ def mapped(self): Scalar coordinates: forecast_reference_time 1859-09-01 06:00:00 height 1.5 m + Cell methods: + mean: time (6 hour) Attributes: Conventions CF-1.5 Model scenario A1B STASH m01s03i236 source Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) >>> print(cube2) air_temperature / (K) (longitude: 49; latitude: 37) Dimension coordinates: @@ -2427,13 +2427,13 @@ def mapped(self): forecast_reference_time 1859-09-01 06:00:00 height 1.5 m time 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) + Cell methods: + mean: time (6 hour) Attributes: Conventions CF-1.5 Model scenario E1 STASH m01s03i236 source Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) >>> Resolve().mapped is None True >>> resolver = Resolve(cube1, cube2) @@ -2478,13 +2478,13 @@ def shape(self): Scalar coordinates: forecast_reference_time 1859-09-01 06:00:00 height 1.5 m + Cell methods: + mean: time (6 hour) Attributes: Conventions CF-1.5 Model scenario A1B STASH m01s03i236 source Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) >>> print(cube2) air_temperature / (K) (longitude: 49; latitude: 37) Dimension coordinates: @@ -2495,13 +2495,13 @@ def shape(self): forecast_reference_time 1859-09-01 06:00:00 height 1.5 m time 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) + Cell methods: + mean: time (6 hour) Attributes: Conventions CF-1.5 Model scenario E1 STASH m01s03i236 source Data from Met Office Unified Model 6.05 - Cell methods: - mean: time (6 hour) >>> Resolve().shape is None True >>> Resolve(cube1, cube2).shape diff --git a/lib/iris/cube.py b/lib/iris/cube.py index d6f441d0f6..7b4cf3281d 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -749,12 +749,12 @@ class Cube(CFVariableMixin): pressure 1000.0 hPa time \ 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) - Attributes: - STASH m01s16i203 - source Data from Met Office Unified Model Cell methods: mean within years: time mean over years: time + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model See the :doc:`user guide` for more information. @@ -3473,12 +3473,12 @@ def collapsed(self, coords, aggregator, **kwargs): forecast_period 0 hours longitude \ 180.0 degrees, bound=(0.0, 360.0) degrees - Attributes: - Conventions CF-1.5 - STASH m01s00i024 Cell methods: mean: month, year mean: longitude + Attributes: + Conventions CF-1.5 + STASH m01s00i024 .. note:: @@ -3708,12 +3708,12 @@ def aggregated_by(self, coords, aggregator, **kwargs): x - - Scalar coordinates: forecast_period 0 hours - Attributes: - Conventions CF-1.5 - STASH m01s00i024 Cell methods: mean: month, year mean: year + Attributes: + Conventions CF-1.5 + STASH m01s00i024 """ groupby_coords = [] @@ -3912,13 +3912,13 @@ def rolling_window(self, coord, aggregator, window, **kwargs): Scalar coordinates: forecast_reference_time 2011-07-23 00:00:00 realization 10 + Cell methods: + mean: time (1 hour) Attributes: STASH m01s00i024 source \ Data from Met Office Unified Model um_version 7.6 - Cell methods: - mean: time (1 hour) >>> print(air_press.rolling_window('time', iris.analysis.MEAN, 3)) @@ -3937,14 +3937,14 @@ def rolling_window(self, coord, aggregator, window, **kwargs): Scalar coordinates: forecast_reference_time 2011-07-23 00:00:00 realization 10 + Cell methods: + mean: time (1 hour) + mean: time Attributes: STASH m01s00i024 source \ Data from Met Office Unified Model um_version 7.6 - Cell methods: - mean: time (1 hour) - mean: time Notice that the forecast_period dimension now represents the 4 possible windows of size 3 from the original cube. diff --git a/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt b/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt index c01c801a21..0d08086d13 100644 --- a/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt +++ b/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt @@ -7,11 +7,11 @@ air_temperature / (K) forecast_reference_time 1998-03-06 03:00:00 pressure 1000.0 hPa time 1998-12-01 00:00:00 - Attributes: - STASH m01s16i203 - source Data from Met Office Unified Model Cell methods: mean: longitude (6 minutes, This is a test comment), latitude (12 minutes) average: longitude (6 minutes, This is another test comment), latitude (15 minutes, This is another comment) average: longitude, latitude - percentile: longitude (6 minutes, This is another test comment) \ No newline at end of file + percentile: longitude (6 minutes, This is another test comment) + Attributes: + STASH m01s16i203 + source Data from Met Office Unified Model \ No newline at end of file From 57ecd566b3c62fc6004634923dba22d36e85a2fd Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 22 Jul 2021 17:26:09 +0100 Subject: [PATCH 14/23] Another test fix. --- lib/iris/experimental/representation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index c33c162d4c..b9a6de6e65 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -93,8 +93,8 @@ def __init__(self, cube): "Ancillary variables:": None, "Scalar coordinates:": None, "Scalar cell measures:": None, - "Attributes:": None, "Cell methods:": None, + "Attributes:": None, } self.dim_desc_coords = [ "Dimension coordinates:", From 3f723bd81a1dc2630e01840edfa5c55f9ffc74c0 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Thu, 5 Aug 2021 17:29:09 +0100 Subject: [PATCH 15/23] Reformat TODO string to aid IDE recognition. --- lib/iris/_representation/cube_printout.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index c2c0316e7d..26a344cff9 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -135,9 +135,9 @@ class CubePrinter: text printout of a :class:`iris.cube.Cube`. TODO: the cube :meth:`iris.cube.Cube.__str__` and - :meth:`iris.cube.Cube.__repr__` methods, and - :meth:`iris.cube.Cube.summary` with 'oneline=True', should use this to - produce cube summary strings. + :meth:`iris.cube.Cube.__repr__` methods, and + :meth:`iris.cube.Cube.summary` with 'oneline=True', should use this to + produce cube summary strings. This class has no internal knowledge of :class:`iris.cube.Cube`, but only of :class:`iris._representation.cube_or_summary.CubeSummary`. From 01a73f23ab672180caf72a070603d7858fdd9edc Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Thu, 5 Aug 2021 17:35:00 +0100 Subject: [PATCH 16/23] CubePrinter tab size constants. --- lib/iris/_representation/cube_printout.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index 26a344cff9..51bde82efe 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -144,6 +144,10 @@ class CubePrinter: """ + N_INDENT_SECTION = 4 + N_INDENT_ITEM = 4 + N_INDENT_EXTRA = 4 + def __init__(self, cube_or_summary): """ An object that provides a printout of a cube. @@ -166,17 +170,11 @@ def __init__(self, cube_or_summary): cube_summary = CubeSummary(cube_or_summary) self.table = self._ingest_summary(cube_summary) - def _ingest_summary( - self, - cube_summary, - n_indent_section=4, - n_indent_item=4, - n_indent_extra=4, - ): + def _ingest_summary(self, cube_summary): """Make a table of strings representing the cube-summary.""" - sect_indent = " " * n_indent_section - item_indent = sect_indent + " " * n_indent_item - item_to_extra_indent = " " * n_indent_extra + sect_indent = " " * self.N_INDENT_SECTION + item_indent = sect_indent + " " * self.N_INDENT_ITEM + item_to_extra_indent = " " * self.N_INDENT_EXTRA extra_indent = item_indent + item_to_extra_indent summ = cube_summary From b548b37949672ca49a7f7a753795090b9b693b2a Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Thu, 5 Aug 2021 17:37:35 +0100 Subject: [PATCH 17/23] CubePrinter don't bother assigning cube_summary on ingestion. --- lib/iris/_representation/cube_printout.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index 51bde82efe..6d84f5387e 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -176,9 +176,8 @@ def _ingest_summary(self, cube_summary): item_indent = sect_indent + " " * self.N_INDENT_ITEM item_to_extra_indent = " " * self.N_INDENT_EXTRA extra_indent = item_indent + item_to_extra_indent - summ = cube_summary - fullheader = summ.header + fullheader = cube_summary.header nameunits_string = fullheader.nameunit dimheader = fullheader.dimension_header cube_is_scalar = dimheader.scalar @@ -227,7 +226,7 @@ def add_row(col_texts, scalar=False): add_row(column_header_texts) # Add rows from all the vector sections - for sect in summ.vector_sections.values(): + for sect in cube_summary.vector_sections.values(): if sect.contents: sect_name = sect.title column_texts = [sect_indent + sect_name] @@ -245,7 +244,7 @@ def add_row(col_texts, scalar=False): add_row(column_texts) # Similar for scalar sections - for sect in summ.scalar_sections.values(): + for sect in cube_summary.scalar_sections.values(): if sect.contents: # Add a row for the "section title" text. sect_name = sect.title From 85fd3cf3dda37c082e3686e68aeca38ef30ffdb4 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 6 Aug 2021 10:47:59 +0100 Subject: [PATCH 18/23] cube_printout docstring fixes. --- lib/iris/_representation/cube_printout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index 6d84f5387e..4ff6783fea 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -131,7 +131,7 @@ def __str__(self): class CubePrinter: """ An object created from a - :class:`iris._representation.cube_or_summary.CubeSummary`, which provides + :class:`iris._representation.CubeSummary`, which provides text printout of a :class:`iris.cube.Cube`. TODO: the cube :meth:`iris.cube.Cube.__str__` and @@ -140,7 +140,7 @@ class CubePrinter: produce cube summary strings. This class has no internal knowledge of :class:`iris.cube.Cube`, but only - of :class:`iris._representation.cube_or_summary.CubeSummary`. + of :class:`iris._representation.CubeSummary`. """ From 4d8859ccb0c9d20c365695bdbd476cd9f4954fb9 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 6 Aug 2021 14:17:02 +0100 Subject: [PATCH 19/23] Cube summary align cell methods with other tabbed content. --- docs/src/further_topics/metadata.rst | 2 +- docs/src/userguide/cube_statistics.rst | 110 +++++++------- .../interpolation_and_regridding.rst | 12 +- docs/src/userguide/loading_iris_cubes.rst | 134 +++++++++--------- lib/iris/_representation/cube_printout.py | 4 +- lib/iris/_representation/cube_summary.py | 13 +- lib/iris/common/resolve.py | 14 +- lib/iris/cube.py | 18 +-- .../cdm/str_repr/cell_methods.__str__.txt | 26 ++-- .../representation/test_CubeRepresentation.py | 7 +- .../cube_printout/test_CubePrintout.py | 6 +- 11 files changed, 180 insertions(+), 166 deletions(-) diff --git a/docs/src/further_topics/metadata.rst b/docs/src/further_topics/metadata.rst index 1d1e4171da..79e9c164a0 100644 --- a/docs/src/further_topics/metadata.rst +++ b/docs/src/further_topics/metadata.rst @@ -118,7 +118,7 @@ For example, given the following :class:`~iris.cube.Cube`, forecast_reference_time 1859-09-01 06:00:00 height 1.5 m Cell methods: - mean: time (6 hour) + mean time (6 hour) Attributes: Conventions CF-1.5 Model scenario A1B diff --git a/docs/src/userguide/cube_statistics.rst b/docs/src/userguide/cube_statistics.rst index e976555512..ac66ff4e53 100644 --- a/docs/src/userguide/cube_statistics.rst +++ b/docs/src/userguide/cube_statistics.rst @@ -23,9 +23,9 @@ Collapsing Entire Data Dimensions In the :doc:`subsetting_a_cube` section we saw how to extract a subset of a cube in order to reduce either its dimensionality or its resolution. -Instead of simply extracting a sub-region of the data, -we can produce statistical functions of the data values -across a particular dimension, +Instead of simply extracting a sub-region of the data, +we can produce statistical functions of the data values +across a particular dimension, such as a 'mean over time' or 'minimum over latitude'. .. _cube-statistics_forecast_printout: @@ -57,9 +57,9 @@ For instance, suppose we have a cube: um_version 7.3 -In this case we have a 4 dimensional cube; -to mean the vertical (z) dimension down to a single valued extent -we can pass the coordinate name and the aggregation definition to the +In this case we have a 4 dimensional cube; +to mean the vertical (z) dimension down to a single valued extent +we can pass the coordinate name and the aggregation definition to the :meth:`Cube.collapsed() ` method: >>> import iris.analysis @@ -81,15 +81,15 @@ we can pass the coordinate name and the aggregation definition to the model_level_number 10, bound=(1, 19) sigma 0.92292976, bound=(0.8458596, 1.0) Cell methods: - mean: model_level_number + mean model_level_number Attributes: STASH m01s00i004 source Data from Met Office Unified Model um_version 7.3 -Similarly other analysis operators such as ``MAX``, ``MIN`` and ``STD_DEV`` -can be used instead of ``MEAN``, see :mod:`iris.analysis` for a full list +Similarly other analysis operators such as ``MAX``, ``MIN`` and ``STD_DEV`` +can be used instead of ``MEAN``, see :mod:`iris.analysis` for a full list of currently supported operators. For an example of using this functionality, the @@ -103,14 +103,14 @@ in the gallery takes a zonal mean of an ``XYT`` cube by using the Area Averaging ^^^^^^^^^^^^^^ -Some operators support additional keywords to the ``cube.collapsed`` method. -For example, :func:`iris.analysis.MEAN ` supports -a weights keyword which can be combined with +Some operators support additional keywords to the ``cube.collapsed`` method. +For example, :func:`iris.analysis.MEAN ` supports +a weights keyword which can be combined with :func:`iris.analysis.cartography.area_weights` to calculate an area average. -Let's use the same data as was loaded in the previous example. -Since ``grid_latitude`` and ``grid_longitude`` were both point coordinates -we must guess bound positions for them +Let's use the same data as was loaded in the previous example. +Since ``grid_latitude`` and ``grid_longitude`` were both point coordinates +we must guess bound positions for them in order to calculate the area of the grid boxes:: import iris.analysis.cartography @@ -124,27 +124,27 @@ These areas can now be passed to the ``collapsed`` method as weights: >>> new_cube = cube.collapsed(['grid_longitude', 'grid_latitude'], iris.analysis.MEAN, weights=grid_areas) >>> print(new_cube) - air_potential_temperature / (K) (time: 3; model_level_number: 7) + air_potential_temperature / (K) (time: 3; model_level_number: 7) Dimension coordinates: - time x - - model_level_number - x + time x - + model_level_number - x Auxiliary coordinates: - forecast_period x - - level_height - x - sigma - x + forecast_period x - + level_height - x + sigma - x Derived coordinates: - altitude - x + altitude - x Scalar coordinates: - forecast_reference_time 2009-11-19 04:00:00 - grid_latitude 1.5145501 degrees, bound=(0.14430022, 2.8848) degrees - grid_longitude 358.74948 degrees, bound=(357.494, 360.00497) degrees - surface_altitude 399.625 m, bound=(-14.0, 813.25) m + forecast_reference_time 2009-11-19 04:00:00 + grid_latitude 1.5145501 degrees, bound=(0.14430022, 2.8848) degrees + grid_longitude 358.74948 degrees, bound=(357.494, 360.00497) degrees + surface_altitude 399.625 m, bound=(-14.0, 813.25) m Cell methods: - mean: grid_longitude, grid_latitude + mean grid_longitude, grid_latitude Attributes: - STASH m01s00i004 - source Data from Met Office Unified Model - um_version 7.3 + STASH m01s00i004 + source Data from Met Office Unified Model + um_version 7.3 Several examples of area averaging exist in the gallery which may be of interest, including an example on taking a :ref:`global area-weighted mean @@ -155,24 +155,24 @@ including an example on taking a :ref:`global area-weighted mean Partially Reducing Data Dimensions ---------------------------------- -Instead of completely collapsing a dimension, other methods can be applied -to reduce or filter the number of data points of a particular dimension. +Instead of completely collapsing a dimension, other methods can be applied +to reduce or filter the number of data points of a particular dimension. Aggregation of Grouped Data ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The :meth:`Cube.aggregated_by ` operation -combines data for all points with the same value of a given coordinate. -To do this, you need a coordinate whose points take on only a limited set -of different values -- the *number* of these then determines the size of the +The :meth:`Cube.aggregated_by ` operation +combines data for all points with the same value of a given coordinate. +To do this, you need a coordinate whose points take on only a limited set +of different values -- the *number* of these then determines the size of the reduced dimension. -The :mod:`iris.coord_categorisation` module can be used to make such -'categorical' coordinates out of ordinary ones: The most common use is -to aggregate data over regular *time intervals*, +The :mod:`iris.coord_categorisation` module can be used to make such +'categorical' coordinates out of ordinary ones: The most common use is +to aggregate data over regular *time intervals*, such as by calendar month or day of the week. -For example, let's create two new coordinates on the cube +For example, let's create two new coordinates on the cube to represent the climatological seasons and the season year respectively:: import iris @@ -188,8 +188,8 @@ to represent the climatological seasons and the season year respectively:: .. note:: - The 'season year' is not the same as year number, because (e.g.) the months - Dec11, Jan12 + Feb12 all belong to 'DJF-12'. + The 'season year' is not the same as year number, because (e.g.) the months + Dec11, Jan12 + Feb12 all belong to 'DJF-12'. See :meth:`iris.coord_categorisation.add_season_year`. @@ -206,10 +206,10 @@ to represent the climatological seasons and the season year respectively:: iris.coord_categorisation.add_season_year(cube, 'time', name='season_year') annual_seasonal_mean = cube.aggregated_by( - ['clim_season', 'season_year'], + ['clim_season', 'season_year'], iris.analysis.MEAN) - + Printing this cube now shows that two extra coordinates exist on the cube: .. doctest:: aggregation @@ -227,7 +227,7 @@ Printing this cube now shows that two extra coordinates exist on the cube: Scalar coordinates: forecast_period 0 hours Cell methods: - mean: month, year + mean month, year Attributes: Conventions CF-1.5 STASH m01s00i024 @@ -238,20 +238,20 @@ These two coordinates can now be used to aggregate by season and climate-year: .. doctest:: aggregation >>> annual_seasonal_mean = cube.aggregated_by( - ... ['clim_season', 'season_year'], + ... ['clim_season', 'season_year'], ... iris.analysis.MEAN) >>> print(repr(annual_seasonal_mean)) - -The primary change in the cube is that the cube's data has been -reduced in the 'time' dimension by aggregation (taking means, in this case). -This has collected together all data points with the same values of season and + +The primary change in the cube is that the cube's data has been +reduced in the 'time' dimension by aggregation (taking means, in this case). +This has collected together all data points with the same values of season and season-year. The results are now indexed by the 19 different possible values of season and season-year in a new, reduced 'time' dimension. -We can see this by printing the first 10 values of season+year -from the original cube: These points are individual months, +We can see this by printing the first 10 values of season+year +from the original cube: These points are individual months, so adjacent ones are often in the same season: .. doctest:: aggregation @@ -271,7 +271,7 @@ so adjacent ones are often in the same season: djf 2007 djf 2007 -Compare this with the first 10 values of the new cube's coordinates: +Compare this with the first 10 values of the new cube's coordinates: All the points now have distinct season+year values: .. doctest:: aggregation @@ -294,7 +294,7 @@ All the points now have distinct season+year values: Because the original data started in April 2006 we have some incomplete seasons (e.g. there were only two months worth of data for 'mam-2006'). -In this case we can fix this by removing all of the resultant 'times' which +In this case we can fix this by removing all of the resultant 'times' which do not cover a three month period (note: judged here as > 3*28 days): .. doctest:: aggregation @@ -306,7 +306,7 @@ do not cover a three month period (note: judged here as > 3*28 days): >>> full_season_means -The final result now represents the seasonal mean temperature for 17 seasons +The final result now represents the seasonal mean temperature for 17 seasons from jja-2006 to jja-2010: .. doctest:: aggregation diff --git a/docs/src/userguide/interpolation_and_regridding.rst b/docs/src/userguide/interpolation_and_regridding.rst index a88b4095e7..2295bdd589 100644 --- a/docs/src/userguide/interpolation_and_regridding.rst +++ b/docs/src/userguide/interpolation_and_regridding.rst @@ -75,8 +75,8 @@ Let's take the air temperature cube we've seen previously: pressure 1000.0 hPa time 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) Cell methods: - mean within years: time - mean over years: time + mean within years time + mean over years time Attributes: STASH m01s16i203 source Data from Met Office Unified Model @@ -94,8 +94,8 @@ We can interpolate specific values from the coordinates of the cube: pressure 1000.0 hPa time 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) Cell methods: - mean within years: time - mean over years: time + mean within years time + mean over years time Attributes: STASH m01s16i203 source Data from Met Office Unified Model @@ -237,12 +237,12 @@ For example:: Regridding ---------- -Regridding is conceptually a very similar process to interpolation in Iris. +Regridding is conceptually a very similar process to interpolation in Iris. The primary difference is that interpolation is based on sample points, while regridding is based on the **horizontal** grid of *another cube*. Regridding a cube is achieved with the :meth:`cube.regrid() ` method. -This method expects two arguments: +This method expects two arguments: #. *another cube* that defines the target grid onto which the cube should be regridded, and #. the regridding scheme to use. diff --git a/docs/src/userguide/loading_iris_cubes.rst b/docs/src/userguide/loading_iris_cubes.rst index 28e2755df7..37c8fc3c12 100644 --- a/docs/src/userguide/loading_iris_cubes.rst +++ b/docs/src/userguide/loading_iris_cubes.rst @@ -4,23 +4,23 @@ Loading Iris Cubes =================== -To load a single file into a **list** of Iris cubes +To load a single file into a **list** of Iris cubes the :py:func:`iris.load` function is used:: import iris filename = '/path/to/file' cubes = iris.load(filename) -Iris will attempt to return **as few cubes as possible** -by collecting together multiple fields with a shared standard name -into a single multidimensional cube. +Iris will attempt to return **as few cubes as possible** +by collecting together multiple fields with a shared standard name +into a single multidimensional cube. -The :py:func:`iris.load` function automatically recognises the format +The :py:func:`iris.load` function automatically recognises the format of the given files and attempts to produce Iris Cubes from their contents. .. note:: - Currently there is support for CF NetCDF, GRIB 1 & 2, PP and FieldsFiles + Currently there is support for CF NetCDF, GRIB 1 & 2, PP and FieldsFiles file formats with a framework for this to be extended to custom formats. @@ -34,26 +34,26 @@ In order to find out what has been loaded, the result can be printed: 1: surface_altitude / (m) (grid_latitude: 204; grid_longitude: 187) -This shows that there were 2 cubes as a result of loading the file, they were: +This shows that there were 2 cubes as a result of loading the file, they were: ``air_potential_temperature`` and ``surface_altitude``. The ``surface_altitude`` cube was 2 dimensional with: - * the two dimensions have extents of 204 and 187 respectively and are + * the two dimensions have extents of 204 and 187 respectively and are represented by the ``grid_latitude`` and ``grid_longitude`` coordinates. The ``air_potential_temperature`` cubes were 4 dimensional with: - * the same length ``grid_latitude`` and ``grid_longitude`` dimensions as + * the same length ``grid_latitude`` and ``grid_longitude`` dimensions as ``surface_altitide`` * a ``time`` dimension of length 3 * a ``model_level_number`` dimension of length 7 .. note:: - The result of :func:`iris.load` is **always** a - :class:`list of cubes `. - Anything that can be done with a Python :class:`list` can be done + The result of :func:`iris.load` is **always** a + :class:`list of cubes `. + Anything that can be done with a Python :class:`list` can be done with the resultant list of cubes. It is worth noting, however, that there is no inherent order to this :class:`list of cubes `. @@ -63,19 +63,19 @@ The ``air_potential_temperature`` cubes were 4 dimensional with: .. hint:: - Throughout this user guide you will see the function - ``iris.sample_data_path`` being used to get the filename for the resources + Throughout this user guide you will see the function + ``iris.sample_data_path`` being used to get the filename for the resources used in the examples. The result of this function is just a string. - - Using this function allows us to provide examples which will work - across platforms and with data installed in different locations, + + Using this function allows us to provide examples which will work + across platforms and with data installed in different locations, however in practice you will want to use your own strings:: - + filename = '/path/to/file' cubes = iris.load(filename) -To get the air potential temperature cube from the list of cubes -returned by :py:func:`iris.load` in the previous example, +To get the air potential temperature cube from the list of cubes +returned by :py:func:`iris.load` in the previous example, list indexing can be used: >>> import iris @@ -104,22 +104,22 @@ list indexing can be used: source Data from Met Office Unified Model um_version 7.3 -Notice that the result of printing a **cube** is a little more verbose than -it was when printing a **list of cubes**. In addition to the very short summary -which is provided when printing a list of cubes, information is provided -on the coordinates which constitute the cube in question. +Notice that the result of printing a **cube** is a little more verbose than +it was when printing a **list of cubes**. In addition to the very short summary +which is provided when printing a list of cubes, information is provided +on the coordinates which constitute the cube in question. This was the output discussed at the end of the :doc:`iris_cubes` section. .. note:: - Dimensioned coordinates will have a dimension marker ``x`` in the - appropriate column for each cube data dimension that they describe. + Dimensioned coordinates will have a dimension marker ``x`` in the + appropriate column for each cube data dimension that they describe. Loading Multiple Files ----------------------- -To load more than one file into a list of cubes, a list of filenames can be +To load more than one file into a list of cubes, a list of filenames can be provided to :py:func:`iris.load`:: filenames = [iris.sample_data_path('uk_hires.pp'), @@ -127,10 +127,10 @@ provided to :py:func:`iris.load`:: cubes = iris.load(filenames) -It is also possible to load one or more files with wildcard substitution +It is also possible to load one or more files with wildcard substitution using the expansion rules defined :py:mod:`fnmatch`. -For example, to match **zero or more characters** in the filename, +For example, to match **zero or more characters** in the filename, star wildcards can be used:: filename = iris.sample_data_path('GloSea4', '*.pp') @@ -139,7 +139,7 @@ star wildcards can be used:: .. note:: - The cubes returned will not necessarily be in the same order as the + The cubes returned will not necessarily be in the same order as the order of the filenames. Lazy Loading @@ -157,9 +157,9 @@ For more on the benefits, handling and uses of lazy data, see :doc:`Real and Laz Constrained Loading ----------------------- -Given a large dataset, it is possible to restrict or constrain the load -to match specific Iris cube metadata. -Constrained loading provides the ability to generate a cube +Given a large dataset, it is possible to restrict or constrain the load +to match specific Iris cube metadata. +Constrained loading provides the ability to generate a cube from a specific subset of data that is of particular interest. As we have seen, loading the following file creates several Cubes:: @@ -167,7 +167,7 @@ As we have seen, loading the following file creates several Cubes:: filename = iris.sample_data_path('uk_hires.pp') cubes = iris.load(filename) -Specifying a name as a constraint argument to :py:func:`iris.load` will mean +Specifying a name as a constraint argument to :py:func:`iris.load` will mean only cubes with matching :meth:`name ` will be returned:: @@ -193,21 +193,21 @@ of ``m01s00i033``:: cubes = iris.load(filename, constraint) To constrain the load to multiple distinct constraints, a list of constraints -can be provided. This is equivalent to running load once for each constraint +can be provided. This is equivalent to running load once for each constraint but is likely to be more efficient:: filename = iris.sample_data_path('uk_hires.pp') cubes = iris.load(filename, ['air_potential_temperature', 'surface_altitude']) -The :class:`iris.Constraint` class can be used to restrict coordinate values -on load. For example, to constrain the load to match +The :class:`iris.Constraint` class can be used to restrict coordinate values +on load. For example, to constrain the load to match a specific ``model_level_number``:: filename = iris.sample_data_path('uk_hires.pp') level_10 = iris.Constraint(model_level_number=10) cubes = iris.load(filename, level_10) -Constraints can be combined using ``&`` to represent a more restrictive +Constraints can be combined using ``&`` to represent a more restrictive constraint to ``load``:: filename = iris.sample_data_path('uk_hires.pp') @@ -215,16 +215,16 @@ constraint to ``load``:: level_10 = iris.Constraint(model_level_number=10) cubes = iris.load(filename, forecast_6 & level_10) -As well as being able to combine constraints using ``&``, -the :class:`iris.Constraint` class can accept multiple arguments, -and a list of values can be given to constrain a coordinate to one of +As well as being able to combine constraints using ``&``, +the :class:`iris.Constraint` class can accept multiple arguments, +and a list of values can be given to constrain a coordinate to one of a collection of values:: filename = iris.sample_data_path('uk_hires.pp') level_10_or_16_fp_6 = iris.Constraint(model_level_number=[10, 16], forecast_period=6) cubes = iris.load(filename, level_10_or_16_fp_6) -A common requirement is to limit the value of a coordinate to a specific range, +A common requirement is to limit the value of a coordinate to a specific range, this can be achieved by passing the constraint a function:: def bottom_16_levels(cell): @@ -234,11 +234,11 @@ this can be achieved by passing the constraint a function:: filename = iris.sample_data_path('uk_hires.pp') level_lt_16 = iris.Constraint(model_level_number=bottom_16_levels) cubes = iris.load(filename, level_lt_16) - + .. note:: - As with many of the examples later in this documentation, the - simple function above can be conveniently written as a lambda function + As with many of the examples later in this documentation, the + simple function above can be conveniently written as a lambda function on a single line:: bottom_16_levels = lambda cell: cell <= 16 @@ -247,8 +247,8 @@ this can be achieved by passing the constraint a function:: Note also the :ref:`warning on equality constraints with floating point coordinates `. -Cube attributes can also be part of the constraint criteria. Supposing a -cube attribute of ``STASH`` existed, as is the case when loading ``PP`` files, +Cube attributes can also be part of the constraint criteria. Supposing a +cube attribute of ``STASH`` existed, as is the case when loading ``PP`` files, then specific STASH codes can be filtered:: filename = iris.sample_data_path('uk_hires.pp') @@ -257,8 +257,8 @@ then specific STASH codes can be filtered:: .. seealso:: - For advanced usage there are further examples in the - :class:`iris.Constraint` reference documentation. + For advanced usage there are further examples in the + :class:`iris.Constraint` reference documentation. Constraining a Circular Coordinate Across its Boundary @@ -390,7 +390,7 @@ PartialDateTime this becomes simple: >>> st_swithuns_daterange = iris.Constraint( ... time=lambda cell: PartialDateTime(month=7, day=15) <= cell < PartialDateTime(month=8, day=25)) >>> within_st_swithuns = long_ts.extract(st_swithuns_daterange) - ... + ... >>> print(within_st_swithuns.coord('time')) DimCoord([2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00, @@ -407,13 +407,13 @@ Strict Loading -------------- The :py:func:`iris.load_cube` and :py:func:`iris.load_cubes` functions are -similar to :py:func:`iris.load` except they can only return +similar to :py:func:`iris.load` except they can only return *one cube per constraint*. The :func:`iris.load_cube` function accepts a single constraint and returns a single cube. The :func:`iris.load_cubes` function accepts any number of constraints and returns a list of cubes (as an `iris.cube.CubeList`). Providing no constraints to :func:`iris.load_cube` or :func:`iris.load_cubes` -is equivalent to requesting exactly one cube of any type. +is equivalent to requesting exactly one cube of any type. A single cube is loaded in the following example:: @@ -426,9 +426,9 @@ A single cube is loaded in the following example:: longitude - x ... Cell methods: - mean: time + mean time -However, when attempting to load data which would result in anything other than +However, when attempting to load data which would result in anything other than one cube, an exception is raised:: >>> filename = iris.sample_data_path('uk_hires.pp') @@ -438,19 +438,19 @@ one cube, an exception is raised:: iris.exceptions.ConstraintMismatchError: Expected exactly one cube, found 2. .. note:: - - All the load functions share many of the same features, hence - multiple files could be loaded with wildcard filenames + + All the load functions share many of the same features, hence + multiple files could be loaded with wildcard filenames or by providing a list of filenames. The strict nature of :func:`iris.load_cube` and :func:`iris.load_cubes` -means that, when combined with constrained loading, it is possible to -ensure that precisely what was asked for on load is given -- otherwise an exception is raised. -This fact can be utilised to make code only run successfully if +means that, when combined with constrained loading, it is possible to +ensure that precisely what was asked for on load is given +- otherwise an exception is raised. +This fact can be utilised to make code only run successfully if the data provided has the expected criteria. -For example, suppose that code needed ``air_potential_temperature`` +For example, suppose that code needed ``air_potential_temperature`` in order to run:: import iris @@ -458,23 +458,23 @@ in order to run:: air_pot_temp = iris.load_cube(filename, 'air_potential_temperature') print(air_pot_temp) -Should the file not produce exactly one cube with a standard name of +Should the file not produce exactly one cube with a standard name of 'air_potential_temperature', an exception will be raised. -Similarly, supposing a routine needed both 'surface_altitude' and +Similarly, supposing a routine needed both 'surface_altitude' and 'air_potential_temperature' to be able to run:: import iris filename = iris.sample_data_path('uk_hires.pp') altitude_cube, pot_temp_cube = iris.load_cubes(filename, ['surface_altitude', 'air_potential_temperature']) -The result of :func:`iris.load_cubes` in this case will be a list of 2 cubes -ordered by the constraints provided. Multiple assignment has been used to put +The result of :func:`iris.load_cubes` in this case will be a list of 2 cubes +ordered by the constraints provided. Multiple assignment has been used to put these two cubes into separate variables. .. note:: - In Python, lists of a pre-known length and order can be exploited + In Python, lists of a pre-known length and order can be exploited using *multiple assignment*: >>> number_one, number_two = [1, 2] diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index 4ff6783fea..f85d163916 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -262,10 +262,10 @@ def add_scalar_row(name, value=""): add_scalar_row(item.name, item.content) if item.extra: add_scalar_row(item_to_extra_indent + item.extra) - elif "attribute" in title: + elif "attribute" in title or "cell method" in title: for title, value in zip(sect.names, sect.values): add_scalar_row(title, value) - elif "scalar cell measure" in title or "cell method" in title: + elif "scalar cell measure" in title: # These are just strings: nothing in the 'value' column. for name in sect.contents: add_scalar_row(name) diff --git a/lib/iris/_representation/cube_summary.py b/lib/iris/_representation/cube_summary.py index 6d0e6e2896..a3a90b5da2 100644 --- a/lib/iris/_representation/cube_summary.py +++ b/lib/iris/_representation/cube_summary.py @@ -218,7 +218,18 @@ def __init__(self, title, attributes): class CellMethodSection(Section): def __init__(self, title, cell_methods): self.title = title - self.contents = [str(cm) for cm in cell_methods] + # self.contents = [str(cm) for cm in cell_methods] + self.names = [] + self.values = [] + self.contents = [] + for method in cell_methods: + name = method.method + # Remove "method: " from the front of the string, leaving the value. + value = str(method)[len(name + ": ") :] + self.names.append(name) + self.values.append(value) + content = "{}: {}".format(name, value) + self.contents.append(content) class CubeSummary: diff --git a/lib/iris/common/resolve.py b/lib/iris/common/resolve.py index 3ba604a80c..6eb79a65f9 100644 --- a/lib/iris/common/resolve.py +++ b/lib/iris/common/resolve.py @@ -110,7 +110,7 @@ class Resolve: forecast_reference_time 1859-09-01 06:00:00 height 1.5 m Cell methods: - mean: time (6 hour) + mean time (6 hour) Attributes: Conventions CF-1.5 Model scenario A1B @@ -128,7 +128,7 @@ class Resolve: height 1.5 m time 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) Cell methods: - mean: time (6 hour) + mean time (6 hour) Attributes: Conventions CF-1.5 Model scenario E1 @@ -151,7 +151,7 @@ class Resolve: forecast_reference_time 1859-09-01 06:00:00 height 1.5 m Cell methods: - mean: time (6 hour) + mean time (6 hour) Attributes: Conventions CF-1.5 STASH m01s03i236 @@ -2411,7 +2411,7 @@ def mapped(self): forecast_reference_time 1859-09-01 06:00:00 height 1.5 m Cell methods: - mean: time (6 hour) + mean time (6 hour) Attributes: Conventions CF-1.5 Model scenario A1B @@ -2428,7 +2428,7 @@ def mapped(self): height 1.5 m time 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) Cell methods: - mean: time (6 hour) + mean time (6 hour) Attributes: Conventions CF-1.5 Model scenario E1 @@ -2479,7 +2479,7 @@ def shape(self): forecast_reference_time 1859-09-01 06:00:00 height 1.5 m Cell methods: - mean: time (6 hour) + mean time (6 hour) Attributes: Conventions CF-1.5 Model scenario A1B @@ -2496,7 +2496,7 @@ def shape(self): height 1.5 m time 1860-06-01 00:00:00, bound=(1859-12-01 00:00:00, 1860-12-01 00:00:00) Cell methods: - mean: time (6 hour) + mean time (6 hour) Attributes: Conventions CF-1.5 Model scenario E1 diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 7b4cf3281d..c6aa9634ee 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -750,8 +750,8 @@ class Cube(CFVariableMixin): time \ 1998-12-01 00:00:00, bound=(1994-12-01 00:00:00, 1998-12-01 00:00:00) Cell methods: - mean within years: time - mean over years: time + mean within years time + mean over years time Attributes: STASH m01s16i203 source Data from Met Office Unified Model @@ -3474,8 +3474,8 @@ def collapsed(self, coords, aggregator, **kwargs): longitude \ 180.0 degrees, bound=(0.0, 360.0) degrees Cell methods: - mean: month, year - mean: longitude + mean month, year + mean longitude Attributes: Conventions CF-1.5 STASH m01s00i024 @@ -3709,8 +3709,8 @@ def aggregated_by(self, coords, aggregator, **kwargs): Scalar coordinates: forecast_period 0 hours Cell methods: - mean: month, year - mean: year + mean month, year + mean year Attributes: Conventions CF-1.5 STASH m01s00i024 @@ -3913,7 +3913,7 @@ def rolling_window(self, coord, aggregator, window, **kwargs): forecast_reference_time 2011-07-23 00:00:00 realization 10 Cell methods: - mean: time (1 hour) + mean time (1 hour) Attributes: STASH m01s00i024 source \ @@ -3938,8 +3938,8 @@ def rolling_window(self, coord, aggregator, window, **kwargs): forecast_reference_time 2011-07-23 00:00:00 realization 10 Cell methods: - mean: time (1 hour) - mean: time + mean time (1 hour) + mean time Attributes: STASH m01s00i024 source \ diff --git a/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt b/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt index 0d08086d13..ba93542e51 100644 --- a/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt +++ b/lib/iris/tests/results/cdm/str_repr/cell_methods.__str__.txt @@ -1,17 +1,17 @@ -air_temperature / (K) (latitude: 73; longitude: 96) +air_temperature / (K) (latitude: 73; longitude: 96) Dimension coordinates: - latitude x - - longitude - x + latitude x - + longitude - x Scalar coordinates: - forecast_period 6477.0 hours - forecast_reference_time 1998-03-06 03:00:00 - pressure 1000.0 hPa - time 1998-12-01 00:00:00 + forecast_period 6477.0 hours + forecast_reference_time 1998-03-06 03:00:00 + pressure 1000.0 hPa + time 1998-12-01 00:00:00 Cell methods: - mean: longitude (6 minutes, This is a test comment), latitude (12 minutes) - average: longitude (6 minutes, This is another test comment), latitude (15 minutes, This is another comment) - average: longitude, latitude - percentile: longitude (6 minutes, This is another test comment) + mean longitude (6 minutes, This is a test comment), latitude (12 minutes) + average longitude (6 minutes, This is another test comment), latitude (15 minutes, This is another comment) + average longitude, latitude + percentile longitude (6 minutes, This is another test comment) Attributes: - STASH m01s16i203 - source Data from Met Office Unified Model \ No newline at end of file + STASH m01s16i203 + source Data from Met Office Unified Model \ No newline at end of file diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 80f0118fab..b05e19e1ee 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -197,8 +197,11 @@ def test_headings__attributes(self): def test_headings__cellmethods(self): contents = self.representer.str_headings["Cell methods:"] content_str = ",".join(content for content in contents) - for cell_method in self.cube.cell_methods: - self.assertIn(str(cell_method), content_str) + for method in self.cube.cell_methods: + name = method.method + value = str(method)[len(name + ": ") :] + self.assertIn(name, content_str) + self.assertIn(value, content_str) @tests.skip_data diff --git a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py index 08d66ea8ce..f49c9f9c0c 100644 --- a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py +++ b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py @@ -506,10 +506,10 @@ def test_section_cell_methods(self): rep = cube_replines(cube) # Note: not alphabetical -- provided order is significant expected = [ - "name / (1) (-- : 1)", + "name / (1) (-- : 1)", " Cell methods:", - " stdev: area", - " mean: y (10m, vertical), time (3min, =duration)", + " stdev area", + " mean y (10m, vertical), time (3min, =duration)", ] self.assertEqual(rep, expected) From 46b6d54a891c8efe4c15220a9ba10c736e4116d6 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 6 Aug 2021 10:51:34 +0100 Subject: [PATCH 20/23] Remove CubePrinter TODO. --- lib/iris/_representation/cube_printout.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index f85d163916..81d46bb29f 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -134,11 +134,6 @@ class CubePrinter: :class:`iris._representation.CubeSummary`, which provides text printout of a :class:`iris.cube.Cube`. - TODO: the cube :meth:`iris.cube.Cube.__str__` and - :meth:`iris.cube.Cube.__repr__` methods, and - :meth:`iris.cube.Cube.summary` with 'oneline=True', should use this to - produce cube summary strings. - This class has no internal knowledge of :class:`iris.cube.Cube`, but only of :class:`iris._representation.CubeSummary`. From 1f9cf8c2d9b69a41629a7ed635d37b90af0009dc Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 6 Aug 2021 14:19:50 +0100 Subject: [PATCH 21/23] Remove CubeSummary TODO. --- lib/iris/_representation/cube_summary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/iris/_representation/cube_summary.py b/lib/iris/_representation/cube_summary.py index a3a90b5da2..bce949d1dc 100644 --- a/lib/iris/_representation/cube_summary.py +++ b/lib/iris/_representation/cube_summary.py @@ -235,7 +235,6 @@ def __init__(self, title, cell_methods): class CubeSummary: """ This class provides a structure for output representations of an Iris cube. - TODO: use to produce the printout of :meth:`iris.cube.Cube.__str__`. """ From 83730c34da4d00616a05b549bd68c25365d7f409 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Mon, 9 Aug 2021 13:26:17 +0100 Subject: [PATCH 22/23] Remove commented cube summary code. --- lib/iris/_representation/cube_summary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/iris/_representation/cube_summary.py b/lib/iris/_representation/cube_summary.py index bce949d1dc..c7d0e15e59 100644 --- a/lib/iris/_representation/cube_summary.py +++ b/lib/iris/_representation/cube_summary.py @@ -218,7 +218,6 @@ def __init__(self, title, attributes): class CellMethodSection(Section): def __init__(self, title, cell_methods): self.title = title - # self.contents = [str(cm) for cm in cell_methods] self.names = [] self.values = [] self.contents = [] From 319963edfede23cfd5f5fb842e715b2c6aeb4a44 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Mon, 9 Aug 2021 14:21:40 +0100 Subject: [PATCH 23/23] Cube summary what's new entries. --- docs/src/whatsnew/latest.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index de99f9a592..4daff5a2d1 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -56,6 +56,12 @@ This document explains the changes made to Iris for this release #. `@Badboy-16`_ implemented a ``CubeList.copy()`` method to return a ``CubeList`` object instead of a ``list``. (:pull:`4094`) +#. `@pp-mo`_ and `@trexfeathers`_ reformatted :meth:`iris.cube.Cube.summary`, + (which is used for ``print(Cube)``); putting + :attr:`~iris.cube.Cube.cell_methods` before + :attr:`~iris.cube.Cube.attributes`, and improving spacing throughout. + (:pull:`4206`) + 🐛 Bugs Fixed ============= @@ -220,9 +226,14 @@ This document explains the changes made to Iris for this release ``gallery`` tasks into a single task and associated `nox`_ session. (:pull:`4219`) -#. `@jamesp`_ and `@trexfeathers`_ implmented a benchmarking CI check +#. `@jamesp`_ and `@trexfeathers`_ implemented a benchmarking CI check using `asv`_. (:pull:`4253`) +#. `@pp-mo`_ refactored almost all of :meth:`iris.cube.Cube.summary` into the + new private module: :mod:`iris._representation`; rewritten with a more + modular approach, resulting in more readable and extensible code. + (:pull:`4206`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, core dev names are automatically included by the common_links.inc: