Skip to content

Commit 664439f

Browse files
committed
update changelog and cleanup example for from_path_ends
1 parent a3ffb06 commit 664439f

File tree

5 files changed

+134
-77
lines changed

5 files changed

+134
-77
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Added
3434
An example of a custom projection is the :class:`~orix.plot.StereographicPlot`.
3535
This function replaces the previous behavior of relying on a side-effect of importing
3636
the :mod:`orix.plot` module, which also registered the projections.
37+
- :func:`~orix.quaternion.Rotation.from_path_ends` returns evenly spaced points
38+
mapping the shortest path betwen two or more rotations.
3739

3840
Changed
3941
-------

examples/plotting/paths_through_orientation_space.py renamed to examples/plotting/visualizing_crystallographic_paths.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
r"""
22
=========================================
3-
Plot Paths In Rotation and Vector Space
3+
Visualizing crystallographic paths
44
=========================================
5-
This example shows how paths though either rotation or vector space
6-
can be plotted using ORIX. These are the shortest paths through their
7-
respective spaces, and thus not always straight lines in euclidean
8-
projections (axis-angle, stereographic, etc.).
5+
6+
This example shows how define and plot paths through either
7+
rotation or vector space.This is akin to describing crystallographic
8+
fiber textures in metallurgy, or the shortest arcs connecting points on
9+
the surface of a unit sphere.
10+
11+
In both cases, "shortest" is defined as the route that minimizes the
12+
movement required to transform from point to point, which is typically
13+
not a stright line when plotted into a euclidean projection
14+
(axis-angle, stereographic, etc.).
15+
916
This functionality is available in :class:`~orix.vector.Vector3d`,
1017
:class:`~orix.quaternions.Rotation`,
1118
:class:`~orix.quaternions.Orientation`,
12-
and :class:`~orix.quaternions.Misorientation`.
13-
"""
19+
and :class:`~orix.quaternions.Misorientation`."""
1420

1521
from matplotlib import cm
1622
import matplotlib.pyplot as plt
@@ -31,16 +37,18 @@
3137
fig = plt.figure(figsize=(6, 6))
3238
n_steps = 30
3339

34-
# ========= #
40+
# ============ #
3541
# Example 1: Plotting multiple paths into a user defined axis
36-
# ========= #
42+
3743
# This subplot shows several paths through the cubic (m3m) fundamental zone
3844
# created by rotating 20 randomly chosen points 30 degrees around the z axis.
3945
# these paths are drawn in rodrigues space, which is an equal-angle projection
4046
# of rotation space. As such, notice how all lines tracing out axial rotations
4147
# are straight, but lines starting closer to the center of the fundamental zone
4248
# appear shorter.
49+
4350
# the sampe paths are then also plotted on an Inverse Pole Figure (IPF) plot.
51+
4452
rod_ax = fig.add_subplot(2, 2, 1, projection="rodrigues", proj_type="ortho")
4553
ipf_ax = fig.add_subplot(2, 2, 2, projection="ipf", symmetry=Oh)
4654

@@ -86,20 +94,22 @@
8694
ipf_ax.set_title(r"IPF, multiple paths ")
8795

8896

89-
# %%
90-
# ========= #
97+
# ============ #
9198
# Example 2: Plotting a path using `Rotation.scatter'
92-
# ========= #
9399
# This subplot traces the path of an object rotated 90 degrees around the
94100
# X axis, then 90 degrees around the Y axis.
101+
95102
rots = Orientation.from_axes_angles(
96103
[[1, 0, 0], [1, 0, 0], [0, 1, 0]], [0, 90, 90], degrees=True, symmetry=C1
97104
)
98105
rots[2] = rots[1] * rots[2]
99106
path = Orientation.from_path_ends(rots, steps=n_steps)
100107
# create a list of RGBA color values for a gradient red line and blue line
101108
path_colors = np.vstack(
102-
[cm.Reds(np.linspace(0.5, 1, n_steps)), cm.Blues(np.linspace(0.5, 1, n_steps))]
109+
[
110+
cm.Reds(np.linspace(0.5, 1, n_steps)),
111+
cm.Blues(np.linspace(0.5, 1, n_steps)),
112+
]
103113
)
104114

105115
# Here, we instead use the in-built plotting tool from
@@ -108,11 +118,10 @@
108118
path.scatter(figure=fig, position=[2, 2, 3], marker=">", c=path_colors)
109119
fig.axes[2].set_title(r"Axis-Angle, two $90^\circ$ rotations")
110120

111-
# %%
112121

113-
# ========= #
122+
# ============ #
114123
# Example 3: paths in stereographic plots
115-
# ========= #
124+
116125
# This is similar to the second example, but now vectors are being rotated
117126
# 30 degrees around the [1,1,1] axis on a stereographic plot.
118127

orix/quaternion/misorientation.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ def symmetry(self) -> tuple[Symmetry, Symmetry]:
8484
return self._symmetry
8585

8686
@symmetry.setter
87-
def symmetry(self, value: list[Symmetry] | tuple[Symmetry, Symmetry]) -> None:
87+
def symmetry(
88+
self, value: list[Symmetry] | tuple[Symmetry, Symmetry]
89+
) -> None:
8890
if not isinstance(value, (list, tuple)):
8991
raise TypeError("Value must be a 2-tuple of Symmetry objects.")
9092
if len(value) != 2 or not all(isinstance(s, Symmetry) for s in value):
@@ -277,41 +279,43 @@ def from_scipy_rotation(
277279
def from_path_ends(
278280
cls, points: Misorientation, closed: bool = False, steps: int = 100
279281
) -> Misorientation:
280-
"""Return misorientations tracing the shortest path (ignoring
281-
symmetry) between two or more consecutive points.
282+
"""Return misorientations tracing the shortest path between
283+
two or more consecutive points.
282284
283285
Parameters
284286
----------
285287
points
286-
Two or more misorientations that define waypoints along
287-
a path through rotation space (SO3).
288+
Two or more misorientations that define points along the
289+
path.
288290
closed
289-
Option to add a final trip from the last waypoint back to
291+
Option to add a final trip from the last point back to
290292
the first, thus closing the loop. The default is False.
291293
steps
292-
Number of misorientations to return along the path
293-
between each pair of waypoints. The default is 100.
294+
Number of misorientations to return between each point
295+
along the path defined by `points`. The default is 100.
294296
295297
Returns
296298
-------
297299
path
298-
misorientations that map a path between the given waypoints.
300+
regularly spaced misorientations following the shortest
301+
path.
299302
300-
Note
301-
-------
302-
This function traces a path between points in SO(3), in which there
303-
is one and only one direct path between every point. The equivalent
304-
is not well-defined for misorientations, which define multiple
305-
symmetrically-equivalent points in SO(3) with non-equivalent paths
306-
between them.
303+
Notes
304+
-----
305+
This function traces the shortest path between points without
306+
any regard to symmetry. Concept of "shortest path" is not
307+
well-defined for misorientations, which can define multiple
308+
symmetrically equivalent points with non-equivalent paths.
307309
"""
308310
# Confirm `points` are misorientations.
309311
if type(points) is not cls:
310312
raise TypeError(
311313
f"Points must be a Misorientation, not of type {type(points)}"
312314
)
313315
# Create a path through Quaternion space, then reapply the symmetry.
314-
out = Rotation.from_path_ends(points=points, closed=closed, steps=steps)
316+
out = Rotation.from_path_ends(
317+
points=points, closed=closed, steps=steps
318+
)
315319
return cls(out.data, symmetry=points.symmetry)
316320

317321
@classmethod
@@ -390,7 +394,9 @@ def equivalent(self, grain_exchange: bool = False) -> Misorientation:
390394
return self.__class__(equivalent).flatten()
391395

392396
@deprecated(since="0.14", removal="0.15", alternative="reduce")
393-
def map_into_symmetry_reduced_zone(self, verbose: bool = False) -> Misorientation:
397+
def map_into_symmetry_reduced_zone(
398+
self, verbose: bool = False
399+
) -> Misorientation:
394400
"""Return equivalent transformations which have the smallest
395401
angle of rotation as a new misorientation.
396402
@@ -549,7 +555,9 @@ def scatter(
549555
ax._correct_aspect_ratio(fundamental_zone)
550556

551557
ax.axis("off")
552-
figure.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0, wspace=0)
558+
figure.subplots_adjust(
559+
left=0, right=1, bottom=0, top=1, hspace=0, wspace=0
560+
)
553561

554562
if size is not None:
555563
to_plot = self.get_random_sample(size)

orix/quaternion/orientation.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ def symmetry(self) -> Symmetry:
7575
@symmetry.setter
7676
def symmetry(self, value: Symmetry) -> None:
7777
if not isinstance(value, Symmetry):
78-
raise TypeError("Value must be an instance of orix.quaternion.Symmetry.")
78+
raise TypeError(
79+
"Value must be an instance of orix.quaternion.Symmetry."
80+
)
7981
self._symmetry = (C1, value)
8082

8183
@property
@@ -356,39 +358,40 @@ def from_scipy_rotation(
356358
def from_path_ends(
357359
cls, points: Orientation, closed: bool = False, steps: int = 100
358360
) -> Misorientation:
359-
"""Return orientations tracing the shortest path (ignoring
360-
symmetry) between two or more consecutive points.
361+
"""Return orientations tracing the shortest path between two
362+
or more consecutive points.
361363
362364
Parameters
363365
----------
364366
points
365-
Two or more orientations that define waypoints along
366-
a path through rotation space (SO3).
367+
Two or more orientations that define points along the
368+
path.
367369
closed
368-
Option to add a final trip from the last waypoint back to
370+
Option to add a final trip from the last point back to
369371
the first, thus closing the loop. The default is False.
370372
steps
371-
Number of orientations to return along the path
372-
between each pair of waypoints. The default is 100.
373+
Number of orientations to return between each point
374+
along the path defined by `points`. The default is 100.
373375
374376
Returns
375377
-------
376378
path
377-
orientations that map a path between the given waypoints.
379+
regularly spaced orientations following the shortest path.
378380
379-
Note
380-
-------
381-
This function traces a path between points in SO(3), in which there
382-
is one and only one direct path between every point. The equivalent
383-
is not well-defined for orientations, which define multiple
384-
symmetrically-equivalent points in SO(3) with non-equivalent paths
385-
between them.
381+
Notes
382+
-----
383+
This function traces the shortest path between points without
384+
any regard to symmetry. Concept of "shortest path" is not
385+
well-defined for orientations, which can define multiple
386+
symmetrically equivalent points with non-equivalent paths.
386387
"""
387388
if type(points) is not cls:
388389
raise TypeError(
389390
f"Points must be an Orientation instance, not of type {type(points)}"
390391
)
391-
out = Rotation.from_path_ends(points=points, closed=closed, steps=steps)
392+
out = Rotation.from_path_ends(
393+
points=points, closed=closed, steps=steps
394+
)
392395
return cls(out.data, symmetry=points.symmetry)
393396

394397
@classmethod
@@ -417,7 +420,9 @@ def random(
417420

418421
# --------------------- Other public methods --------------------- #
419422

420-
def angle_with(self, other: Orientation, degrees: bool = False) -> np.ndarray:
423+
def angle_with(
424+
self, other: Orientation, degrees: bool = False
425+
) -> np.ndarray:
421426
"""Return the smallest symmetry reduced angles of rotation
422427
transforming the orientations to the other orientations.
423428
@@ -707,7 +712,9 @@ def plot_unit_cell(
707712
If :attr:`size` > 1.
708713
"""
709714
if self.size > 1:
710-
raise ValueError("Can only plot a single unit cell, so *size* must be 1")
715+
raise ValueError(
716+
"Can only plot a single unit cell, so *size* must be 1"
717+
)
711718

712719
from orix.plot.unit_cell_plot import _plot_unit_cell
713720

@@ -748,8 +755,12 @@ def in_euler_fundamental_region(self) -> np.ndarray:
748755

749756
# Find the first triplet among the symmetrically equivalent ones
750757
# inside the fundamental region
751-
max_alpha, max_beta, max_gamma = np.radians(pg.euler_fundamental_region)
752-
is_inside = (alpha <= max_alpha) * (beta <= max_beta) * (gamma <= max_gamma)
758+
max_alpha, max_beta, max_gamma = np.radians(
759+
pg.euler_fundamental_region
760+
)
761+
is_inside = (
762+
(alpha <= max_alpha) * (beta <= max_beta) * (gamma <= max_gamma)
763+
)
753764
first_nonzero = np.argmax(is_inside, axis=1)
754765

755766
euler_in_region = np.column_stack(
@@ -764,7 +775,9 @@ def in_euler_fundamental_region(self) -> np.ndarray:
764775

765776
def scatter(
766777
self,
767-
projection: Literal["axangle", "rodrigues", "homochoric", "ipf"] = "axangle",
778+
projection: Literal[
779+
"axangle", "rodrigues", "homochoric", "ipf"
780+
] = "axangle",
768781
figure: mfigure.Figure | None = None,
769782
position: int | tuple[int, int, int] | SubplotSpec | None = None,
770783
return_figure: bool = False,
@@ -890,7 +903,9 @@ def inv(self) -> Orientation:
890903

891904
# -------------------- Other private methods --------------------- #
892905

893-
def _dot_outer_dask(self, other: Orientation, chunk_size: int = 20) -> da.Array:
906+
def _dot_outer_dask(
907+
self, other: Orientation, chunk_size: int = 20
908+
) -> da.Array:
894909
"""Symmetry reduced dot product of every orientation in this
895910
instance to every orientation in another instance, returned as a
896911
Dask array.

0 commit comments

Comments
 (0)