Conversation
|
I just realised that this transformation may cause issues with molecule wrapping/unwrapping. An easy example would be a membrane system rotated by 90 degrees on an axis which is parallel to the plane of the membrane, in a non cubic box. For the system to "fill" the box would require taking atoms from its periodic images, which would change its composition. |
|
Rotating a system is always tricky with periodic conditions. @Tsjerk told me once that you can solve the problem by rotating the box definition as well. It may be worth investigating, else a warning in the doc could do. Le 15 juin 2018 7:42 PM, Davide Cruz <notifications@github.com> a écrit :I just realised that this transformation may cause issues with molecule wrapping/unwrapping. An easy example would be a membrane system rotated by 90 degrees on an axis which is parallel to the plane of the membrane, in a non cubic box. For the system to "fill" the box would require taking atoms from its periodic images, which would change it's composition.
Should I add a warning to the docs of the function?
I was looking at the wrapping functions wrap, pack_into_box and apply_PBC, and I don't think they will fail graciously in this case 🤔
—You are receiving this because your review was requested.Reply to this email directly, view it on GitHub, or mute the thread.
|
|
The problem is that any rotation of the coordinates causes mismatch with
the PBC. As mentioned, the solution is to also rotate the PBC. However,
(un)wrapping routines typically rely on certain conditions of the PBC, such
as having a first vector aligned with X, the second in the XY plane and the
third with positive Z (Gromacs rules). Then (un)wrapping routines may still
fail after rotation. The ultimate solution is to store the cumulative
rotation of the system (due to fitting etc) alongside PBC and for every
(un)wrapping operation, including distance calculations and clustering,
rotate the coordinates to match the PBC and rotate back afterwards if so
required.
Hope it helps,
Tsjerk
…On Fri, Jun 15, 2018, 20:09 Jonathan Barnoud ***@***.***> wrote:
Rotating a system is always tricky with periodic conditions. @Tsjerk told
me once that you can solve the problem by rotating the box definition as
well. It may be worth investigating, else a warning in the doc could
do. Le 15 juin 2018 7:42 PM, Davide Cruz ***@***.***> a
écrit :I just realised that this transformation may cause issues with
molecule wrapping/unwrapping. An easy example would be a membrane system
rotated by 90 degrees on an axis which is parallel to the plane of the
membrane, in a non cubic box. For the system to "fill" the box would
require taking atoms from its periodic images, which would change it's
composition.
Should I add a warning to the docs of the function?
I was looking at the wrapping functions wrap, pack_into_box and apply_PBC,
and I don't think they will fail graciously in this case 🤔
—You are receiving this because your review was requested.Reply to this
email directly, view it on GitHub, or mute the thread.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1937 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABIX971ezUfryFGQqxTUcW38wlNgKf7Lks5t8_hXgaJpZM4UmqZ0>
.
|
|
@Tsjerk in the context of the API, the cumulative rotation could be stored in the TImestep object. Then every one of those operations would read that property of the timestep and perform the rotation. For now I think it's better to warn the user in the docs. What do you think @jbarnoud ? |
|
I think the best is to warn the user in the doc. Latter on we can think about a smart unwrap method that knows about the box. Le 16 juin 2018 3:07 PM, Davide Cruz <notifications@github.com> a écrit :@Tsjerk in the context of the API, the cumulative rotation could be stored in the TImestep object. Then every one of those operations would read that property of the timestep and perform the rotation.
Another solution is just adding the rotation to the timestep without actually changing the coordinates. Then at the end of the workflow a hidden transformation would rotate the coordinates if a rotation is present in the timestep.
For now I think it's better to warn the user in the docs. What do you think @jbarnoud ?
—You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub, or mute the thread.
|
| from ..lib.transformations import rotation_matrix | ||
| from ..core.groups import AtomGroup | ||
|
|
||
| def rotate(angle, direction, center="geometry", **kwargs): |
There was a problem hiding this comment.
There is no reason to use **kwargs here. It is better to write explicitly all the arguments in the signature. The main issue with **kwargs is that it makes a bunch of argument validation moot: there will not be an error if I make a typo in an argument name for instance, or if I give too many arguments. Also, having a full signature is better on a documentation standpoint.
| ts = u.trajectory.ts | ||
| angle = math.pi/2 | ||
| ag = u.atoms() | ||
| rotated = MDAnalysis.transformations.rotate(angle, a)(ts) |
There was a problem hiding this comment.
a is not defined, and ag is not used. Typo I guess.
| point: list, optional | ||
| list of the coordinates of the point from where a custom axis of rotation will | ||
| be defined. | ||
| ts: Timestep |
There was a problem hiding this comment.
The function does not take ts as an argument.
| from ..lib.transformations import rotation_matrix | ||
| from ..core.groups import AtomGroup | ||
|
|
||
| def rotate(angle, direction, center="geometry", **kwargs): |
There was a problem hiding this comment.
It may be better to call the function rotateby to be consistent with AtomGroup.rotateby. Also, it may be easier to use AtomGroup.rotateby in your function rather than reimplementing half of it.
| from MDAnalysisTests.datafiles import GRO | ||
|
|
||
| def test_rotate_custom_position(): | ||
| u = mda.Universe(GRO) |
There was a problem hiding this comment.
so reading the GRO system requires file i/o etc. better would be to use make_Universe() (in core/test_groups etc). I can't remember if this gives nonzero positions, if it doesn't you can make your own fixture that does.
| u = mda.Universe(GRO) | ||
| vector = np.float32([1,0,0]) | ||
| pos = np.float32([0,0,0]) | ||
| matrix = rotation_matrix(math.pi/2, vector, pos)[:3, :3] |
jbarnoud
left a comment
There was a problem hiding this comment.
A few comments on top of Richard's ones. The one on the tests is the most serious: please try to break your code on purpose and see if your tests fail.
| ref = u.trajectory.ts | ||
| ref.positions = np.dot(ref.positions, matrix) | ||
| transformed = rotate(math.pi/2, vector, position = pos)(ref) | ||
| transformed = rotateby(math.pi/2, vector, position = pos)(ref) |
There was a problem hiding this comment.
The conventions in PEP8 say that there should not be spaces around the = here, but there should be spaces around the /.
| ref.positions = np.dot(ref.positions, matrix) | ||
| selection = u.atoms.select_atoms('resid 1') | ||
| transformed = rotate(math.pi/2, vector, ag = selection, center='geometry')(ref) | ||
| transformed = rotateby(math.pi/2, vector, ag = selection, center='geometry')(ref) |
There was a problem hiding this comment.
Here you are overwriting your reference. transformed and ref are the same object so the test cannot fail.
| from MDAnalysisTests.datafiles import GRO | ||
|
|
||
| def test_translate_custom_position(): | ||
| def test_rotate_custom_position(): |
There was a problem hiding this comment.
Should prabably be test_rotateby_....
…sts now use make_Universe
|
As the commit says, the typecasting to numpy.float32 in the rotateby function was causing floating point operation errors, and the tests were failing. # matrix from within rotateby
array([[ 1.000000e+00, 0.000000e+00, 0.000000e+00],
[ 0.000000e+00, -4.371139e-08, -1.000000e+00],
[ 0.000000e+00, 1.000000e+00, -4.371139e-08]])
# matrix when calling rotation_matrix outside the transformation
array([[ 1.000000e+00, 0.000000e+00, 0.000000e+00],
[ 0.000000e+00, 6.123234e-17, -1.000000e+00],
[ 0.000000e+00, 1.000000e+00, 6.123234e-17]])And this was causing the tests to fail. I removed all the typecasting in my code, since the |
| from MDAnalysis.lib.transformations import rotation_matrix | ||
| from MDAnalysisTests import make_Universe | ||
|
|
||
| def rotateby_universes(): |
There was a problem hiding this comment.
@pytest.fixture()
def rotateby_universes():to make this work. Pytest then inspects arguments to test functions and sees if any names for missing arguments match its known fixtures
| from ..lib.transformations import rotation_matrix | ||
| from ..core.groups import AtomGroup | ||
|
|
||
| def rotateby(angle, direction, center="geometry", pbc=None, ag=None, position=[]): |
There was a problem hiding this comment.
iirc there's some weird stuff that can happen with position=[] as a kwarg. Like I think you can append to that list and the default drifts over time... use =None
|
@davidercruz is there a list somewhere of the transforms you're doing next? ie when is wrap/unwrap happening? :) |
|
@richardjgowers there is a list in my GSOC project. Today I'll create a PR for a box centering transformation. PBC corrections come next. There is a wrap method in |
|
@davidercruz there's also |
|
FWIW, what -ur compact of Gromacs intends to do and a possible
(un)wrap/boxwrap should do is to put everything in the Voronoi region of
the lattice with respect to some reference point, like, e.g., the center of
mass of a protein. This may not be the most optimal visualization still,
and I think it's a good opportunity to advertise the 'molecular shaped
box', which was also introduced in my thesis, and which is the Voronoi
region of the lattice with respect to a group of reference points, like,
e.g., a protein.
To unwrap/repack molecules, a possible strategy is to calculate the
circular center of mass and place all atoms of the molecule at the smallest
circular distance from that center. The molecule can then also be
repositioned to the desired position based on that center.
T.
…On Tue, Jun 19, 2018 at 5:49 PM Richard Gowers ***@***.***> wrote:
@davidercruz <https://github.com/davidercruz> there's also
lib.mdamath.make_whole but it's shamefully slow (but I could make it
faster if we had a cool transformation that used it...). I'm not great at
gromacs, but it'd be cool if it unchopped molecules that had crossed a box
boundary and got written to the trajectory in a "packed" way
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1937 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABIX9yRQ_a8MQ9C7VjrQQgh5G_khIw76ks5t-R2ZgaJpZM4UmqZ0>
.
--
Tsjerk A. Wassenaar, Ph.D.
|
| pbc_arg = pbc | ||
| if position and len(position)>2: | ||
| position = position | ||
| elif isinstance(ag, AtomGroup): |
There was a problem hiding this comment.
This has the same issues I pointed in #1946:
- it is better not to check for type, but instead try to do the thing (duck typing)
- the center is not updated after the first frame
| from ..core.groups import AtomGroup | ||
|
|
||
| def rotateby(angle, direction, center="geometry", pbc=None, ag=None, position=None): | ||
| ''' |
There was a problem hiding this comment.
You mentioned a warning about periodic boundary conditions to add in the documentation. It should be added.
| Parameters | ||
| ---------- | ||
| angle: float | ||
| rotation angle in radians |
There was a problem hiding this comment.
All other functions of MDAnalysis that work with angle expect degrees from the user. For consistency, this function should too.
| ---------- | ||
| angle: float | ||
| rotation angle in radians | ||
| direction: list |
| If True, all the atoms from the given AtomGroup will be moved to the unit cell | ||
| before calculating the center | ||
| position: list, optional | ||
| before calculating the center. Warning: Wrapping/unwrapping the trajectory or |
There was a problem hiding this comment.
Thins is indeed the warning. Though, it has nothing to do with the PBC option. It is a more general problem that should be discussed in the body of the docstring. You can add a "Warnings" section.
The pbc option is about wrapping prior to calculating the center. Setting pcs to False is not going to change the fact that the coordinates are modified in a different way than the reference frame.
There was a problem hiding this comment.
You're right. I don't know what I was thinking, writing that there 😕
| @@ -76,31 +77,44 @@ def rotateby(angle, direction, center="geometry", pbc=None, ag=None, position=No | |||
| or 'mass' | |||
| pbc: bool or None, optional | |||
There was a problem hiding this comment.
This option has the same issue as the one from the centering transformation.
| position: list, optional | ||
| before calculating the center. Warning: Wrapping/unwrapping the trajectory or | ||
| performing PBC corrections may not be possible after rotating the trajectory. | ||
| position: array-like, optional |
There was a problem hiding this comment.
To be consistent with the other transformation, the option should be named point. The name of the user facing variable is more important than the name of the variable we manipulate inside; so the variable in the wrapper should be renamed so the argument is consistent to the user.
| raise ValueError('{} is not a valid argument for center'.format(center)) | ||
| angle = np.deg2rad(angle) | ||
| if position: | ||
| if len(position)!=3: |
There was a problem hiding this comment.
I mentioned it already; len on an array is a terrible idea. An array with a (1, 3) shape will have length 3, the same goes for an array of shape (3, 3). First convert to an array with np.asarray, then compare the shape with position.shape != (3, ). You may be permissive and accept (1, 3) as a shape as well.
|
I made a mistake and commented on a previous state. Still have a look at my last batch of comments, they are still all valid. |
|
A couple more things:
|
|
@jbarnoud I'm slicing the rotation matrix because the output matrix is 4 by 4 by default. def rotation_matrix(angle, direction, point=None):
# things are done before
M = np.identity(4) # M is 4 by 4
M[:3, :3] = R
# things are done after
return M # M is still 4 by 4 |
|
@davidercruz note on "Why is |
| from ..lib.transformations import rotation_matrix | ||
| from ..core.groups import AtomGroup | ||
|
|
||
| def rotateby(angle, direction, point=None, center="geometry", pbc=False, ag=None): |
There was a problem hiding this comment.
Shouldn't the pbc argument be wrap to be consistent with the remove center transformation?
|
The tests are failing. I strongly encourage you to run the tests locally when you do changes: https://github.com/MDAnalysis/mdanalysis/wiki/UnitTests#developers. |
Adds a rotation transformation to the transformations module
Changes made in this Pull Request:
is formed by a given position or the center of mass/(geometry of an AtomGroup
PR Checklist