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 23 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
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
* UserWarning displayed when empty atomgroup is given
Copy link
Member

Choose a reason for hiding this comment

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

you also raise a ValueError – please document

to DensityAnalysis (Issue #3055)
* 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
45 changes: 33 additions & 12 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 @@ -394,8 +395,18 @@ def _prepare(self):
logger.warning(msg)
# Generate a copy of smin/smax from coords to later check if the
# defined box might be too small for the selection
smin = np.min(coord, axis=0)
smax = np.max(coord, axis=0)
try:
IAlibay marked this conversation as resolved.
Show resolved Hide resolved
smin = np.min(coord, axis=0)
smax = np.max(coord, axis=0)
except ValueError as err:
msg = ("No atoms in AtomGroup at input time frame. "
"Grid for density could not be automatically"
" generated. A user defined grid was found"
" and is being used as the density grid")
Copy link
Member

@IAlibay IAlibay Jan 31, 2021

Choose a reason for hiding this comment

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

My example message for the other message can't be directly copied here, the context is different. It's incorrect to say that you were attempting to "automatically generate" the grid since it was manually set by the user. Please amend this to better fit the context of the warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

            `msg = ("No atoms in AtomGroup at input time frame. "`
             `           "Density will contain a grid constructed with`
             `           "user defined values ")`

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 it's worth considering why we are emitting the warning. Is it reasonable to be warning folks that we're following expected operation (i.e. they asked to use a user defined grid, is it informative to have us tell them we're doing this)?

Rather than thinking about what the code is doing next, it's best to phrase the warning in terms of the consequences of not having any atoms in the atomgroup. What does this mean in terms of the user grid? How does it impact us checking if the user grid is large enough to accommodate the selected atoms?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, that would mean that the user should have made sure that they enter a grid large enough to accomodate atoms in the next frames.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

        `msg = ("No atoms in AtomGroup at input time frame. "`
         `           "Please ensure grid values are in a large range to`
         `           "accommodate all selected atoms ")`

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 sure "large range" is the right set of words here. Maybe something like "This be may be intended; please ensure that your grid selection covers the atomic positions you wish to capture."?

warnings.warn(msg)
logger.warning(msg)
smin = self._gridcenter
smax = self._gridcenter
Copy link
Member

Choose a reason for hiding this comment

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

Why are smin and smax assigned the grid center coordinates?

Please explain the logic here. What consequences does it have downstream? Does the code fail further downstream if the user has not supplied a proper grid selection?

Copy link
Contributor Author

@aditya-kamath aditya-kamath Feb 15, 2021

Choose a reason for hiding this comment

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

Oh, that was done because _set_user_grid wanted a preassigned range from the coordinates of AtomGroup in selection. But there is no AtomGroup in this case, so I initialize a zero range grid at the grid center and take the range from user-defined grid values.

Copy link
Member

Choose a reason for hiding this comment

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

Could you do

# need to be set for _set_user_grid() but will be
# overriden by user-defined values
smin = smax = None

to make clearer what's happening? If None is not possible (even though that would be the best value to indicate that we are not using these values), just setting to 0 or [0, 0, 0] would still be cleaner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It will trigger this warning in set_user_grid if I do that.
if any(smin < umin) or any(smax > umax):
msg = ("Atom selection does not fit grid --- "
"you may want to define a larger box")
warnings.warn(msg)
logger.warning(msg)

The check is meant for regular situations to compare the atom selection with user-defined grid.

In the case of no atoms in selection, I just want it to pass through. So setting smin and smax at the gridcenter will always pass the comparison with a user defined box.

Copy link
Member

Choose a reason for hiding this comment

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

Document in a comment, please.

orbeckst marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

Add a comment explaining the hacky assignment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! :)

# Overwrite smin/smax with user defined values
smin, smax = self._set_user_grid(self._gridcenter, self._xdim,
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
self._ydim, self._zdim, smin,
Expand All @@ -408,8 +419,16 @@ def _prepare(self):
# ideal solution would use images: implement 'looking across the
# periodic boundaries' but that gets complicated when the box
# rotates due to RMS fitting.
smin = np.min(coord, axis=0) - self._padding
smax = np.max(coord, axis=0) + self._padding
try:
smin = np.min(coord, axis=0) - self._padding
smax = np.max(coord, axis=0) + self._padding
except ValueError as err:
errmsg = ("No atoms in AtomGroup at input time frame. "
orbeckst marked this conversation as resolved.
Show resolved Hide resolved
"Grid for density could not be automatically"
" generated. If this is expected, a user"
" defined grid will need to be "
"provided instead.")
raise ValueError(errmsg) from err
BINS = fixedwidth_bins(self._delta, smin, smax)
arange = np.transpose(np.vstack((BINS['min'], BINS['max'])))
bins = BINS['Nbins']
Expand Down Expand Up @@ -487,8 +506,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 +676,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 +714,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 +783,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 +835,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 +848,4 @@ def __repr__(self):
grid_type = 'density'
else:
grid_type = 'histogram'
return '<Density ' + grid_type + ' with ' + str(self.grid.shape) + ' bins>'
return f"<Density {grid_type} with {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.

Did you test this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, perhaps I'll have to think about a test for this. I'd leave it alone, but it looks like autoPEP8 felt that the line was very long!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @orbeckst, I've removed this change. However, a check seems to be failing and I'm not sure if it is a concern.(?)

Copy link
Member

Choose a reason for hiding this comment

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

The test failure was down to a non-related RDKIT test (it's a flaky test that fails sometimes on CI), I've restarted the jobs, hopefully they should pass this time.

22 changes: 21 additions & 1 deletion testsuite/MDAnalysisTests/analysis/test_density.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ class DensityParameters(object):
topology = TPR
trajectory = XTC
delta = 2.0
selections = {'static': "name OW",
selections = {'none': "resname None",
'static': "name OW",
'dynamic': "name OW and around 4 (protein and resid 1-10)",
'solute': "protein and not name H*",
}
Expand Down Expand Up @@ -254,6 +255,25 @@ def test_ValueError_userdefn_xdim_type(self, universe):
delta=self.delta, xdim="MDAnalysis", ydim=10.0, zdim=10.0,
gridcenter=self.gridcenters['static_defined']).run(step=5)

def test_warn_noatomgroup(self, universe):
regex = ("No atoms in AtomGroup at input time frame. "
"Grid for density could not be automatically"
" generated. A user defined grid was found"
" and is being used as the density grid")
with pytest.warns(UserWarning, match=regex):
aditya-kamath marked this conversation as resolved.
Show resolved Hide resolved
D = density.DensityAnalysis(
universe.select_atoms(self.selections['none']),
delta=self.delta, xdim=1.0, ydim=2.0, zdim=2.0, padding=0.0,
gridcenter=self.gridcenters['static_defined']).run(step=5)

def test_ValueError_noatomgroup(self,universe):
with pytest.raises(ValueError, match="No atoms in AtomGroup at input time frame. "
"Grid for density could not be automatically"
" generated. If this is expected, a user"
" defined grid will need to be "
"provided instead."):
D = density.DensityAnalysis(
universe.select_atoms(self.selections['none'])).run(step=5)

class TestGridImport(object):

Expand Down