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

Minor maintenance updates before 0.12.0 release #478

Merged
merged 19 commits into from
Apr 11, 2024

Conversation

hakonanes
Copy link
Member

@hakonanes hakonanes commented Apr 2, 2024

Description of the change

This PR contains a few maintenance updates before the 0.12.0 release.

In particular, one test, test_restrict_to_fundamental_sector(), failed on my Mac with Python 3.11 and Matplotlib 3.8.3. The relevant part of the test ensures that the edges of the fundamental sectors of C1 and C6 are different, but failed because a different number of edges were returned. I'm not sure why. Anyways, to ensure the edges are different, we only need to check the first couple edge vectors. I've made it so we check the first ten.

Other changes:

  • Clarify a bit the changelog entry by @viljarjf
  • Add @CSSFrancis to package credits (not just Zenodo)
  • Re-run failing tests once using pytest-rerunfailures on GitHub CI. Package added as a test dependency.

Progress of the PR

For reviewers

  • The PR title is short, concise, and will make sense 1 year later.
  • New functions are imported in corresponding __init__.py.
  • New features, API changes, and deprecations are mentioned in the unreleased
    section in CHANGELOG.rst.
  • Contributor(s) are listed correctly in __credits__ in orix/__init__.py and in
    .zenodo.json.

Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
@hakonanes hakonanes added the dev Package maintenance label Apr 2, 2024
@hakonanes hakonanes added this to the v0.12.0 milestone Apr 2, 2024
@hakonanes hakonanes marked this pull request as draft April 2, 2024 18:14
@pc494 pc494 mentioned this pull request Apr 3, 2024
5 tasks
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
@hakonanes
Copy link
Member Author

The only thing left I'd like to look at is the new gallery example on sampling reduced rotations by @CSSFrancis:

==================
Sampling rotations
==================
This example shows how to sample some phase object in Orix. We will
get both the zone axis and the reduced fundamental zone rotations for
the phase of interest.
"""
from diffpy.structure import Atom, Lattice, Structure
from orix.crystal_map import Phase
from orix.sampling import get_sample_reduced_fundamental, get_sample_zone_axis
from orix.vector import Vector3d
a = 5.431
latt = Lattice(a, a, a, 90, 90, 90)
atom_list = []
for coords in [[0, 0, 0], [0.5, 0, 0.5], [0, 0.5, 0.5], [0.5, 0.5, 0]]:
x, y, z = coords[0], coords[1], coords[2]
atom_list.append(Atom(atype="Si", xyz=[x, y, z], lattice=latt)) # Motif part A
atom_list.append(
Atom(atype="Si", xyz=[x + 0.25, y + 0.25, z + 0.25], lattice=latt)
) # Motif part B
struct = Structure(atoms=atom_list, lattice=latt)
p = Phase(structure=struct, space_group=227)
reduced_fun = get_sample_reduced_fundamental(resolution=4, point_group=p.point_group)
vect_rot = (
reduced_fun * Vector3d.zvector()
) # get the vector representation of the rotations
vect_rot.scatter(grid=True) # plot the stereographic projection of the rotations
# %%
zone_axis_rot, directions = get_sample_zone_axis(
phase=p, density="7", return_directions=True
) # get the zone axis rotations
zone_vect_rot = (
zone_axis_rot * Vector3d.zvector()
) # get the vector representation of the rotations
zone_vect_rot.scatter(
grid=True, vector_labels=directions, text_kwargs={"size": 8, "rotation": 0}
) # plot the stereographic projection of the rotations

A couple questions:

  1. The only necessary phase information to run the example are the symmetry operations of the proper point group 432. Can we reduce the part with the phase setup to something like pg432 = symmetry.O? How to create a phase is covered in other parts of the documentation, like in the crystal map tutorial (https://orix.readthedocs.io/en/stable/tutorials/crystal_map.html). We can of course expand upon this by adding a Phase section to the gallery.
  2. In the example, we sample SO(3). But, the plotting only shows S2. We should show the sampled orientations, not just the rotated vectors. Otherwise, if rotated vectors was the goal, I'm wondering as a user why I can't just use the S2 sampling instead. What do you think, @CSSFrancis?

@hakonanes
Copy link
Member Author

If it's OK with you @CSSFrancis, I'd like to re-work the example a bit along those lines.

@CSSFrancis
Copy link
Member

  1. The only necessary phase information to run the example are the symmetry operations of the proper point group 432. Can we reduce the part with the phase setup to something like pg432 = symmetry.O? How to create a phase is covered in other parts of the documentation, like in the crystal map tutorial (https://orix.readthedocs.io/en/stable/tutorials/crystal_map.html). We can of course expand upon this by adding a Phase section to the gallery.

I think that is fine for the most part but it is sometimes nice to point out how someone can get that information from a Phase object. At least for people doing orientation mapping I think they are more likely to be working with the Phase rather than the symmetry.

  1. In the example, we sample SO(3). But, the plotting only shows S2. We should show the sampled orientations, not just the rotated vectors. Otherwise, if rotated vectors was the goal, I'm wondering as a user why I can't just use the S2 sampling instead. What do you think, @CSSFrancis?

This is also related to the orientation mapping. I guess the reason is that we want the rotations because that is the result of the orientation mapping code. I think this is a good example of converting rotations to vectors for visualization by essentially defining a beam direction. You lose in-plane rotation information but in this case there isn't any. Maybe this is an anti-pattern and I don't understand the orix code as well as I should....

@CSSFrancis
Copy link
Member

@hakonanes I don't have a great understanding of orix but it seems like the S2 sampling could return a constrained rotation based on some vector direction rather than return a vector and that might be more useful in relation to the other parts of orix.

@hakonanes
Copy link
Member Author

At least for people doing orientation mapping I think they are more likely to be working with the Phase rather than the symmetry.

This is a good point. I've opened #480 to discuss this further.

it seems like the S2 sampling could return a constrained rotation based on some vector direction

This is what you added in get_sample_reduced_fundamental(), which is great. I just didn't get the motivation behind your added example. Basically, the new function returns rotations which map the vector (0, 0, 1) onto the fundamental sector on the sphere of the corresponding Laue group.

May this then be a more descriptive example?

import matplotlib.pyplot as plt
import numpy as np

from orix import plot, sampling
from orix.quaternion import symmetry
from orix.vector import Vector3d

# Input: sampling resolution and symmetry
res = 2  # Degrees
laue = symmetry.D4h  # 4/mmm

R = sampling.get_sample_reduced_fundamental(res, point_group=laue)

# Show equivalence of vectors v1 and v2
v1 = R * Vector3d.zvector()

v2 = sampling.sample_S2(res)
v2 = v2[v2 <= laue.fundamental_sector]

assert np.allclose(v1.data, v2.data)
# True

fig, (ax0, ax1) = plt.subplots(
    ncols=2, subplot_kw={"projection": "ipf", "symmetry": pg}, layout="tight"
)
ax0.scatter(v1, s=5)
ax1.scatter(v2, c="C1", s=5)
ax0.set_title("Rotated Z vectors", loc="left")
ax1.set_title("Directly sampled", loc="left")
fig.suptitle("Vectors in the fundamental sector of 4/mmm", y=0.8)

test

Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
…th zsh

Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
@CSSFrancis
Copy link
Member

At least for people doing orientation mapping I think they are more likely to be working with the Phase rather than the symmetry.

This is a good point. I've opened #480 to discuss this further.

Perfect!

it seems like the S2 sampling could return a constrained rotation based on some vector direction

This is what you added in get_sample_reduced_fundamental(), which is great. I just didn't get the motivation behind your added example. Basically, the new function returns rotations which map the vector (0, 0, 1) onto the fundamental sector on the sphere of the corresponding Laue group.

May this then be a more descriptive example?

That is much more descriptive :) I don't think I thought as much as I should about what the example should show.

@hakonanes
Copy link
Member Author

hakonanes commented Apr 7, 2024

I've added the above example to the examples gallery: https://orix--478.org.readthedocs.build/en/478/examples/rotations/rotations_mapping_fundamental_sector.html

I've got the following questions regarding the new function get_sample_zone_axis():

  1. Where are the hard-coded fundamental sector vertices (corners) from? Many of them differ from the vertices we use in orix. We have eleven Laue classes, with a different fundamental sector for each one (not just 6/7 crystal systems, excluding triclinic). For these, only mmm, 4/mmm, and m-3m match. We don't get one for -1 (triclinic), while the remaining are different. I suggest to get the corners from Phase.point_group.fundamental_sector.vertices.
  2. Adding unit vectors are different than adding non-unit vectors, say (1, 1, 1) + (0, 0, 1) vs. (0.5574, 0.5574, 0.5574) + (0, 0, 1). Thus, the midpoint vectors returned when density="7" and return_directions=True are not equidistant from the two (non-unit) vectors that are added. Is this intentional?
  3. And what is the purpose of these midpoint vectors in down-stream packages?

We need to change this function for it to align with the rest of orix.

@hakonanes
Copy link
Member Author

I've updated the TODO list in the top comment with what's left before we're ready for the 0.12.0 release.

@CSSFrancis
Copy link
Member

CSSFrancis commented Apr 7, 2024

I've added the above example to the examples gallery: https://orix--478.org.readthedocs.build/en/478/examples/rotations/rotations_mapping_fundamental_sector.html

I've got the following questions regarding the new function get_sample_zone_axis():

  1. Where are the hard-coded fundamental sector vertices (corners) from? Many of them differ from the vertices we use in orix. We have eleven Laue classes, with a different fundamental sector for each one (not just 6/7 crystal systems, excluding triclinic). For these, only mmm, 4/mmm, and m-3m match. We don't get one for -1 (triclinic), while the remaining are different. I suggest to get the corners from Phase.point_group.fundamental_sector.vertices.

I'd imagine that this is a holdover from diffsims. I just copied what was previously in diffsims but in relation to orix I think the approach is a bit naive. We should drop the hard coded and just use the fundamental sector. I still think the method to return the Rotations for the fundamental sectors/ zone axes is useful. Usually I like to use this to give examples of high symmetry zone axes to help with aligning on the TEM.

  1. Adding unit vectors are different than adding non-unit vectors, say (1, 1, 1) + (0, 0, 1) vs. (0.5574, 0.5574, 0.5574) + (0, 0, 1). Thus, the midpoint vectors returned when density="7" and return_directions=True are not equidistant from the two (non-unit) vectors that are added. Is this intentional?

This is most likely a bug. And using the fundamental sectors.vectors would be much better.

  1. And what is the purpose of these midpoint vectors in down-stream packages?

We need to change this function for it to align with the rest of orix.

Honestly coming from a non crystallography background I looked in orix but am not particularly familiar with terms like fundamental sector and fundamental_sector.vecticies is fairly hidden. So something like Zone Axis, high symmetry axis etc is much more descriptive to me. I'm not sure that there is a difference between the two? But it would be good to have something in the documentation related to Zone Axis or high symmetry which helps those of us with a TEM background. Adding an example like "Getting High Symmetry Axes" would be nice, but maybe the Great Circle Example should have been enough for me :).

Edit: This doesn't make sense... I'm mixing this up a bit. Let me try again

Edit x2: Okay now this makes more sense.

@hakonanes
Copy link
Member Author

Thank you for clarifying!

Since the get_sample_zone_axis() function won't be used directly by down-stream packages, I suggest to remove it but add descriptive examples to get the desired result.

The below is one route to some nice high-symmetry zone axes is. If it's OK with you @CSSFrancis, I'll replace the function and corresponding tests with this example.

from orix.crystal_map import Phase
from orix.vector import Miller

phase = Phase(point_group="mmm")  # m-3m, 6/mmm, 4/mmm, -3m, mmm, 2/m
t = Miller.from_highest_indices(phase, uvw=[1, 1, 1])
t = t.in_fundamental_sector()  # Project all to the fundamental sector
t = t.unit.unique(use_symmetry=True).round()  # Unit vectors ensure e.g. [222] == [111]
print(t)
# Miller (7,), point group mmm, uvw
# [[0. 0. 1.]
#  [0. 1. 1.]
#  [0. 1. 0.]
#  [1. 1. 1.]
#  [1. 0. 1.]
#  [1. 1. 0.]
#  [1. 0. 0.]]

Plotting the annotated vectors, only showing the fundamental sector

fig = t.scatter(
    vector_labels=[str(vi).replace(".", "") for vi in t.data],
    text_kwargs={"size": 15, "offset": (0, 0.02), "bbox": {"fc": "w", "pad": 1, "alpha": 0.75}},
    return_figure=True,
)
fig.axes[0].restrict_to_sector(t.phase.point_group.fundamental_sector)

test

@CSSFrancis
Copy link
Member

CSSFrancis commented Apr 8, 2024

Thank you for clarifying!

Since the get_sample_zone_axis() function won't be used directly by down-stream packages, I suggest to remove it but add descriptive examples to get the desired result.

The below is one route to some nice high-symmetry zone axes is. If it's OK with you @CSSFrancis, I'll replace the function and corresponding tests with this example.

from orix.crystal_map import Phase
from orix.vector import Miller

phase = Phase(point_group="mmm")  # m-3m, 6/mmm, 4/mmm, -3m, mmm, 2/m
t = Miller.from_highest_indices(phase, uvw=[1, 1, 1])
t = t.in_fundamental_sector()  # Project all to the fundamental sector
t = t.unit.unique(use_symmetry=True).round()  # Unit vectors ensure e.g. [222] == [111]
print(t)
# Miller (7,), point group mmm, uvw
# [[0. 0. 1.]
#  [0. 1. 1.]
#  [0. 1. 0.]
#  [1. 1. 1.]
#  [1. 0. 1.]
#  [1. 1. 0.]
#  [1. 0. 0.]]

Plotting the annotated vectors, only showing the fundamental sector

fig = t.scatter(
    vector_labels=[str(vi).replace(".", "") for vi in t.data],
    text_kwargs={"size": 15, "offset": (0, 0.02), "bbox": {"fc": "w", "pad": 1, "alpha": 0.75}},
    return_figure=True,
)
fig.axes[0].restrict_to_sector(t.phase.point_group.fundamental_sector)

I think that is useful but it would be nice to include an example the returns the Rotations as well as the vectors for simulation purposes.

I think a workflow like:

import diffsims as ds
from orix.crystal_map import Phase

phase = Phase.from_cif("strucuture.cif")
sim = ds.simulations.Simulation()
zone_axes_patterns = sim.get_zone_axes_patterns(phase)

is something we should be able to do fairly easily. If this is only used in diffsims then no reason to have a separate function but I suspect that this is a general enough function that others might be interested in it.

I think something like the ASTAR/ JEMS GUI is something that we could fairly easily replicate with orix/diffsims and it would be very useful to a lot of people.

Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
…directions

Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
@hakonanes
Copy link
Member Author

it would be nice to include an example the returns the Rotations as well as the vectors for simulation purposes.

I've added the above example with an additional section showing how to get the rotations rotating the Z-vector (0, 0, 1) onto the high-symmetry crystal directions.

To have the syntax you suggest in diffsims, I suggest to make a function there instead of in orix. My feeling is that orix should be free of diffraction-specific functionality and stick to crystal orientations and directions. It may be that a more specific class method could be added to the ReciprocalLatticeVector class. This class is orix' Miller(hkl) class specifically tailored toward diffraction.

@hakonanes hakonanes marked this pull request as ready for review April 9, 2024 22:48
@hakonanes
Copy link
Member Author

@CSSFrancis, I'd appreciate it if you could have a look at the new examples, the rephrased docstring of get_sample_reduced_fundamental(), and the reworked test of that function.

Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
…ser facing change

Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
Copy link
Member

@CSSFrancis CSSFrancis left a comment

Choose a reason for hiding this comment

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

@hakonanes This all looks very nice.

Unrelated to this PR, but it would be a good idea to all the Rotation.from_align_vectors
to take multiple vectors so you could do:

vz = Miller(uvw=[0, 0, 1], phase=t.phase)
 R = Rotation.identity(t.size)
 for i, t_i in enumerate(t):
     R[i] = Rotation.from_align_vectors(t_i, vz)
 print(R)

# Change to 
R = Rotation.from_align_vectors(t, vz)

It's probably not super simple as the align vectors can handle the case where you are trying to align multiple vectors but we could also just add the function Rotation.from_align_vector which assumes a direct rotation from one vector to another.

examples/crystal_maps/create_empty_map.py Outdated Show resolved Hide resolved
orix/sampling/sample_generators.py Show resolved Hide resolved
Signed-off-by: Håkon Wiik Ånes <hwaanes@xnovotech.com>
@hakonanes
Copy link
Member Author

It's probably not super simple as the align vectors can handle the case where you are trying to align multiple vectors but we could also just add the function Rotation.from_align_vector which assumes a direct rotation from one vector to another.

Good point. Well, in the API, we only support the case where the two end point vectors have the same size. So, we could just say that when the sizes differ, we do broadcasting, instead of raising an error. In the simplest case, we can support 1-to-n and n-to-1. It may be slow, since the SciPy function we call does not support broadcasting, but it is possible.

@CSSFrancis
Copy link
Member

LGTM! @hakonanes You can merge if you would like.

@hakonanes hakonanes merged commit 7a42ea2 into pyxem:develop Apr 11, 2024
13 checks passed
@hakonanes hakonanes deleted the prepare-for-0.12 branch April 11, 2024 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dev Package maintenance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants