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

distopia 0.3.0 compatibility changes #4734

Open
wants to merge 23 commits into
base: develop
Choose a base branch
from

Conversation

hmacdope
Copy link
Member

@hmacdope hmacdope commented Oct 15, 2024

Test implementation of distopia-0.3.0 distances API.

Changes made in this Pull Request:

  • Adds support for distopia 0.3.0 API

PR Checklist

  • Tests?
  • Docs?
  • CHANGELOG updated?
  • Issue raised/referenced?

Developers certificate of origin


📚 Documentation preview 📚: https://mdanalysis--4734.org.readthedocs.build/en/4734/

@pep8speaks
Copy link

pep8speaks commented Oct 15, 2024

Hello @hmacdope! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

Line 87:80: E501 line too long (86 > 79 characters)
Line 93:80: E501 line too long (103 > 79 characters)
Line 95:80: E501 line too long (83 > 79 characters)
Line 99:80: E501 line too long (103 > 79 characters)
Line 102:80: E501 line too long (83 > 79 characters)
Line 106:80: E501 line too long (107 > 79 characters)
Line 108:80: E501 line too long (87 > 79 characters)
Line 110:1: E302 expected 2 blank lines, found 1
Line 111:80: E501 line too long (124 > 79 characters)
Line 113:80: E501 line too long (95 > 79 characters)
Line 115:1: E302 expected 2 blank lines, found 1
Line 116:80: E501 line too long (124 > 79 characters)
Line 118:80: E501 line too long (95 > 79 characters)
Line 120:1: E302 expected 2 blank lines, found 1
Line 127:80: E501 line too long (82 > 79 characters)
Line 129:80: E501 line too long (82 > 79 characters)
Line 131:1: E302 expected 2 blank lines, found 1
Line 132:80: E501 line too long (82 > 79 characters)
Line 134:80: E501 line too long (82 > 79 characters)
Line 136:1: E302 expected 2 blank lines, found 1
Line 141:1: E302 expected 2 blank lines, found 1
Line 146:1: E302 expected 2 blank lines, found 1
Line 150:1: W391 blank line at end of file

Line 77:53: W291 trailing whitespace
Line 315:1: W293 blank line contains whitespace
Line 319:44: W291 trailing whitespace
Line 339:80: E501 line too long (80 > 79 characters)
Line 417:44: W291 trailing whitespace
Line 437:80: E501 line too long (80 > 79 characters)
Line 1527:44: W291 trailing whitespace
Line 1553:80: E501 line too long (80 > 79 characters)
Line 1645:44: W291 trailing whitespace
Line 1666:80: E501 line too long (80 > 79 characters)
Line 1774:44: W291 trailing whitespace
Line 1794:80: E501 line too long (80 > 79 characters)

Line 441:1: E302 expected 2 blank lines, found 1
Line 723:5: E303 too many blank lines (2)
Line 766:5: E303 too many blank lines (2)
Line 790:5: E303 too many blank lines (2)
Line 800:5: E303 too many blank lines (2)
Line 854:1: E303 too many blank lines (4)
Line 857:54: W291 trailing whitespace
Line 953:80: E501 line too long (93 > 79 characters)
Line 1079:80: E501 line too long (105 > 79 characters)
Line 1210:44: W291 trailing whitespace

Comment last updated at 2024-10-24 03:42:25 UTC

Copy link

Linter Bot Results:

Hi @hmacdope! Thanks for making this PR. We linted your code and found the following:

Some issues were found with the formatting of your code.

Code Location Outcome
main package ⚠️ Possible failure
testsuite ✅ Passed

Please have a look at the darker-main-code and darker-test-code steps here for more details: https://github.com/MDAnalysis/mdanalysis/actions/runs/11344140762/job/31548247192


Please note: The black linter is purely informational, you can safely ignore these outcomes if there are no flake8 failures!

Copy link

codecov bot commented Oct 15, 2024

Codecov Report

Attention: Patch coverage is 25.00000% with 45 lines in your changes missing coverage. Please review.

Project coverage is 86.21%. Comparing base (101008b) to head (50efeb9).

Files with missing lines Patch % Lines
package/MDAnalysis/lib/distances.py 0.00% 14 Missing and 9 partials ⚠️
package/MDAnalysis/lib/_distopia.py 40.54% 22 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (101008b) and HEAD (50efeb9). Click for more details.

HEAD has 2 uploads less than BASE
Flag BASE (101008b) HEAD (50efeb9)
7 5
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #4734      +/-   ##
===========================================
- Coverage    93.65%   86.21%   -7.44%     
===========================================
  Files          175      187      +12     
  Lines        21564    22676    +1112     
  Branches      3023     3030       +7     
===========================================
- Hits         20195    19550     -645     
- Misses         925     2690    +1765     
+ Partials       444      436       -8     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hmacdope hmacdope marked this pull request as ready for review October 17, 2024 03:26
@hmacdope
Copy link
Member Author

@orbeckst @richardjgowers this is ready for a first look over. The failing tests are for a 180 degree dihedral where numpy returns np.pi and distopia returns -np.pi (equivalent in polar coordinates). Are we ok to change the test to account for this? There are also options for changing in distopia but at the cost of a lot of performance improvement.

@orbeckst
Copy link
Member

I'd be ok with changing tests but adding a note to docs.

Copy link
Member

@orbeckst orbeckst left a comment

Choose a reason for hiding this comment

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

I am not sure if the tests are actually run — if we can confirm that they run then I have no blockers.

ALso updated CHANGELOG, please.

package/MDAnalysis/lib/distances.py Outdated Show resolved Hide resolved
package/MDAnalysis/lib/_distopia.py Show resolved Hide resolved
@orbeckst
Copy link
Member

FYI, upstream distopia 0.3.0 release is currently breaking all tests, see #4739 .

@orbeckst orbeckst mentioned this pull request Oct 18, 2024
5 tasks
@orbeckst orbeckst mentioned this pull request Oct 18, 2024
8 tasks
@hmacdope hmacdope changed the title [WIP] distopia 0.3.0 compatibility changes distopia 0.3.0 compatibility changes Oct 23, 2024
@hmacdope
Copy link
Member Author

@orbeckst @IAlibay feature freeze aside this is probably ready for re-review.

Copy link
Member

@orbeckst orbeckst left a comment

Choose a reason for hiding this comment

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

Awesome to also have support for distance_array/self_distance_array!

I have a few comments on the docs (see inline) and minor suggestion for the test.

My major issue at the moment is that codecov does not mark the distance_array functions as tested. Given that it sees the bonds and angles, I wonder if we are really testing distopia-accelerated distance_array. Can you please double check? Thanks!

package/MDAnalysis/lib/distances.py Outdated Show resolved Hide resolved
package/MDAnalysis/lib/distances.py Outdated Show resolved Hide resolved
package/CHANGELOG Outdated Show resolved Hide resolved
import MDAnalysis.lib._distopia
assert not MDAnalysis.lib._distopia.HAS_DISTOPIA
sys.modules.pop("MDAnalysis.lib._distopia", None)
if HAS_DISTOPIA:
Copy link
Member

Choose a reason for hiding this comment

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

For the test I'd ignore HAS_DISTOPIA and not add the conditional. In principle we always want to test explicitly that the version check is working. By using the conditional you implicitly assume that HAS_DISTOPIA was set to False because of the version check.

assert not MDAnalysis.lib._distopia.HAS_DISTOPIA
sys.modules.pop("MDAnalysis.lib._distopia", None)
if HAS_DISTOPIA:
with patch('distopia.__version__', '0.1.0'):
Copy link
Member

Choose a reason for hiding this comment

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

This only works if we can actually import distopia so I now understand why you need the conditional.

I'd prefer the test to work regardless of distopia being present, which can be achieved with the previous approach of creating a mocked module.

background on patch()

(I had to read up on patch to understand how it does its magic so in case anyone else is interested, here's the background.)

The docs for patch() say

unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)
...
target should be a string in the form 'package.module.ClassName'. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch() from.

(my emphasis).

Just to confirm: patch needs an importable module if one relies on the default behavior:

In [2]: from unittest.mock import patch

In [3]: import distopia
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[3], line 1
----> 1 import distopia

ModuleNotFoundError: No module named 'distopia'

In [4]: with patch('distopia.__version__', '0.1.0'):
   ...:     import distopia
   ...:     print(distopa.__version__)
   ...:

---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[4], line 1
----> 1 with patch('distopia.__version__', '0.1.0'):
      2     import distopia
      3     print(distopa.__version__)
...
ModuleNotFoundError: No module named 'distopia'

package/MDAnalysis/lib/_distopia.py Show resolved Hide resolved
) -> None:
distopia.calc_self_distance_array_no_box(coords, results=results)

def calc_self_distance_array_ortho(
Copy link
Member

Choose a reason for hiding this comment

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

Not tested + docs of self_distance_array

package/MDAnalysis/lib/_distopia.py Show resolved Hide resolved
package/MDAnalysis/lib/_distopia.py Outdated Show resolved Hide resolved
package/MDAnalysis/lib/distances.py Show resolved Hide resolved
@@ -946,6 +939,19 @@ def test_bonds(self, box, backend, dtype, pos, request):
assert_almost_equal(dists_pbc[3], 3.46410072, self.prec,
err_msg="PBC check #w with box")

@pytest.mark.parametrize("dtype", (np.float32, np.float64))
@pytest.mark.parametrize("backend", distopia_conditional_backend())
def test_results_inplace_all_backends(self, backend, dtype,):
Copy link
Member Author

Choose a reason for hiding this comment

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

Problematic test here.

@@ -303,6 +312,14 @@ def distance_array(reference: Union[npt.NDArray, 'AtomGroup'],
distances = _check_result_array(result, (refnum, confnum))
if len(distances) == 0:
return distances

Copy link
Member Author

Choose a reason for hiding this comment

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

@orbeckst @richardjgowers we have an issue which is that the distopia backend doesn't seem to fill the result array on return. ie post call for the other backends, result == distances outside the function while for distopia its passed through. Not sure if I am doing something simple wrong.

Offending example in a test on this PR.

    @pytest.mark.parametrize("dtype", (np.float32, np.float64))
   @pytest.mark.parametrize("backend", distopia_conditional_backend())
   def test_results_inplace_all_backends(self,  backend, dtype,):
       N = 10
       c0 = np.ones(3 * N, dtype=dtype).reshape(N, 3) * 2
       c1 = np.ones(3 * N, dtype=dtype).reshape(N, 3) * 3

       result = np.zeros(N, dtype=np.float64)
       distances.calc_bonds(c0, c1, result=result, backend=backend)
       expected = np.ones(N, dtype=dtype) * 3**(1/2)
       # test the result array is updated in place
       assert_almost_equal(result, expected, self.prec, err_msg="calc_bonds inplace failed")

We can get around this with a copy, but shouldn't be nessecary right? The distopia core API works inplace. Im fairly sure I am just getting some basic scoping wrong.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

I'd look into any use of @check_coords as it creates copies by default.

Copy link
Member

Choose a reason for hiding this comment

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

(Maybe not relevant as you're concerned with results.)

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 actually the astype() — see comment review.

HAS_DISTOPIA = False


from .c_distances import (
calc_bond_distance_triclinic as _calc_bond_distance_triclinic_serial,
)
import numpy as np


def calc_bond_distance_ortho(
coords1, coords2: np.ndarray, box: np.ndarray, results: np.ndarray
Copy link
Member

Choose a reason for hiding this comment

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

Any good reason why coords1 does not have a type?

# distopia requires that all the input arrays are the same type,
# while MDAnalysis allows for mixed types, this should be changed
# pre 0.3.0 release see issue #3707
distances = distances.astype(np.float32)
Copy link
Member

Choose a reason for hiding this comment

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

This may create a copy.

Copy link
Member

@orbeckst orbeckst left a comment

Choose a reason for hiding this comment

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

With the astype required for distopia you may be creating a copy so that the top-level in-place operation is not actually in-place anymore. ... I think.

if backend == 'distopia':
# mda expects the result to be in float64, so we need to convert it back
# to float64, change for 3.0, see #3707
distances = distances.astype(np.float64)
Copy link
Member

Choose a reason for hiding this comment

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

This may create a copy.

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 have to instead put the numbers back into the original array to make in-place work.

result[:] = distances

...

return result

or something of that kind.

@orbeckst
Copy link
Member

@hmacdope did you look further into the copy vs in-place issue?

@orbeckst orbeckst self-assigned this Nov 21, 2024
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.

4 participants