Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
581ac0e
Initial working.
pp-mo Jan 13, 2022
8dfa329
Temporary test exercising.
pp-mo Jan 13, 2022
a1745b6
Fix docstring, simplify.
pp-mo Jan 13, 2022
70ba216
Small mods.
pp-mo Jan 13, 2022
38ba475
Additional changes, with old+intermediate versions commented.
pp-mo Jan 17, 2022
9553d9b
Tidy, removing old commented forms.
pp-mo Jan 17, 2022
77fc1f6
Repr always has shape, except scalar cases.
pp-mo Jan 17, 2022
1a97a98
Don't print calendar in oneline summary.
pp-mo Jan 17, 2022
12f0be5
Initial working dim-meta printout tests.
pp-mo Jan 18, 2022
910f090
Printout features and all tests complete : existing tests *not* yet f…
pp-mo Jan 18, 2022
e85ba19
Fix existing connectivity print tests.
pp-mo Jan 18, 2022
d3eaf2f
Fix existing MeshCoord printout tests.
pp-mo Jan 18, 2022
881d7a0
Fix various str+repr changes in tests/test_coord_api.
pp-mo Jan 18, 2022
d7308b5
Fix various str+repr changes in tests/unit/coords/test_Coord.
pp-mo Jan 18, 2022
a68f31b
Fix existing printout tests for Ancils and CellMeasures.
pp-mo Jan 19, 2022
c82283b
Added new str and repr for Mesh.
pp-mo Jan 19, 2022
286565c
Doctest fixes.
pp-mo Jan 19, 2022
beba1f2
Tidy up unused methods, api and docstrings
pp-mo Jan 19, 2022
9a5d9d3
Add specific tests for 'summary' method.
pp-mo Jan 19, 2022
4e1867d
Tiny fixes.
pp-mo Jan 19, 2022
49bf0c3
Use UGRID term 'optional' connectivities instead of 'extra'.
pp-mo Jan 21, 2022
e8bef5f
Revise kwargs and clarify their relationship to numpy printoptions.
pp-mo Jan 21, 2022
a2a6015
More small cosmetic changes, plus minimal bounds in repr.
pp-mo Jan 21, 2022
0a56e37
Fix coord_api repr tests.
pp-mo Jan 21, 2022
2e82bba
Added whatsnew + fixed docstring formatting.
pp-mo Jan 24, 2022
fdb352f
Add summary section-location info for MeshCoord. Fix and test for bo…
pp-mo Jan 25, 2022
b868d40
Clarify text-output code.
pp-mo Jan 25, 2022
d6cf339
Remove obsolete comment.
pp-mo Jan 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 94 additions & 33 deletions lib/iris/coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ def summary(
linewidth=None,
precision=None,
convert_dates=True,
_section_indices=None,
):
r"""
Make a printable text summary.
Expand Down Expand Up @@ -324,6 +325,11 @@ def summary(
arguments.

"""
# NOTE: the *private* key "_section_indices" can be set to a dict, to
# return details of which (line, character) each particular section of
# the output text begins at.
# Currently only used by MeshCoord.summary(), which needs this info to
# modify the result string, for idiosyncratic reasons.

def array_summary(data, n_max, n_edge, linewidth, precision):
# Return a text summary of an array.
Expand Down Expand Up @@ -412,12 +418,30 @@ def array_summary(data, n_max, n_edge, linewidth, precision):
precision=precision,
)

#
# Routines to track output lines and the locations of named 'sections'
#
output_lines = []
line_text = ""

def add_output_line():
output_lines.append(line_text)

def record_section_index(section_name):
if _section_indices is not None:
# record the current line number and character position
i_line = len(output_lines)
i_char = len(line_text)
_section_indices[section_name] = (i_line, i_char)

if shorten:
result = f"<{cls_str}: {title_str} "
if data_str == "<lazy>":
# Data summary is invalid due to being lazy : show shape.
data_str += f" shape{shape_str}"
else:
# One-line output format.
line_text = f"<{cls_str}: "
record_section_index("title")
line_text += title_str + " "
record_section_index("data") # this is where the data text begins

if data_str != "<lazy>":
# Flatten to a single line, reducing repeated spaces.
def flatten_array_str(array_str):
array_str = array_str.replace("\n", " ")
Expand All @@ -429,7 +453,7 @@ def flatten_array_str(array_str):
data_str = flatten_array_str(data_str)
# Adjust maximum-width to allow for the title width in the
# repr form.
using_array_width = given_array_width - len(result)
using_array_width = given_array_width - len(line_text)
# Work out whether to include a summary of the data values
if len(data_str) > using_array_width:
# Make one more attempt, printing just the *first* point,
Expand All @@ -448,18 +472,22 @@ def flatten_array_str(array_str):
# "placeholder" representation.
data_str = "[...]"

if self.has_bounds():
data_str += "+bnds"
if self.has_bounds():
data_str += "+bounds"

if self.shape != (1,):
# Anything non-scalar : show shape as well.
data_str += f" shape{shape_str}"
if self.shape != (1,):
# Anything non-scalar : show shape as well.
data_str += f" shape{shape_str}"

# (N.B. in the oneline case, we *ignore* any bounds)
result += f"{data_str}>"
line_text += f"{data_str}>"
# single-line output in 'shorten' mode
add_output_line()
else:
# Long (multi-line) form.
result = f"{cls_str} : {title_str}"
# Long (multi-line) output format.
line_text = f"{cls_str} : "
record_section_index("title")
line_text += title_str
add_output_line()

def reindent_data_string(text, n_indent):
lines = [line for line in text.split("\n")]
Expand All @@ -471,14 +499,26 @@ def reindent_data_string(text, n_indent):
return result

data_str = reindent_data_string(data_str, 2 * n_indent)
result += addline + f"{self._values_array_name}: "
line_text = indent
record_section_index("data")
# start the 'data_text' from 'line_text', to preserve any indent
# : it may cover multiple lines
# NOTE: actual section name is variable here : data/points/indices
data_text = line_text + f"{self._values_array_name}: "
if "\n" in data_str:
# Place on subsequent lines
result += "[" + addline + indent + data_str[1:]
# Put initial '[' here, and the rest on subsequent lines
data_text += "[" + addline + indent + data_str[1:]
else:
result += data_str
# All on one line
data_text += data_str

# split the 'data_text' and output each line
for data_line in data_text.split("\n"):
line_text = data_line
add_output_line()

if self.has_bounds():
# Add a bounds section : basically just like the 'data'.
if self._bounds_dm.has_lazy_data():
bounds_str = "<lazy>"
elif max_values == 0:
Expand All @@ -492,22 +532,39 @@ def reindent_data_string(text, n_indent):
precision=precision,
)
bounds_str = reindent_data_string(bounds_str, 2 * n_indent)
result += addline + "bounds: "

line_text = indent
record_section_index("bounds")
# start the 'bounds_text' from 'line_text', to preserve any
# indent : it may cover multiple lines
bounds_text = line_text + "bounds: "
if "\n" in bounds_str:
# Place on subsequent lines
result += "[" + addline + indent + bounds_str[1:]
# Put initial '[' here, and the rest on subsequent lines
bounds_text += "[" + addline + indent + bounds_str[1:]
else:
result += bounds_str
# All on one line
bounds_text += bounds_str

# split the 'bounds_text' and output each line
for bounds_line in bounds_text.split("\n"):
line_text = bounds_line
add_output_line()

# Add shape declaration (always)
result += addline + f"shape: {shape_str}"
# Add shape section (always)
line_text = indent
record_section_index("shape")
line_text += f"shape: {shape_str}"

if self.has_bounds():
# line = f'bounds_shape: {self._bounds_dm.shape}'
result += f" bounds{self._bounds_dm.shape}"
line_text += f" bounds{self._bounds_dm.shape}"

# Add dtype declaration (always)
result += addline + f"dtype: {self.dtype}"
add_output_line()

# Add dtype section (always)
line_text = indent
record_section_index("dtype")
line_text += f"dtype: {self.dtype}"
add_output_line()

for name in self._metadata_manager._fields:
if name == "units":
Expand All @@ -525,10 +582,14 @@ def reindent_data_string(text, n_indent):
# work for all those defined so far.
show = val is not None and val is not False
if show:
line = f"{name}: {val!r}"
result += addline + line

return result
# add a section for this property (metadata item)
# TODO: modify to do multi-line attribute output
line_text = indent
record_section_index(name)
line_text += f"{name}: {val!r}"
add_output_line()

return "\n".join(output_lines)

def __str__(self):
return self.summary()
Expand Down
29 changes: 16 additions & 13 deletions lib/iris/experimental/ugrid/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -2959,39 +2959,42 @@ def summary(self, *args, **kwargs):
# so fix linewidth to suppress them
kwargs["linewidth"] = 1

# Plug private key, to get back the section structure info
section_indices = {}
kwargs["_section_indices"] = section_indices
result = super().summary(*args, **kwargs)

# Modify the generic 'default-form' result to produce what we want.
if shorten:
# Single-line form : insert the mesh+location before the array part
i_array = result.index("[")
# Single-line form : insert mesh+location before the array part
# Construct a text detailing the mesh + location
mesh_string = self.mesh.name()
if mesh_string == "unknown":
# If no name, replace with the one-line summary
mesh_string = self.mesh.summary(shorten=True)
extra_str = f"mesh({mesh_string}) location({self.location}) "
# find where in the line the data-array text begins
i_line, i_array = section_indices["data"]
assert i_line == 0
# insert the extra text there
result = result[:i_array] + extra_str + result[i_array:]
# NOTE: this invalidates the original width calculation and may
# easily extend the result beyond the intended maximum linewidth.
# We do treat that as an advisory control over array printing, not
# an absolute contract, so just ignore the problem for now.
else:
# Multiline form : find the line with " location: ... " in it
# Multiline form
# find where the "location: ... " section is
i_location, i_namestart = section_indices["location"]
lines = result.split("\n")
(i_location,) = [
i
for i, line in enumerate(lines)
if line.strip().startswith("location:")
]
location_line = lines[i_location]
# find the start of the 'location:' to copy the indent spacing
i_namestart = location_line.index("location:")
# copy the indent spacing
indent = location_line[:i_namestart]
# Construct a suitable 'mesh' line
# use that to construct a suitable 'mesh' line
mesh_string = self.mesh.summary(shorten=True)
mesh_line = f"{indent}mesh: {mesh_string}"
# Move the 'location' line, putting it and the 'mesh' line
# immediately after the header
# Move the 'location' line, putting it and the 'mesh' line right at
# the top, immediately after the header line.
del lines[i_location]
lines[1:1] = [mesh_line, location_line]
# Re-join lines to give the result
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<DimCoord: grid_latitude / (degrees) [-0.128, -0.127, ..., -0.121, -0.12 ]+bnds shape(10,)>
<DimCoord: level_height / (m) [ 5. , 21.667, ..., 325. , 395. ]+bounds shape(10,)>
18 changes: 9 additions & 9 deletions lib/iris/tests/results/coord_api/str_repr/aux_nontime_str.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
DimCoord : grid_latitude / (degrees)
DimCoord : level_height / (m)
points: [
-0.1278, -0.1269, -0.126 , -0.1251, -0.1242, -0.1233, -0.1224,
-0.1215, -0.1206, -0.1197]
5. , 21.666664, 45. , 75. , 111.66668 ,
155. , 205. , 261.6667 , 325. , 395. ]
bounds: [
[-0.12825, -0.12735],
[-0.12735, -0.12645],
[ 0. , 13.333332],
[ 13.333332, 33.333332],
...,
[-0.12105, -0.12015],
[-0.12015, -0.11925]]
[293.3333 , 360. ],
[360. , 433.3332 ]]
shape: (10,) bounds(10, 2)
dtype: float32
standard_name: 'grid_latitude'
coord_system: RotatedGeogCS(37.5, 177.5, ellipsoid=GeogCS(6371229.0))
long_name: 'level_height'
attributes: {'positive': 'up'}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<DimCoord: time / (hours since 1970-01-01 00:00:00) [...] shape(6,)>
<DimCoord: forecast_period / (hours) [0.]>
10 changes: 4 additions & 6 deletions lib/iris/tests/results/coord_api/str_repr/aux_time_str.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar)
points: [
2009-09-09 17:10:00, 2009-09-09 17:20:00, 2009-09-09 17:30:00,
2009-09-09 17:40:00, 2009-09-09 17:50:00, 2009-09-09 18:00:00]
shape: (6,)
DimCoord : forecast_period / (hours)
points: [0.]
shape: (1,)
dtype: float64
standard_name: 'time'
standard_name: 'forecast_period'
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<DimCoord: grid_latitude / (degrees) [-0.128, -0.127, ..., -0.121, -0.12 ]+bnds shape(10,)>
<DimCoord: grid_latitude / (degrees) [-0.128, -0.127, ..., -0.121, -0.12 ]+bounds shape(10,)>
16 changes: 10 additions & 6 deletions lib/iris/tests/test_coord_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ def test_complex(self):
@tests.skip_data
class TestCoord_ReprStr_nontime(tests.IrisTest):
def setUp(self):
self.lat = iris.tests.stock.realistic_4d().coord("grid_latitude")[:10]
cube = iris.tests.stock.realistic_4d()
self.lat = cube.coord("grid_latitude")[:10]
self.height = cube.coord("level_height")[:10]
Copy link
Member Author

@pp-mo pp-mo Jan 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests were a bit of a mess : in both the 'time' and 'nontime' classes, the 4 cases were 2 duplicates of only 2 things.
Unfortunately I'm not sure I've improved things much, though.
'height' is not actually an AuxCoord (class), though it is an aux-coord in the cube.
likewise, 'forecast_period' is not a brilliant example of a "time AuxCoord".

Given that this wasn't doing anything very useful before, I'm not going to spend any more effort on it.


def test_DimCoord_repr(self):
self.assertRepr(
Expand All @@ -187,7 +189,7 @@ def test_DimCoord_repr(self):

def test_AuxCoord_repr(self):
self.assertRepr(
self.lat, ("coord_api", "str_repr", "aux_nontime_repr.txt")
self.height, ("coord_api", "str_repr", "aux_nontime_repr.txt")
)

def test_DimCoord_str(self):
Expand All @@ -197,14 +199,16 @@ def test_DimCoord_str(self):

def test_AuxCoord_str(self):
self.assertString(
str(self.lat), ("coord_api", "str_repr", "aux_nontime_str.txt")
str(self.height), ("coord_api", "str_repr", "aux_nontime_str.txt")
)


@tests.skip_data
class TestCoord_ReprStr_time(tests.IrisTest):
def setUp(self):
self.time = iris.tests.stock.realistic_4d().coord("time")
cube = iris.tests.stock.realistic_4d()
self.time = cube.coord("time")
self.fp = cube.coord("forecast_period")

def test_DimCoord_repr(self):
self.assertRepr(
Expand All @@ -213,7 +217,7 @@ def test_DimCoord_repr(self):

def test_AuxCoord_repr(self):
self.assertRepr(
self.time, ("coord_api", "str_repr", "aux_time_repr.txt")
self.fp, ("coord_api", "str_repr", "aux_time_repr.txt")
)

def test_DimCoord_str(self):
Expand All @@ -223,7 +227,7 @@ def test_DimCoord_str(self):

def test_AuxCoord_str(self):
self.assertString(
str(self.time), ("coord_api", "str_repr", "aux_time_str.txt")
str(self.fp), ("coord_api", "str_repr", "aux_time_str.txt")
)


Expand Down
Loading