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

Add Symmetry plot method #306

Merged
merged 28 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
42eb037
add symmetry scatter method
harripj Mar 21, 2022
8cbe2e7
rename scatter to plot and tidy up
harripj Mar 22, 2022
196c7bb
add symmetry name to figure title
harripj Mar 22, 2022
c5ac018
add point_groups notebook
harripj Mar 22, 2022
946627e
add Symmetry.plot test
harripj Mar 22, 2022
9fa4583
add notebook to index.rst
harripj Mar 22, 2022
66e249c
update docstring, add markers to args
harripj Mar 22, 2022
1f200f8
improve coverage
harripj Mar 22, 2022
d8193d6
Update CHANGELOG.rst
harripj Mar 22, 2022
32042cd
update notebook title and MD formatting
harripj Mar 22, 2022
6347843
add plot to reference.rst
harripj Mar 25, 2022
fbbd642
update testing, remove marker tests
harripj Mar 25, 2022
6339f90
update docstrings, change plot orientation default arg to None
harripj Mar 25, 2022
bc24ee9
Update point_groups.ipynb
harripj Mar 25, 2022
009e6f2
remove blank line
harripj Mar 25, 2022
41cfec9
fix typo
harripj Mar 25, 2022
beb1ea9
fix method reference in docstring
harripj Mar 25, 2022
a73f148
fit test symmetry.plot raises
harripj Mar 25, 2022
39bccd7
retrigger checks
harripj Mar 25, 2022
a59cdbb
update package locations
harripj Mar 25, 2022
d273f81
Merge branch 'add_Symmetry_scatter' of https://github.com/harripj/ori…
harripj Mar 25, 2022
2ac334f
add %matplotlib inline
harripj Mar 25, 2022
8ad76c4
Merge remote-tracking branch 'upstream/master' into add_Symmetry_plot
harripj Mar 29, 2022
bd85667
fix marker default, add comment about formatting
harripj Mar 29, 2022
7131934
update with #307, fix tests
harripj Mar 29, 2022
0fa530b
update notebook and add comments
harripj Mar 29, 2022
e4057ad
remove marker color test to improve coverage
harripj Mar 29, 2022
546ceb5
update v_reproject calculation
harripj Mar 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ Unreleased

Added
-----
- Point group `Symmetry` elements can now be viewed under the stereographic projection
using `Symmetry.plot()`. The notebook point_groups.ipynb has been added to the
documentation.
- `Rotation` objects can now be checked for equality. Equality is determined by
comparing their shape, data, and whether the rotations are improper.
- `angle_with_outer()` has been added to both `Rotation` and `Orientation` classes
Expand Down
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Work using orix
clustering_across_fundamental_region_boundaries.ipynb
clustering_orientations.ipynb
uniform_sampling_of_orientation_space.ipynb
point_groups.ipynb

.. toctree::
:hidden:
Expand Down
116 changes: 116 additions & 0 deletions doc/point_groups.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"nbsphinx": "hidden"
},
"source": [
"This notebook is part of the orix documentation https://orix.rtfd.io. Links to the documentation won’t work from the notebook."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Visualizing point groups\n",
"\n",
"Point group symmetry operations are shown here under the stereographic projection.\n",
"\n",
"Vectors located on the upper (`z > 0`) hemisphere are displayed as crosses (`+`), whereas vectors on the lower hemisphere are reprojected onto the upper hemisphere and shown as open circles (`o`).\n",
"\n",
"More explanation of these figures is provided at http://xrayweb.chem.ou.edu/notes/symmetry.html#point."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For example, the `O (432)` point group:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"nbsphinx-thumbnail"
]
},
"outputs": [],
"source": [
"from orix.quaternion import symmetry\n",
harripj marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"symmetry.O.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The stereographic projection of all point groups is shown below:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from matplotlib import pyplot as plt\n",
"import numpy as np\n",
"\n",
"from orix import plot\n",
"from orix.quaternion import Rotation\n",
"from orix.vector import Vector3d\n",
"\n",
"schoenflies = ['C1', 'Ci', 'C2x', 'C2y', 'C2z', 'Csx', 'Csy', 'Csz', 'C2h', 'D2', 'C2v', 'D2h', 'C4', 'S4', 'C4h', 'D4', 'C4v', 'D2d', 'D4h', 'C3', 'S6', 'D3x', 'D3y', 'D3', 'C3v', 'D3d', 'C6', 'C3h', 'C6h', 'D6', 'C6v', 'D3h', 'D6h', 'T', 'Th', 'O', 'Td', 'Oh']\n",
"\n",
"assert len(symmetry._groups) == len(schoenflies)\n",
"\n",
"schoenflies = [s for s in schoenflies if not (s.endswith('x') or s.endswith('y'))]\n",
"\n",
"assert len(schoenflies) == 32\n",
"\n",
"orientation = Rotation.from_axes_angles((-1, 8, 1), np.deg2rad(65))\n",
"\n",
"fig, ax = plt.subplots(nrows=8, ncols=4, figsize=(10, 20), subplot_kw=dict(projection='stereographic'))\n",
"ax = ax.ravel()\n",
"\n",
"for i, s in enumerate(schoenflies):\n",
" sym = getattr(symmetry, s)\n",
"\n",
" ori_sym = sym.outer(orientation)\n",
" v = (ori_sym * Vector3d.zvector())\n",
"\n",
" v_upper = v[v.z >= 0]\n",
" v_lower = v[v.z < 0]\n",
" v_lower.z *= -1\n",
" \n",
" ax[i].scatter(v_upper, marker='+', s=150)\n",
" ax[i].scatter(v_lower, marker='o', fc='None', ec='k', s=150)\n",
"\n",
" ax[i].set_title(f'${s}$ $({sym.name})$')\n",
" ax[i].set_labels('a', 'b', None)\n",
"\n",
"fig.tight_layout()"
]
}
],
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
1 change: 1 addition & 0 deletions doc/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ Symmetry
.. currentmodule:: orix.quaternion.Symmetry
.. autosummary::
from_generators
plot
.. autoclass:: orix.quaternion.Symmetry
:show-inheritance:
:members:
Expand Down
70 changes: 70 additions & 0 deletions orix/quaternion/symmetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,76 @@ def fundamental_zone(self):
sr = SphericalRegion(n.unique())
return sr

def plot(
self,
orientation=None,
markers=("+", "o"),
**kwargs,
):
"""Stereographic projection of symmetry operations.

The upper hemisphere of the stereographic projection is shown.
Vectors on the lower hemisphere are shown after reprojection
onto the upper hemisphere.

Parameters
----------
orientation : Orientation, optional
The symmetry operations are applied to this orientation
before plotting. The default value uses an orientation
optimized to show symmetry elements.
markers : tuple, optional
Markers for vectors located on the upper and lower
hemisphere of the unit sphere, respectively. Passed to
:meth:`matplotlib.axes.Axes.scatter()`.
kwargs
Keyword arguments passed to
:meth:`~orix.plot.StereographicPlot.scatter()`, which passes
these on to :meth:`matplotlib.axes.Axes.scatter()`.

Returns
-------
fig : matplotlib.figure.Figure
The created figure, returned if `return_figure` is supplied
as a keyword argument and is True.
"""
if orientation is None:
# orientation chosen to mimic stereographic projections as
# shown: http://xrayweb.chem.ou.edu/notes/symmetry.html
orientation = Rotation.from_axes_angles((-1, 8, 1), np.deg2rad(65))
if not isinstance(orientation, Rotation):
raise TypeError("Orientation must be a Rotation instance.")
orientation = self.outer(orientation)

v = orientation * Vector3d.zvector()
# separate vectors on upper/lower hemisphere
v_upper = v[v.z >= 0]
v_lower = v[v.z < 0]
# reproject vectors on lower hemisphere to upper hemisphere
v_lower.z *= -1

kwargs.setdefault("return_figure", False)
return_figure = kwargs.pop("return_figure")

kwargs.setdefault("s", 100)
kwargs.setdefault("ec", "tab:blue")

figure = v_upper.scatter(
hakonanes marked this conversation as resolved.
Show resolved Hide resolved
marker=markers[0],
return_figure=True,
axes_labels=("a", "b", None),
label="upper",
**kwargs,
)
v_lower.scatter(
marker=markers[1], fc="None", figure=figure, label="lower", **kwargs
)
# add symmetry name to figure title
figure.suptitle(f"${self.name}$")

if return_figure:
return figure


# Triclinic
C1 = Symmetry((1, 0, 0, 0))
Expand Down
28 changes: 28 additions & 0 deletions orix/tests/quaternion/test_symmetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from copy import deepcopy

from diffpy.structure.spacegroups import GetSpaceGroup
from matplotlib import pyplot as plt
import numpy as np
import pytest

Expand Down Expand Up @@ -406,6 +407,33 @@ def test_hash_persistence():
assert all(h1a == h2a for h1a, h2a in zip(h1, h2))


@pytest.mark.parametrize("symmetry", [C1, C4, Oh])
def test_symmetry_plot(symmetry):
figure = symmetry.plot(return_figure=True)
assert isinstance(figure, plt.Figure)
assert len(figure.axes) == 1
ax = figure.axes[0]
num = 1 if symmetry.is_proper else 2
assert len(ax.collections) == num
c0 = ax.collections[0]
assert len(c0.get_sizes()) == np.count_nonzero(~symmetry.improper)
assert c0.get_label().lower() == "upper"
if num > 1:
c1 = ax.collections[1]
assert len(c1.get_sizes()) == np.count_nonzero(symmetry.improper)
assert c1.get_label().lower() == "lower"
assert len(ax.texts) == 2
assert ax.texts[0].get_text() == "a"
assert ax.texts[1].get_text() == "b"
plt.close("all")


@pytest.mark.parametrize("symmetry", [C1, C4, Oh])
def test_symmetry_plot_raises(symmetry):
with pytest.raises(TypeError, match="Orientation must be a Rotation instance"):
_ = symmetry.plot(return_figure=True, orientation="test")


class TestFundamentalSectorFromSymmetry:
"""Test the normals, vertices and centers of the fundamental sector
for all 32 crystallographic point groups.
Expand Down