Skip to content

Commit e5c307f

Browse files
committed
including suggestions and updating example
1 parent 269996c commit e5c307f

File tree

4 files changed

+169
-75
lines changed

4 files changed

+169
-75
lines changed
Lines changed: 85 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,115 @@
11
r"""
2-
========================
3-
Plot symmetry operations
4-
========================
2+
========================================
3+
Plot Paths Through Non-Euclidean Spaces
4+
========================================
55
6-
This example shows how to use the `from_path_ends` functions from
7-
:class:`~orix.vector.Vector3d`, :class:`~orix.quaternions.Rotation`, and
8-
:class:`~orix.quaternions.Orientation` to draw paths through thier
9-
respective non-Euclidean spaces.
6+
This example shows three variations on how 'from_path_ends' can be
7+
used to plot paths between points in rotational and vector spaces.
8+
9+
This functionality is available in :class:`~orix.vector.Vector3d`,
10+
:class:`~orix.quaternions.Rotation`,
11+
:class:`~orix.quaternions.Orientation`,
12+
and :class:`~orix.quaternions.Misorientation`.
1013
"""
1114

1215
import matplotlib.pyplot as plt
16+
from matplotlib import cm
1317
import numpy as np
1418

15-
from orix.quaternion import Orientation, Rotation
19+
from orix.quaternion import Misorientation, Orientation, Rotation
1620
from orix.quaternion.symmetry import D3, Oh
1721
from orix.vector import Vector3d
1822

19-
fig = plt.figure()
23+
fig = plt.figure(figsize=(4, 8))
2024

21-
# plot a path in homochoric space with no symmetry
22-
rot_path = Rotation(
25+
# ========= #
26+
# Example 1: Plotting a path of rotations with no symmetry in homochoric space
27+
# ========= #
28+
rots_along_path = Rotation(
2329
data=np.array(
2430
[
2531
[1, 0, 0, 0],
26-
[1, 1, 0, 0],
27-
[1, 0, 1, 0],
2832
[1, 0, 0, 1],
29-
[1, 0, -1, 0],
30-
[1, 0, 0, -1],
31-
[1, 0, 0, -1],
33+
[1, 1, 1, 1],
3234
]
3335
)
3436
)
35-
rotation_path = Rotation.from_path_ends(rot_path, closed=True)
36-
# cast the rotation to a symmetry-less orientation for plotting purposes
37-
Orientation(rotation_path).scatter(
38-
figure=fig, position=[2, 2, 1], marker=">", c=np.arange(700)
39-
)
37+
n_steps = 20
38+
rotation_path = Rotation.from_path_ends(rots_along_path, steps=n_steps)
39+
# create an Orientation loop using this path with no symmetry elements
40+
ori_path = Orientation(rotation_path)
41+
# plot the path in homochoric space
42+
segment_colors = cm.inferno(np.linspace(0, 1, n_steps))
4043

41-
# plot a path in rodrigues space with m-3m (cubic) symmetry.
42-
m3m_path = Orientation(
44+
path_colors = np.vstack([segment_colors for x in range(rots_along_path.size - 1)])
45+
ori_path.scatter(figure=fig, position=[3, 1, 1], marker=">", c=path_colors)
46+
fig.axes[0].set_title(r"$90^\circ$ rotation around X, then Y")
47+
48+
# ========= #
49+
# Example 2: Plotting the rotation of several orientations in m3m Rodrigues
50+
# space around the z axis.
51+
# ========= #
52+
oris = Orientation(
4353
data=np.array(
4454
[
45-
[1, 0, 0, 0],
46-
[2, 1, 0, 0],
47-
[3, 0, 1, 0],
48-
[4, 0, 0, 1],
49-
[5, 0, -1, 0],
50-
[6, 0, 0, -1],
51-
[7, 0, 0, -1],
52-
[8, 1, 0, 0],
53-
[9, 0, 1, 0],
54-
[10, 0, 0, 1],
55-
[11, 0, -1, 0],
56-
[12, 0, 0, -1],
57-
[13, 0, 0, -1],
55+
[0.69, 0.24, 0.68, 0.01],
56+
[0.26, 0.59, 0.32, 0.7],
57+
[0.07, 0.17, 0.93, 0.31],
58+
[0.6, 0.03, 0.61, 0.52],
59+
[0.51, 0.38, 0.34, 0.69],
60+
[0.31, 0.86, 0.22, 0.35],
61+
[0.68, 0.67, 0.06, 0.31],
62+
[0.01, 0.12, 0.05, 0.99],
63+
[0.39, 0.45, 0.34, 0.72],
64+
[0.65, 0.59, 0.46, 0.15],
5865
]
5966
),
6067
symmetry=Oh,
68+
).reduce()
69+
# define a 20 degree rotation around the z axis
70+
shift = Orientation.from_axes_angles([0, 0, 1], np.pi / 9)
71+
segment_colors = cm.inferno(np.linspace(0, 1, 10))
72+
73+
ori_paths = []
74+
for ori in oris:
75+
shifted = (shift * ori).reduce()
76+
to_from = Orientation.stack([ori, shifted]).flatten()
77+
ori_paths.append(Orientation.from_path_ends(to_from, steps=10))
78+
# plot a path in roddrigues space with m-3m (cubic) symmetry.
79+
ori_path = Orientation.stack(ori_paths).flatten()
80+
ori_path.symmetry = Oh
81+
ori_path.scatter(
82+
figure=fig,
83+
position=[3, 1, 2],
84+
marker=">",
85+
c=np.tile(segment_colors, [10, 1]),
86+
projection="rodrigues",
6187
)
62-
orientation_path = Orientation.from_path_ends(m3m_path.reduce(), closed=True).reduce()
63-
orientation_path.scatter(figure=fig, position=[2, 2, 2], marker=">", c=np.arange(1300))
88+
fig.axes[1].set_title(r"$20^{\circ}$ rotations around X-axis in m3m")
6489

65-
# plot a second path in rodrigues space with symmetry, but while also crossing a
66-
# symmetry boundary
67-
fiber_start = Rotation.identity(1)
68-
fiber_middle = Rotation.from_axes_angles([1, 2, 3], np.pi)
69-
fiber_end = Rotation.from_axes_angles([1, 2, 3], 2 * np.pi)
70-
fiber_points = Orientation.stack([fiber_start, fiber_middle, fiber_end])
71-
fiber_points.symmetry = Oh
72-
fiber_path = Orientation.from_path_ends(fiber_points, closed=False).reduce()
73-
fiber_path.scatter(figure=fig, position=[2, 2, 3], marker=">", c=np.arange(200))
90+
# ========= #
91+
# Example 3: creating a customized Wulf Plotting the rotation of several orientations in m3m Rodrigues
92+
# space around the z axis.
93+
# ========= #
7494

7595

7696
# plot vectors
77-
ax4 = plt.subplot(2, 2, 4, projection="stereographic")
78-
vector_points = Vector3d(
79-
np.array([[-1, 0, 0], [0, 1, 0.1], [1, 0, 0.2], [0, -1, 0.3], [-1, 0, 0.4]])
80-
)
97+
ax_upper = plt.subplot(3, 1, 3, projection="stereographic", hemisphere="upper")
98+
r90x = Rotation.from_axes_angles([1, -1, -1], [0, 60], degrees=True)
99+
x_axis_points = r90x * Vector3d.xvector()
100+
y_axis_points = r90x * Vector3d.yvector()
101+
z_axis_points = r90x * Vector3d.zvector()
102+
103+
x_axis_path = Vector3d.from_path_ends(x_axis_points.unique())
104+
y_axis_path = Vector3d.from_path_ends(y_axis_points.unique())
105+
z_axis_path = Vector3d.from_path_ends(z_axis_points.unique())
106+
cx = cm.Reds(np.linspace(0.1, 1, x_axis_path.size))
107+
cy = cm.Greens(np.linspace(0.1, 1, y_axis_path.size))
108+
cz = cm.Blues(np.linspace(0.1, 1, z_axis_path.size))
109+
110+
spx = ax_upper.scatter(x_axis_path, figure=fig, marker=">", c=cx, label="X")
111+
spy = ax_upper.scatter(y_axis_path, figure=fig, marker=">", c=cy, label="Y")
112+
spz = ax_upper.scatter(z_axis_path, figure=fig, marker=">", c=cz, label="Z")
113+
ax_upper.legend(loc="lower center", ncols=3)
81114

82-
vector_path = Vector3d.from_path_ends(vector_points, steps=200)
83-
ax4.scatter(vector_path, figure=fig, marker=">", c=np.arange(vector_path.size))
115+
plt.tight_layout()

orix/quaternion/misorientation.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,31 +270,35 @@ def from_scipy_rotation(
270270
def from_path_ends(
271271
cls, points: Misorientation, closed: bool = False, steps: int = 100
272272
) -> Misorientation:
273-
"""Return (mis)orientations tracing the shortest path between
273+
"""Return misorientations tracing the shortest path between
274274
two or more consecutive points.
275275
276276
Parameters
277277
----------
278278
points
279-
Two or more (mis)orientations that define waypoints along
279+
Two or more misorientations that define waypoints along
280280
a path through rotation space (SO3).
281281
closed
282282
Option to add a final trip from the last waypoint back to
283283
the first, thus closing the loop. The default is False.
284284
steps
285-
Number of (mis)orientations to return along the path
285+
Number of misorientations to return along the path
286286
between each pair of waypoints. The default is 100.
287287
288288
Returns
289289
-------
290290
path
291-
quaternions that map a path between the given waypoints.
291+
misorientations that map a path between the given waypoints.
292292
"""
293+
# Confirm `points` are (mis)orientations.
294+
if not isinstance(points, Misorientation):
295+
raise TypeError(
296+
f"Points must be a Misorientation, not of type {type(points)}"
297+
)
298+
# Create a path through Quaternion space, then reapply the symmetry.
293299
out = super().from_path_ends(points=points, closed=closed, steps=steps)
294300
path = cls(out.data)
295-
# copy the symmetry if it exists.
296-
if hasattr(points, "_symmetry"):
297-
path._symmetry = points._symmetry
301+
path._symmetry = points._symmetry
298302
return path
299303

300304
@classmethod

orix/quaternion/orientation.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,41 @@ def from_scipy_rotation(
346346
O.symmetry = symmetry
347347
return O
348348

349+
@classmethod
350+
def from_path_ends(
351+
cls, points: Orientation, closed: bool = False, steps: int = 100
352+
) -> Misorientation:
353+
"""Return orientations tracing the shortest path between
354+
two or more consecutive points.
355+
356+
Parameters
357+
----------
358+
points
359+
Two or more orientations that define waypoints along
360+
a path through rotation space (SO3).
361+
closed
362+
Option to add a final trip from the last waypoint back to
363+
the first, thus closing the loop. The default is False.
364+
steps
365+
Number of orientations to return along the path
366+
between each pair of waypoints. The default is 100.
367+
368+
Returns
369+
-------
370+
path
371+
orientations that map a path between the given waypoints.
372+
"""
373+
# Confirm `points` are orientations.
374+
if not isinstance(points, Orientation):
375+
raise TypeError(
376+
f"Points must be an Orientation instance, not of type {type(points)}"
377+
)
378+
# Create a path through Quaternion space, then reapply the symmetry.
379+
out = super().from_path_ends(points=points, closed=closed, steps=steps)
380+
path = cls(out.data)
381+
path._symmetry = points._symmetry
382+
return path
383+
349384
@classmethod
350385
def random(
351386
cls, shape: Union[int, tuple] = 1, symmetry: Optional[Symmetry] = None

orix/tests/quaternion/test_orientation.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -333,25 +333,48 @@ def test_from_path_ends():
333333
o = Orientation.random(10, Oh)
334334
m = Misorientation.random(10, [D3, Oh])
335335

336-
# make sure the result is the correct class.
336+
# Quaternion sanity checks
337337
a = Quaternion.from_path_ends(q)
338-
b = Rotation.from_path_ends(q)
339-
c = Orientation.from_path_ends(r)
340-
d = Quaternion.from_path_ends(o)
341-
e = Orientation.from_path_ends(q)
342-
f = Misorientation.from_path_ends(m)
343-
g = Orientation.from_path_ends(o)
344338
assert isinstance(a, Quaternion)
345-
assert isinstance(b, Rotation)
346-
assert isinstance(c, Orientation)
339+
b = Quaternion.from_path_ends(r)
340+
assert isinstance(b, Quaternion)
341+
c = Quaternion.from_path_ends(o)
342+
assert isinstance(c, Quaternion)
343+
d = Quaternion.from_path_ends(m)
347344
assert isinstance(d, Quaternion)
348-
assert isinstance(e, Orientation)
349-
assert isinstance(f, Misorientation)
350-
assert isinstance(g, Orientation)
351345

352-
# make sure symmetry information is preserved.
353-
assert f.symmetry == m.symmetry
354-
assert g.symmetry == o.symmetry
346+
# Rotation sanity checks
347+
a = Rotation.from_path_ends(q)
348+
assert isinstance(a, Rotation)
349+
b = Rotation.from_path_ends(r)
350+
assert isinstance(b, Rotation)
351+
c = Rotation.from_path_ends(o)
352+
assert isinstance(c, Rotation)
353+
d = Rotation.from_path_ends(m)
354+
assert isinstance(d, Rotation)
355+
356+
# Misorientation sanity checks
357+
with pytest.raises(TypeError, match="Points must be a Misorientation"):
358+
a = Misorientation.from_path_ends(q)
359+
with pytest.raises(TypeError, match="Points must be a Misorientation"):
360+
b = Misorientation.from_path_ends(r)
361+
c = Misorientation.from_path_ends(o)
362+
assert isinstance(c, Misorientation)
363+
d = Misorientation.from_path_ends(m)
364+
assert isinstance(d, Misorientation)
365+
assert c.symmetry[1] == o.symmetry
366+
assert d.symmetry == m.symmetry
367+
368+
# Orientation sanity checks
369+
with pytest.raises(TypeError, match="Points must be an Orientation"):
370+
a = Orientation.from_path_ends(q)
371+
with pytest.raises(TypeError, match="Points must be an Orientation"):
372+
b = Orientation.from_path_ends(r)
373+
c = Orientation.from_path_ends(o)
374+
assert c.symmetry == o.symmetry
375+
assert isinstance(c, Orientation)
376+
with pytest.raises(TypeError, match="Points must be an Orientation"):
377+
d = Orientation.from_path_ends(m)
355378

356379

357380
class TestMisorientation:

0 commit comments

Comments
 (0)