Skip to content

MAINT: Legacy mode specified as string, fix all-zeros legacy bug #10030

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 18, 2017

Conversation

ahaldane
Copy link
Member

@ahaldane ahaldane commented Nov 15, 2017

Fixes #10020 and #10026

The first commit makes the legacy option specify version number, as np.set_printoptions(legacy='1.13').

The second commit fixes a bug where the legacy mode spacing for float arrays didn't get activated for arrays which didn't have any non-zero finite values.

@ahaldane ahaldane added this to the 1.14.0 release milestone Nov 15, 2017
@ahaldane ahaldane changed the title Legacy mode fixes MAINT: Legacy mode fixes Nov 15, 2017
@@ -87,6 +87,9 @@ def _make_options_dict(precision=None, threshold=None, edgeitems=None,
if sign not in [None, '-', '+', ' ']:
raise ValueError("sign option must be one of ' ', '+', or '-'")

if legacy not in [None, '', '1.13']:
raise ValueError("legacy option can currently only be '1.13' or ''")
Copy link
Member

Choose a reason for hiding this comment

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

I think this should warn, not error - otherwise, code which does not want 1.15 behavior (passing legacy='1.14'), will crash when run on 1.13.

Copy link
Member

Choose a reason for hiding this comment

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

Agree.

@eric-wieser
Copy link
Member

Unsure if we should be using strings or tuples here. Tuples would work better for version comparison, but perhaps that's not something we want to encourage.

@lesteve
Copy link
Contributor

lesteve commented Nov 16, 2017

Unsure if we should be using strings or tuples here. Tuples would work better for version comparison, but perhaps that's not something we want to encourage.

My 2c: use strings and distutils.version.LooseVersion. As long as you are comparing released versions, i.e. X.Y.Z (and not things like 1.14.dev0 or 1.14.1b1) distutils.LooseVersion is good enough. You can also LooseVersion('1.1.0').version to get a list of integers corresponding to the major/minor/micro versions.

@charris
Copy link
Member

charris commented Nov 16, 2017

@lesteve We don't need to worry about actual versions, just need a way to specify which printing style we want. I'd go for a string.

@ahaldane
Copy link
Member Author

ahaldane commented Nov 16, 2017

You're right to warn, instead of raise.

Fixed.

@ahaldane ahaldane changed the title MAINT: Legacy mode fixes MAINT: Legacy mode specified as string, fix all-zeros legacy bug Nov 16, 2017
@@ -87,6 +87,10 @@ def _make_options_dict(precision=None, threshold=None, edgeitems=None,
if sign not in [None, '-', '+', ' ']:
raise ValueError("sign option must be one of ' ', '+', or '-'")

if legacy not in [None, '', '1.13']:
warnings.warn("legacy printing option can currently only be '1.13' or "
"''", stacklevel=3)
Copy link
Member

@eric-wieser eric-wieser Nov 17, 2017

Choose a reason for hiding this comment

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

Empty string seems weird to me. Can we use False instead? I'm guessing None is a problem because of needing a default value in set_printoptions?

valid = not_equal(data, 0) & ~special
non_zero = data[valid]
non_special = data[~special]
non_zero = non_special[non_special != 0]
Copy link
Member

Choose a reason for hiding this comment

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

Some comments might be nice here explaining why each of these are needed.

Copy link
Member

Choose a reason for hiding this comment

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

A 'not' prefix has more force than 'non'. Not worth worrying about here, just a comment.

Copy link
Member

Choose a reason for hiding this comment

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

I would reserve a not_ prefix for a boolean type, so non_zero = data[not_zero]

@ahaldane
Copy link
Member Author

Thanks for the suggestions. I made legacy=False disable legacy mode, and I did a lot of cleanup in the FloatingFormat code in a new commit.

@@ -841,7 +834,7 @@ def __init__(self, *args, **kwargs):
def format_float_scientific(x, precision=None, unique=True, trim='k',
sign=False, pad_left=None, exp_digits=None):
"""
Format a floating-point scalar as a string in scientific notation.
Format a floating-point scalar as a decimal string in scientific notation.
Copy link
Member Author

Choose a reason for hiding this comment

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

this is an unrelated small doc tweak I am piggybacking on this PR

If set to the string `'1.13'` enables 1.13 legacy printing mode. This
approximates numpy 1.13 print output by including a space in the sign
position of floats and different behavior for 0d arrays. If set to
`False`, disables legacy mode.

Copy link
Member

Choose a reason for hiding this comment

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

Needs .. versionadded::. If folks want to use this they will need to check the NumPy version.

Copy link
Member

Choose a reason for hiding this comment

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

Might want a comment here explaining that invalid versions will be ignored for forward-compatibility too.

Copy link
Member

Choose a reason for hiding this comment

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

Don't forget to add this to both docstrings

finite_vals = data[isfinite(data)]

# choose exponential mode based on the non-zero finite values:
abs_non_zero = absolute(finite_vals[finite_vals != 0])
Copy link
Member

Choose a reason for hiding this comment

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

Way clearer now :)

Copy link
Member

Choose a reason for hiding this comment

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

@eric-wieser If you are good with this, I'll make the fix and merge.

@@ -701,40 +707,32 @@ def __init__(self, data, precision, floatmode, suppress_small, sign=False, **kwa
self.fillFormat(data)

def fillFormat(self, data):
with errstate(all='ignore'):
Copy link
Member

Choose a reason for hiding this comment

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

Why did this line go away?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh I think I need to keep it. It must be because the division operation can overflow, eg 1.0/np.float64(2.2250738585072e-310).

Copy link
Member

Choose a reason for hiding this comment

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

Oops, I just made the other fixes online.

Copy link
Member

Choose a reason for hiding this comment

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

You may need to update from origin.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe push it to a deeper scope this time

@eric-wieser
Copy link
Member

A little puzzled by the errstate(all='ignore') vanishing, but all looks good to me modulo the doc fix you spotted.

Copy link
Member

@eric-wieser eric-wieser left a comment

Choose a reason for hiding this comment

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

LGTM. Might be nice to squash @charris' commits together

def test_float_overflow_nowarn(self):
# make sure internal computations in FloatingFormat don't
# warn about overflow
repr(array([1.0, 1e-310], dtype='f8'))
Copy link
Member

Choose a reason for hiding this comment

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

Nice!

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm but actually tests just finished running on my system and I don't think this triggers the problem because the min_val < 0.0001 short-circtuits out the division clause anytime overflow could occur.

I think overflow may only be posible for float16 types under certain circumstances. np.array([1e4, 0.1], dtype='f2') works.

Note that the `legacy` keyword was added in NumPy 1.14.0.

[ci skip]
@ahaldane ahaldane force-pushed the legacy_mode_fixes branch 3 times, most recently from 3b3cde8 to 85dabca Compare November 18, 2017 03:25
@charris
Copy link
Member

charris commented Nov 18, 2017

The errstate block seems to have originated back around 2007, although not implemented that way as errstate was a later addition.

@charris charris merged commit e5f4ac0 into numpy:master Nov 18, 2017
@charris
Copy link
Member

charris commented Nov 18, 2017

Thanks Allan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

np.set_printoptions(legacy=True) is not future proof
4 participants