added single distance calculations to lib.distances#1939
Conversation
|
Thanks for taking this on.
|
|
@orbeckst how would you feel about flattening out the namespace slightly, so |
|
I really don't like top-level functions such as I generally like nested name spaces better than flat ones, especially for anything that's not primarily user-facing such as |
|
We could do something like |
Well. Ideally it is the case. There still is |
I think having a part of the library that has no external dependencies is good, and having it under And although
That... should be fixed eventually :-). If we are primarily talking about from .lib import coordinate_calculations as raw
def distance_array(x, y, **kwargs):
return raw.distance_array(x.positions, y.positions, **kwargs)Or come up with a naming scheme that allows the different functions to coexist from .lib.coordinate_calculations import distance_array
def mda_distance_array(x, y, **kwargs):
return distance_array(x.positions, y.positions, **kwargs)(I am not saying that prefixing with |
|
I guess I don't like that you have to know to look in I like the idea of |
45a7168 to
04cf3eb
Compare
|
Ok this is ready for review @MDAnalysis/coredevs |
| coords = np.array([[ 83.37363434, 65.01402283, 35.03564835], | ||
| [ 82.28535461, 59.99816513, 34.94399261], | ||
| [ 81.19387817, 54.97631073, 34.85218048]], | ||
| coords = np.array([[1, 1, 1], |
There was a problem hiding this comment.
Is there a reason you changed this test?
There was a problem hiding this comment.
it wasn't giving 180.0 any more, more like 179.999 so I changed it to a real 180 angle
| # 90 degree angle | ||
| (np.array([[1, 1, 1], [1, 2, 1], [2, 2, 1]], dtype=np.float32), 90.), | ||
| # straight line / 180. | ||
| (np.array([[1, 1, 1], [1, 2, 1], [1, 3, 1]], dtype=np.float32), 180.), |
There was a problem hiding this comment.
90 and 180° are special cases. Maybe something an angle less peculiar would be worth testing.
| # 0 degree angle (cis) | ||
| (np.array([[1, 2, 1], [1, 1, 1], [2, 1, 1], [2, 2, 1]], dtype=np.float32), 0.), | ||
| # 180 degree (trans) | ||
| (np.array([[1, 2, 1], [1, 1, 1], [2, 1, 1], [2, 0, 1]], dtype=np.float32), 180.), |
There was a problem hiding this comment.
Same as above, maybe a less peculiar angle in addition to the edge cases.
| .. versionchanged:: 0.17.0 | ||
| Fixed angles close to 180 giving NaN | ||
| .. versionchanged:: 0.18.1 | ||
| Added pbc keyword |
There was a problem hiding this comment.
This would be worth a note in the changelog, I think.
There was a problem hiding this comment.
(Thanks for adding the CHANGELOG entry.)
orbeckst
left a comment
There was a problem hiding this comment.
Overall looking good, the main thing is more general tests, as @jbarnoud said.
Thanks a lot for taking this one on, @richardjgowers !
| @@ -1037,17 +1038,12 @@ def _get_timestep(): | |||
| @staticmethod | |||
There was a problem hiding this comment.
Can you get rid of the method completely and just call distances.calc_angle() in the code?
There was a problem hiding this comment.
could do, it'd technically be a API break but yeah
| return distances.calc_angle(d.position, h.position, a.position, box) | ||
|
|
||
| @staticmethod | ||
| def calc_eucl_distance(a1, a2, box=None): |
There was a problem hiding this comment.
Can you get rid of the method completely and just call distances.calc_distance() in the code?
| .. versionchanged:: 0.17.0 | ||
| Fixed angles close to 180 giving NaN | ||
| .. versionchanged:: 0.18.1 | ||
| Added pbc keyword |
There was a problem hiding this comment.
(Thanks for adding the CHANGELOG entry.)
|
|
||
| .. versionadded:: 0.9.0 | ||
| .. versionchanged:: 0.18.1 | ||
| Added pbc keyword |
| .. versionchanged:: 0.17.0 | ||
| Fixed angles close to 180 giving NaN | ||
| .. versionchanged:: 0.18.1 | ||
| Added pbc keyword |
package/MDAnalysis/lib/distances.py
Outdated
| a, b : numpy.ndarray | ||
| single coordinate vectors | ||
| box : numpy.ndarray, optional | ||
| simulation box, if given periodic boundary conditions will be applied |
There was a problem hiding this comment.
I think somewhere in the docs we will have to describe in more detail what exactly we mean and do when we say "periodic boundary conditions will be applied".
|
Ok maybe good to go now |
|
@jbarnoud makes an important point that the test cases are edge cases. Can you add something more generic? Perhaps we can generate a whole bunch of tests using simple geometry and some random angles? For example, using the affine transformations from # build in internal coordinates starting at origin
theta_deg = np.random.uniform(0, 180) # angle A-B-C
theta = np.deg2rad(theta_deg)
a, c = np.random.uniform(0, 1.5, 2)
# construct three "atoms" A, B, C with bonds A-B and B-C and the desired angle
A = np.array([a, 0, 0])
B = np.array([0, 0, 0])
C = c * np.array([np.cos(theta), np.sin(theta), 0])
# now translate and rotate randomly
import MDAnalysis.lib.transformations as trans
T = trans.translation_matrix(np.random.uniform(-50, 50, 3))
R = trans.random_rotation_matrix(np.random.rand(3))
M = trans.concatenate_matrices(T, R)
# use affine transformations just for the heck of it (instead of R.x + t)
positions = np.array([A, B, C])
# add '1' in the fourth coordinate (treat as points, not vectors for affine transformation)
points = np.hstack((positions, np.ones(positions.shape[0])[:, np.newaxis]))
# transform (to use broadcasting and take account of array orders, switch M*x to x*M.T)
points_transformed = np.dot(points, M.T)
# transformed coordinates (x, y, z)
positions_transformed = points_transformed[:, :3]
# check that the angle is the same
A1, B1, C1 = positions_transformed
AB1 = A1 - B1
CB1 = C1 - B1
a1 = np.linalg.norm(AB1)
c1 = np.linalg.norm(CB1)
print(np.allclose(a, a1))
print(np.allclose(c, c1))
theta_deg1 = np.rad2deg(np.arccos(np.dot(AB1, CB1) / (a1*c1)))
assert np.allclose(theta_deg, theta_deg1)You can also check with from MDAnalysis.analysis.rms import rmsd
assert np.allclose(rmsd(positions, positions_transformed, superposition=True), 0, atol=1e-7)that we just do a rigid body translation + rotation. |
|
You missed a |
a3e608c to
ce46c79
Compare
|
|
||
| reference = ref if periodic else manual_angle(a, b, c) | ||
|
|
||
| assert result == pytest.approx(reference, abs=1e-3) |
There was a problem hiding this comment.
This fails with numpy 1.10.4 – is pytest.approx() a good way to do this comparison?
There was a problem hiding this comment.
Yeah I was trying to be cute and use pytest, looks like np is better
|
|
||
| reference = ref if periodic else manual_dihedral(a, b, c, d) | ||
|
|
||
| assert abs(result) == pytest.approx(abs(reference), abs=1e-3) |
There was a problem hiding this comment.
fails for numpy 1.10.4
assert 2.5444436693403972e-12 == 0.0 ± 1.0e-03
This is not behaving as well as np.testing.assert_almost_equal().
orbeckst
left a comment
There was a problem hiding this comment.
Ok for the slightly more general angle tests.
However, the assertions are not very robust and fail under numpy 1.10.4, even though the absolute difference is near machine precision.
defaulted Bond.value pbc to True to match other objects
c59faab to
68446bd
Compare
|
@orbeckst ok good to go now! |
Fixes #1262 #1938
Adds some single distance/angle/dihedral calculations to
lib.distances(alongside the bulk functions) which should act as the standard way to do this. It's a little silly that we've got how to calculate an angle implemented so many times (with and without pbc, maybe with different limits).Things to discuss that I wasn't sure about....
lib.distancesseems right, so what'slib.mdamaththen?lib.distancesalso has the C functions in there, we should probably clean that up