Skip to content
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

BUG: Fix scipy incompatibility with cleanup to poly1d #8788

Merged
merged 2 commits into from
Mar 21, 2017

Conversation

eric-wieser
Copy link
Member

@eric-wieser eric-wieser commented Mar 16, 2017

Scipy was relying on some of the broken things that #8762 fixed

@charris
Copy link
Member

charris commented Mar 16, 2017

I think this should work. Might make a note in __init__ that assigning to self._coeffs creates and initializes self.coeffs.

@charris
Copy link
Member

charris commented Mar 16, 2017

Maybe the easiest thing to do all round is to make the attributes writable.

@eric-wieser
Copy link
Member Author

Might make a note in init that assigning to self._coeffs creates and initializes self.coeffs

I'd argue not - what _coeffs does is an implementation detail.

Maybe the easiest thing to do all round is to make the attributes writable.

If you measure easiness by lines changed, that's actually less easy!

@charris
Copy link
Member

charris commented Mar 16, 2017

In the original poly1d it would only have required removing {get,set}attr plus the equality of the attribute aliases.

@eric-wieser
Copy link
Member Author

Yes, that's true. Although making order writeable doesn't make any sense, so that should remain a property as it is in the new poly1d

@seberg
Copy link
Member

seberg commented Mar 16, 2017

If there is an easy way to fix the "broken scipy", we could also put a deprecation warning somewhere probably.

@eric-wieser
Copy link
Member Author

@seberg: I've already submitted the easy fix to "broken scipy" in scipy/scipy#7184

@charris
Copy link
Member

charris commented Mar 16, 2017

I'd argue not - what _coeffs does is an implementation detail.

Which is why it needs a comment in the code...

@eric-wieser
Copy link
Member Author

By that, I mean its an implementation detail of _coeffs not of poly1d. Perhaps the comments I did add aren't descriptive enough, but I think they're in the right place

@charris
Copy link
Member

charris commented Mar 16, 2017

The problem is that one needs to read through a lot of garbage code and implementation details to figure out how coeffs gets into the dictionary. If that wasn't a problem in scipy no one would care, but given the implementation of orthopoly1d and the problems it has caused, it would be good for it to be clear how it got fixed for future reference.

@charris
Copy link
Member

charris commented Mar 17, 2017

So at line 1098, something like

# This also initializes self.coeffs for backwards compatibility
# with scipy.special.orthogonal.orthopoly1d.
self._coeffs = c_or_r

@mhvk
Copy link
Contributor

mhvk commented Mar 18, 2017

This is really ugly, and now you end up with a coeff attribute that can actually be modified, while the problem is that scipy is relying on self.__dict__ containing 'coeff' and modifying that. Since that is so, how about (inspired by lazyproperty):

def get_coeffs(self):
    return self.__dict__['coeffs']

def set_coeff(self, coeffs):
    if 'coeffs' in self.__dict__:
        raise AttributeError(...)
    self.__dict__['coeffs'] = coeffs

coeffs = property(get_coeffs, set_coeffs)

(note that the property itself gets store in the class's __dict__ and not in an instance's __dict__). Obviously, it would then still need a note that it should become a regular property eventually, and you need to change __init__ to set self.coeff rather than self._coeff.

@charris
Copy link
Member

charris commented Mar 19, 2017

@mhvk I like that. Bit tricksy -- I would not have understood it without the explanation -- but definitely cleaner.

@eric-wieser
Copy link
Member Author

eric-wieser commented Mar 19, 2017

@mhvk: Huh, wasn't away that while __dict__ takes priority over property.fget, property.fset takes priority over __dict__.

I still think that we want poly1d to use _coeffs internally, because that's what we want the api to look like. So perhaps:

# our internal _coeffs property need to be backed by __dict__['coeffs'] for scipy to work
@property
def _coeffs(self):
    return self.__dict__['coeffs']
@_coeffs.setter
def _coeffs(self, coeffs):
    self.__dict__['coeffs'] = coeffs

# expose a readonly copy of the coefficients publicly
# note that this does not return a copy for .coeffs, as the above workaround overwrites
# this getter. It does not, however, affect the behaviour for .c, .coef, or .coefficients
@property
def coeffs(self):
    return self._coeffs.copy()
c = coef = coefficients = coeffs

@charris
Copy link
Member

charris commented Mar 21, 2017

Both the proposed new versions work, although only if poly1d is derived from object, as it is. @eric-wieser I'll leave the choice up to you, but we need to get this in.

Scipy needs `.__dict__['coeffs']` to work, so we can't call the member _coeffs
`poly.coeffs = 1` has always failed with a strong exception guarantee.
However, `poly.coeffs += 1` would both change the state and fail.

Now both fail without affecting the value.
@eric-wieser
Copy link
Member Author

@charris: Ok, updated with my suggestion.

Is the second commit reasonable, given that it makes .coef and .coefficients copies of data, but .coeffs is the original (because making it a copy is not possible)?

@charris
Copy link
Member

charris commented Mar 21, 2017

I think it is worth a shot. We will see if anyone complains in the 1.13.0, aka FU, release.

@charris
Copy link
Member

charris commented Mar 21, 2017

I think that p.coeffs is still a copy. Apparently __getattribute__ assigns priority in way that makes this all work. See https://docs.python.org/2/howto/descriptor.html.

@charris charris merged commit 0dd7ca5 into numpy:master Mar 21, 2017
@charris
Copy link
Member

charris commented Mar 21, 2017

Thanks Eric.

@charris
Copy link
Member

charris commented Mar 21, 2017

If you look at the class dict, poly1d.__dict__, I think you will see that c, coef, coefficients, coeffs, are all the same property, and properties seem to have precedence.

@eric-wieser
Copy link
Member Author

eric-wieser commented Mar 21, 2017

@charris: I really don't think that is the case, else @mhvk's remark about this being derived from @lazyproperty doesn't apply - that relies on being able to overwrite the getter with the value.

And if it is the case, then I need to correct the comment I made in the code saying that it wasn't the case ...

@eric-wieser
Copy link
Member Author

Well, it turns out you're right! I guess I need to rethink my understanding of the property model, and make a PR to fix this PR fixing that PR...

@@ -1059,6 +1059,16 @@ def roots(self):
""" The roots of the polynomial, where self(x) == 0 """
return roots(self._coeffs)

# our internal _coeffs property need to be backed by __dict__['coeffs'] for
# scipy to work correctly. Note that as a result, the getter for .coeffs
# does not run unless accessed through one of its aliases.
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 comment is false

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry to have been out of the loop, but indeed the second sentence is incorrect.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in #8807

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.

None yet

4 participants