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

Catch nans before they cause crash in auto_ticks #636

Merged
merged 10 commits into from
Apr 13, 2021

Conversation

aaronayres35
Copy link
Contributor

fixes #529

Copy link
Contributor

@rahulporuri rahulporuri left a comment

Choose a reason for hiding this comment

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

LGTM. As usual, it'd be awesome if we could have a test for this regression but I tested this with the code from the issue - and this fix does prevent the plot window from crashing. Note that I still see the following on the command prompt when i click on the empty plot -

> edm run -e chaco-test-3.6-pyqt5 -- python .\chaco_bug.py
C:\Users\rporuri\work\github\ets\chaco\chaco\data_range_1d.py:276: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  if self._high_setting != val:
C:\Users\rporuri\work\github\ets\chaco\chaco\data_range_1d.py:283: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  if val == "auto":
C:\Users\rporuri\work\github\ets\chaco\chaco\data_range_1d.py:290: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  elif val == "track":

chaco/ticks.py Show resolved Hide resolved
@rahulporuri
Copy link
Contributor

rahulporuri commented Apr 9, 2021

#636 (review)

output of edm list for "chaco-test-3.6-pyqt5" edm environment
> edm list -e chaco-test-3.6-pyqt5
# Packages in environment 'chaco-test-3.6-pyqt5'
#  prefix: 'C:\Users\rporuri\.edm\envs\chaco-test-3.6-pyqt5'
alabaster                      0.7.10-1      enthought/free
appdirs                        1.4.3-1       enthought/free
babel                          2.6.0-2       enthought/free
bzip2_runtime                  1.0.8-1       enthought/free
certifi                        2020.12.5-1   enthought/free
chardet                        3.0.4-1       enthought/free
colorama                       0.3.7-1       enthought/free
cython                         0.29.13-1     enthought/free
distribute_remove              1.0.0-4       enthought/free
docutils                       0.16-2        enthought/free
enable                         5.1.0-1       enthought/free
enthought_sphinx_theme         0.6.2-1       enthought/free
flake8                         3.8.4-3       enthought/free
fonttools                      3.29.1-1      enthought/free
freetype                       2.10.0-7      enthought/free
harfbuzz                       2.6.4-4       enthought/free
icu4c                          50.2-2        enthought/free
idna                           3.1-1         enthought/free
imagesize                      0.7.1-1       enthought/free
importlib_metadata             2.0.0-1       enthought/free
importlib_resources            3.3.0-1       enthought/free
intel_runtime                  15.0.6.285-3  enthought/free
jinja2                         2.11.3-1      enthought/free
liblcms2                       2.11-1        enthought/free
libopenjpeg                    2.4.0-2       enthought/free
libpng                         1.6.37-1      enthought/free
libtiff                        4.0.10-9      enthought/free
markupsafe                     1.1.1-1       enthought/free
mccabe                         0.6.1-1       enthought/free
mkl                            2018.0.3-2    enthought/free
mock                           4.0.2-1       enthought/free
numpy                          1.17.4-1      enthought/free
packaging                      16.8-4        enthought/free
pandas                         0.25.3-3      enthought/free
pillow                         7.2.0-2       enthought/free
pin_intel_runtime_build        1.0-1         enthought/free
pip                            20.0.2-3      enthought/free
pycodestyle                    2.6.0-1       enthought/free
pyface                         7.3.0-1       enthought/free
pyflakes                       2.2.0-1       enthought/free
pygments                       2.2.0-1       enthought/free
pyparsing                      2.2.0-1       enthought/free
pyqt5                          5.14.2-4      enthought/free
pyqt5_sip                      12.7.2-1      enthought/free
python_dateutil                2.8.0-4       enthought/free
pytz                           2020.1-1      enthought/free
qt                             5.12.6-10     enthought/free
requests                       2.21.0-9      enthought/free
scipy                          1.4.1-2       enthought/free
setuptools                     41.6.0-4      enthought/free
six                            1.15.0-1      enthought/free
snowballstemmer                1.2.1-1       enthought/free
sphinx                         2.3.1-4       enthought/free
sphinxcontrib_applehelp        1.0.1-2       enthought/free
sphinxcontrib_devhelp          1.0.1-2       enthought/free
sphinxcontrib_htmlhelp         1.0.2-2       enthought/free
sphinxcontrib_jsmath           1.0.1-2       enthought/free
sphinxcontrib_qthelp           1.0.2-2       enthought/free
sphinxcontrib_serializinghtml  1.1.3-2       enthought/free
sphinxcontrib_websupport       1.2.4-1       enthought/free
swig                           3.0.11-2      enthought/free
traits                         6.2.0-1       enthought/free
traitsui                       7.1.1-4       enthought/free
urllib3                        1.26.3-3      enthought/free
zipp                           3.4.0-2       enthought/free
zlib_runtime                   1.2.11-1      enthought/free

@@ -170,6 +170,11 @@ def panning_mouse_move(self, event):
for direction, bound_name, index in direction_info:
if not self.constrain or self.constrain_direction == direction:
mapper = getattr(plot, direction + "_mapper")

# there is nowhere one could pan!
if mapper._null_screen_range or mapper._null_data_range:
Copy link
Contributor Author

@aaronayres35 aaronayres35 Apr 9, 2021

Choose a reason for hiding this comment

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

This is where we start getting weird things (at least for the PanTool), so I try to catch it early. Unfortunately this trait is not defined on the AbstactMapper base class, so I am uncertain if this is safe here. However, it is defined on all of the LinearMapper, LogMapper and PolarMapper classes.

Ultimately we are ending up in a situation where newlow = [-inf], newhigh = [-inf], domain_min = -inf, domain_high = inf. I do not understand why LinearMapper.map_data returns a singleton array of the low end of the range when one of the null traits is true, see:

if self._null_screen_range:
return array([self.range.low])
elif self._null_data_range:
return array([self.range.low])

This reminds me of #528 / #589
I feel like we should not change the types of the return value for a function for special cases, or if we do make it cleary documented that that is an edge case and handle it appropriately. What happens here is that array([-inf]) gets carried through and tried to be handled as if it were a normal float representing a a point in data space. But there is no range of data space at all in this scenario

Also there is this comment on AbstractMapper:

# FIXME: domain_limits is never used

but domain_limits very much appear to be being used here. (Not super important just pointing it out)

Overall I am unsure we want to use these traits here, but we definitely need some sort of edge nan, inf, None, etc case checking here and in other tools.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note, the change from the original commit of this PR seems to solve the reported issue, but I wanted to investigate up the chain to see where things were coming from and catch it earlier

@aaronayres35
Copy link
Contributor Author

LGTM. As usual, it'd be awesome if we could have a test for this regression but I tested this with the code from the issue - and this fix does prevent the plot window from crashing. Note that I still see the following on the command prompt when i click on the empty plot -

> edm run -e chaco-test-3.6-pyqt5 -- python .\chaco_bug.py
C:\Users\rporuri\work\github\ets\chaco\chaco\data_range_1d.py:276: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  if self._high_setting != val:
C:\Users\rporuri\work\github\ets\chaco\chaco\data_range_1d.py:283: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  if val == "auto":
C:\Users\rporuri\work\github\ets\chaco\chaco\data_range_1d.py:290: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  elif val == "track":

Ah this is from a [nan] getting passed around. Currently investigating this / the source

@aaronayres35
Copy link
Contributor Author

aaronayres35 commented Apr 9, 2021

Okay at this point I think I am probably trying to do too much here...
I am happy to revert the latest changes and go back to just the fix made in the original commit if need be.

Note in my latest commit, I change LinearMapper.map_data to return just self.range.low not array([self.range.low]) in each of the null cases. With these changes, the test suite still passes for me locally, the original issue is solved (there is no crash), and there are no longer warnings. It also makes sense to me intuitively that map_data has a consistent return type.

However there is a separate map_data_array method which simply calls map_data on an array of screen_vals. In this case, it would actually make sense to have the null cases return singleton arrays as that is what is consistent with what is returned in the non null case.
It feels like IMO map_data should always return a float, and map_data_array always return an array...
See various issues hitting this problem or something like it: #255, #272, #289, #550, (possibly) #542!
Generally speaking we need to pin down these return values in some consistent way to avoid these sorts of issues.
@rahulporuri tagging you here to make sure I don't forget to bring this up for discussion on Monday

This is getting a little orthogonal to what this PR aims to address. As I am writing this I am thinking I will actually undo these changes now and perhaps make them in a follow up PR... 🤔

@aaronayres35 aaronayres35 changed the title [WIP] catch nans before they cause crash Catch nans before they cause crash in auto_ticks Apr 9, 2021
@aaronayres35 aaronayres35 requested a review from rahulporuri April 9, 2021 20:09
Copy link
Contributor

@rahulporuri rahulporuri left a comment

Choose a reason for hiding this comment

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

Still LGTM with a couple of minor comments

chaco/ticks.py Outdated Show resolved Hide resolved
@@ -242,6 +243,10 @@ def auto_ticks(
An array of tick mark locations. The first and last tick entries are the
Copy link
Contributor

Choose a reason for hiding this comment

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

nit - this is related but also tangential. the docstring says that this method returns an array - i am not sure if that means that we should strictly return an empty array.

this goes back to the general issue you mentioned of checking and enforcing return types across chaco. i think this can be an umbrella issue where we accumulate and fix issues that we discover.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I saw this too, but then at the end of the method it appears to return just a list so an empty list is consistent.
Perhaps I should update the docstring instead? Or maybe they both really should be arrays?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I am going to push this through for now, but this can be re-evaluated as part of #662

chaco/tests/test_plot.py Outdated Show resolved Hide resolved
Co-authored-by: Poruri Sai Rahul <[email protected]>
@aaronayres35 aaronayres35 merged commit 43a7486 into master Apr 13, 2021
@aaronayres35 aaronayres35 deleted the dont-crash-empty-plot branch April 13, 2021 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Empty chaco plot crashes when clicked on
2 participants