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

Emit warnings for empty Ag and user grid in DensityAnalysis #3091

Merged
merged 36 commits into from
Mar 11, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fb491fd
Update topologyattrs.py
aditya-kamath Jan 1, 2021
7fec795
Changed-authors,changelog
aditya-kamath Jan 2, 2021
c965b77
including Tests for atommethods Quickfix
aditya-kamath Jan 2, 2021
d2d54fc
minor correction to test_topologyattrs.py
aditya-kamath Jan 2, 2021
2b1c50d
update - test_topologyattrs.py
aditya-kamath Jan 2, 2021
c00e3f3
Fixing PEP8 issues
aditya-kamath Jan 3, 2021
65e0c45
add name to CHANGELOG
aditya-kamath Jan 3, 2021
14acdfb
Fixing PEP8 indent error
aditya-kamath Jan 4, 2021
d0f5263
Attempt to resolve empty selection
aditya-kamath Jan 6, 2021
4257b45
Update CHANGELOG
aditya-kamath Jan 6, 2021
e4fc9aa
Merge branch 'develop' into develop
aditya-kamath Jan 7, 2021
8232192
Merge remote-tracking branch 'upstream/develop' into develop
aditya-kamath Jan 7, 2021
52fd1f3
reducing abstraction in density.py
aditya-kamath Jan 9, 2021
eff2854
Merge branch 'develop' of https://github.com/aditya-kamath/mdanalysis…
aditya-kamath Jan 9, 2021
ec9c1f3
Revert "reducing abstraction in density.py"
aditya-kamath Jan 9, 2021
f4319ee
Revert "Revert "reducing abstraction in density.py""
aditya-kamath Jan 9, 2021
0423a48
New approach to solve empty atomgroup in densityanalysis
aditya-kamath Jan 17, 2021
c7caaef
Minor change to \ break line
aditya-kamath Jan 17, 2021
b861b34
Fixing autopep8 errors and review changes
aditya-kamath Jan 18, 2021
d6d23d6
Changed to raise error when no user-defined grid present
aditya-kamath Jan 19, 2021
33c94fa
Minor corrections and test added for no atomgroup fail.
aditya-kamath Jan 19, 2021
dab9ad9
Minor PEP8 corrections
aditya-kamath Jan 19, 2021
7c22dc1
User friendly messages
aditya-kamath Jan 20, 2021
dc834c8
Docstring+warning message change
aditya-kamath Feb 3, 2021
4e8008e
PEP8 Fixs
aditya-kamath Feb 3, 2021
a83c27b
fix PEP8 errors
aditya-kamath Feb 7, 2021
6675097
Merge branch 'develop' into develop
aditya-kamath Feb 8, 2021
d22916c
Update Docstring
aditya-kamath Feb 11, 2021
f3af280
Revert cosmetic changes
aditya-kamath Feb 14, 2021
245a859
fix PEP8 issues
aditya-kamath Feb 15, 2021
9c127f5
Update Docstring +changelog
aditya-kamath Feb 15, 2021
e652669
Fixing PEP8 issues
aditya-kamath Feb 17, 2021
e3990ee
orbeckst requested changes
aditya-kamath Feb 20, 2021
5a2b371
Adding a new method to docs
aditya-kamath Feb 21, 2021
10db351
updates to fix doc fail
aditya-kamath Feb 26, 2021
9341947
revert change to line not covered in tests
aditya-kamath Mar 3, 2021
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
1 change: 1 addition & 0 deletions package/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ Chronological list of authors
- Mieczyslaw Torchala
- Haochuan Chen
- Karthikeyan Singaravelan

aditya-kamath marked this conversation as resolved.
Show resolved Hide resolved
2021
- Aditya Kamath

Expand Down
2 changes: 2 additions & 0 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ The rules for this file:
* 2.0.0

Fixes

aditya-kamath marked this conversation as resolved.
Show resolved Hide resolved
* UserWarning displayed when empty atomgroup is given to DensityAnalysis (Issue #3055)
aditya-kamath marked this conversation as resolved.
Show resolved Hide resolved
* atommethods (_get_prev/next_residues_by_resid) returns empty residue group
when given empty residue group (Issue #3089)
* MOL2 files without bonds can now be read and written (Issue #3057)
Expand Down
35 changes: 27 additions & 8 deletions package/MDAnalysis/analysis/density.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ class DensityAnalysis(AnalysisBase):
.. versionchanged:: 2.0.0
:func:`_set_user_grid` is now a method of :class:`DensityAnalysis`.
"""

IAlibay marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, atomgroup, delta=1.0,
metadata=None, padding=2.0,
gridcenter=None,
Expand All @@ -383,7 +384,18 @@ def __init__(self, atomgroup, delta=1.0,
self._ydim = ydim
self._zdim = zdim

def _checkinput(self):
if len(self._atomgroup) == 0:
aditya-kamath marked this conversation as resolved.
Show resolved Hide resolved
msg = ("No atoms in selection over whole trajectory")
aditya-kamath marked this conversation as resolved.
Show resolved Hide resolved
warnings.warn(msg)
logger.warning(msg)
return 1
else:
return 0

def _prepare(self):
if _checkinput(self) == 1:
return
Copy link
Member

Choose a reason for hiding this comment

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

Couple of points to consider here;

  1. Is this the right place to be putting this? Given the context of the original issue, and the need to make it so that the user defined grid still works even if there are no atoms in the frame, would placing this elsewhere be more appropriate?

  2. Doing a return post-warning is a non-safe way to handle things. It's worth considering what the difference between a "warning" and an "error" is. If you're issuing a warning, you are telling users that non-expected behaviour is likely to occur (and why) but that the code will carry on. If you raise an error, you are telling users that something wrong/unexpected has occurred and you are aborting further code execution. In this case, if you are attempting to abort execution (as you are via return), then an error (of appropriate type) should be raised instead.

  3. I think the way this is written probably needs to be changed, but it's worth mentioning anyways. Doing a return equality check on method return that essentially returns a Boolean is a non-pythonic way to handle things. If you're checking for True/False, the best way to do things is to have the method you're calling return True/False and then just do:

if method(input):
    true_condition
else:
    false_condition

coord = self._atomgroup.positions
if self._gridcenter is not None:
# Issue 2372: padding is ignored, defaults to 2.0 therefore warn
Expand Down Expand Up @@ -424,6 +436,8 @@ def _prepare(self):
self.density = None

def _single_frame(self):
if _checkinput(self) == 1:
Copy link
Member

Choose a reason for hiding this comment

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

Reading your replies to some of the other comments, I think it might be worth you investing a bit more time in understanding how the AnalysisBase class works, particularly the order in which each of these class methods get called.

Have a look at MDAnalysis.analysis.base with the aim to conceptualise what _single_frame and _conclude mean in terms of analyzing a Molecular Dynamics trajectory. Once you've done this, it's then worth going back to the original issue and trying to understand where the edge case is causing the code to fail and why.

Unless I'm missing something (which is always very much a possibility), doing these checks here may not be appropriate?

return
h, _ = np.histogramdd(self._atomgroup.positions,
bins=self._bins, range=self._arange,
normed=False)
Expand All @@ -434,6 +448,8 @@ def _single_frame(self):
self._grid += h

def _conclude(self):
if _checkinput(self) == 1:
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned above in _single_frame.

return
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if three duplications of the same code block is enough to justify abstraction to a single i.e., function. Maybe borderline if the return has to remain outside of the abstraction I suppose.

On a more basic level, just asking--the code will still produce the original failure/exception (is that what it was doing?) and this will simply add a warning to clarify?

Copy link
Contributor Author

@aditya-kamath aditya-kamath Jan 7, 2021

Choose a reason for hiding this comment

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

This might sound very primitive but my logic was to return out of the function and warn the user of no atoms until atomgroup gets updated with atleast one atom. In the Issue, the user wanted to avoid the program crashing and would provide a dummy atom to keep the density frames generating. I tried to fix this by preventing a crash at the initializing, frame generation and conclusion. Perhaps a more efficient way is to make a function to check for no atoms and return.

Copy link
Contributor Author

@aditya-kamath aditya-kamath Jan 7, 2021

Choose a reason for hiding this comment

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

Again, my understanding of the DensityAnalysis function process may not be very accurate so I apologize for any incorrect arguments I make :P

# average:
self._grid /= float(self.n_frames)
density = Density(grid=self._grid, edges=self._edges,
Expand Down Expand Up @@ -487,8 +503,8 @@ def _set_user_grid(gridcenter, xdim, ydim, zdim, smin, smax):
raise ValueError("xdim, ydim, and zdim must be numbers") from err

# Set min/max by shifting by half the edge length of each dimension
umin = gridcenter - xyzdim/2
umax = gridcenter + xyzdim/2
umin = gridcenter - xyzdim / 2
umax = gridcenter + xyzdim / 2

# Here we test if coords of selection fall outside of the defined grid
# if this happens, we warn users they may want to resize their grids
Expand Down Expand Up @@ -657,7 +673,7 @@ def __init__(self, *args, **kwargs):

parameters = kwargs.pop('parameters', {})
if (len(args) > 0 and isinstance(args[0], str) or
isinstance(kwargs.get('grid', None), str)):
isinstance(kwargs.get('grid', None), str)):
Copy link
Member

Choose a reason for hiding this comment

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

I'll be honest, this flake8 issue makes no sense to me, visual indentation should start at len not at its argument.

Either way it's a minor thing so I'll let the other reviewers make a decision if they want here, but I'm willing to let it go.

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 ran this file through autopep8.

Copy link
Member

Choose a reason for hiding this comment

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

Looks bizzare to me. I would revert this.

# try to be smart: when reading from a file then it is likely that
# this is a density
parameters.setdefault('isDensity', True)
Expand Down Expand Up @@ -695,7 +711,8 @@ def _check_set_unit(self, u):
# all this unit crap should be a class...
try:
for unit_type, value in u.items():
if value is None: # check here, too iffy to use dictionary[None]=None
# check here, too iffy to use dictionary[None]=None
if value is None:
self.units[unit_type] = None
continue
try:
Expand Down Expand Up @@ -763,7 +780,8 @@ def convert_length(self, unit='Angstrom'):
"""
if unit == self.units['length']:
return
cvnfact = units.get_conversion_factor('length', self.units['length'], unit)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@IAlibay , Do you want me to revert these changes? I feel like these changes might cause issues..

Copy link
Member

Choose a reason for hiding this comment

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

I'm not really following what happened here. Why do you think these changes are causing issues?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are a few changes which were made by autoPEP8 and not a part of fixing the error. The only failing check of CodeCov is because these changes are not covered by tests..

Copy link
Member

Choose a reason for hiding this comment

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

It's up to you, codecov is complaining because it's gone from one line of untested code to two. The best solution here would be to go ahead and add a test for this, but otherwise I think we can live with just a style change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! Thanks @IAlibay :)

Copy link
Member

Choose a reason for hiding this comment

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

If there are no tests for these altered lines of code then we should have them.

I am tending towards: If you touch code it's your responsibility to test it.

Even if it looks as if it's just cosmetic, it might not be. Bugs can be subtle. Either leave it or test it.

cvnfact = units.get_conversion_factor(
'length', self.units['length'], unit)
self.edges = [x * cvnfact for x in self.edges]
self.units['length'] = unit
self._update() # needed to recalculate midpoints and origin
Expand Down Expand Up @@ -814,8 +832,8 @@ def convert_density(self, unit='Angstrom'):
if unit == self.units['density']:
return
try:
self.grid *= units.get_conversion_factor('density',
self.units['density'], unit)
self.grid *= units.get_conversion_factor(
'density', self.units['density'], unit)
except KeyError:
errmsg = (f"The name of the unit ({unit} supplied) must be one "
f"of:\n{units.conversion_factor['density'].keys()}")
Expand All @@ -827,4 +845,5 @@ def __repr__(self):
grid_type = 'density'
else:
grid_type = 'histogram'
return '<Density ' + grid_type + ' with ' + str(self.grid.shape) + ' bins>'
return '<Density ' + grid_type + ' with ' + \
str(self.grid.shape) + ' bins>'
Copy link
Member

Choose a reason for hiding this comment

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

Breaking lines with \ ends up introducing a PEP8 issue, it also ends up making the code harder to read. It's always worth noting that PEP8 is a guideline rather than a specific set of rules, code readability is always the end goal.

That being said, there is an easy way to make this readable and also less than 79 characters in one line by using f-strings:

Suggested change
return '<Density ' + grid_type + ' with ' + \
str(self.grid.shape) + ' bins>'
return f"<Density {grid_type} with {self.grid.shape} bins>"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for this! Noted