{ "cells": [ { "cell_type": "markdown", "id": "6bcf8e50", "metadata": {}, "source": [ "# Examples\n", "\n", "Let's start by importing the {py:class}`quatorch.Quaternion` class:" ] }, { "cell_type": "code", "execution_count": null, "id": "3a61013c", "metadata": { "tags": [ "remove-output" ] }, "outputs": [], "source": [ "from quatorch import Quaternion" ] }, { "cell_type": "markdown", "id": "2806da51", "metadata": {}, "source": [ "There are two main ways to initialize a quaternion: \n", "- from four scalars representing WXYZ components\n", "- from a tensor that has shape $(..., 4)$\n", "\n", "Here are two ways to define a $45$-degree rotation around the X axis:" ] }, { "cell_type": "code", "execution_count": null, "id": "c85ece73", "metadata": {}, "outputs": [], "source": [ "import torch\n", "\n", "\n", "# Create a quaternion from four scalars (W, X, Y, Z)\n", "q = Quaternion(0.9239, 0.3827, 0.0, 0.0)\n", "\n", "# Or from a tensor of shape (..., 4)\n", "q2 = Quaternion(torch.tensor([0.9239, 0.3827, 0.0, 0.0]))\n", "\n", "print(f\"{(q == q2) = }\")" ] }, { "cell_type": "markdown", "id": "447056ef", "metadata": {}, "source": [ "Both methods create the same quaternion. Notice that element-wise comparison was performed, as a quaternion is still a subclass of `torch.Tensor`. Let's inspect some quaternion operations as multiplication and exponentiation:" ] }, { "cell_type": "code", "execution_count": null, "id": "f0d9c5e1", "metadata": {}, "outputs": [], "source": [ "q = q.normalize()\n", "\n", "print(f\"{q*q*q*q = }\")\n", "print(f\"{q**4 = }\")\n", "print(f\"{(4 * q.log()).exp() = }\")\n", "print(f\"{-q**(-4) = }\")" ] }, { "cell_type": "markdown", "id": "b086eb3d", "metadata": {}, "source": [ "As expected, all the above returned the same result, which represents a 180 degrees rotation around the X axis. We can verify that by converting it to an axis-angle representation:" ] }, { "cell_type": "code", "execution_count": null, "id": "4b84596b", "metadata": {}, "outputs": [], "source": [ "axis, angle = (q**4).to_axis_angle()\n", "print(f\"Axis: {axis}, Angle (degrees): {angle.rad2deg()}\")" ] }, { "cell_type": "markdown", "id": "2e8f3e99", "metadata": {}, "source": [ "Or a rotation matrix:" ] }, { "cell_type": "code", "execution_count": null, "id": "397b9997", "metadata": {}, "outputs": [], "source": [ "q.to_rotation_matrix()" ] }, { "cell_type": "markdown", "id": "f8b886a3", "metadata": {}, "source": [ "
\n", "
Tip
\n", "

You may also construct quaternions from rotation matrices or an axis-angle representation using the methods {any}`quatorch.Quaternion.from_rotation_matrix` and {any}`quatorch.Quaternion.from_axis_angle`

\n", "
\n", "\n", "\n", "One interesting property of quaternions is that they take value on a 4D hyper-sphere. This means that we can interpolate between two quaternions using spherical linear interpolation ({any}`quatorch.Quaternion.slerp`). Let's see how it works:" ] }, { "cell_type": "code", "execution_count": null, "id": "2d772be1", "metadata": {}, "outputs": [], "source": [ "q_final = q**4\n", "t = 1 / 3 # interpolation parameter in [0, 1]\n", "\n", "q.slerp(q_final, t)" ] }, { "cell_type": "markdown", "id": "94006cc5", "metadata": {}, "source": [ "Notice that this (and all other operations documented in the [API reference](project:api.md)) supports broadcasting. Thus, we can work with batches of quaternions seamlessly. In the example above, let's use `t` as a tensor of shape (4, 1) to get a batch of interpolated quaternions:" ] }, { "cell_type": "code", "execution_count": null, "id": "675e7c87", "metadata": {}, "outputs": [], "source": [ "t = torch.linspace(0, 1, steps=4).unsqueeze(-1)\n", "\n", "q.slerp(q_final, t)" ] }, { "cell_type": "markdown", "id": "fc6c1427", "metadata": {}, "source": [ "Finally let's use this batch of quaternions to rotate a vector:" ] }, { "cell_type": "code", "execution_count": null, "id": "752d8d60", "metadata": {}, "outputs": [], "source": [ "t = torch.linspace(0, 1, steps=10).unsqueeze(-1)\n", "\n", "batch_q = q.slerp(q_final, t)\n", "batch_q.rotate_vector(torch.tensor([1.0, 0.0, 0.0]))" ] }, { "cell_type": "markdown", "id": "8d53a18f", "metadata": {}, "source": [ "Unfortunately, rotating $[1, 0, 0]$ around the X axis does not change it. Let's try with another vector:" ] }, { "cell_type": "code", "execution_count": null, "id": "96fa41f5", "metadata": {}, "outputs": [], "source": [ "rotated_vectors = batch_q.rotate_vector(torch.tensor([0.0, 1.0, -1.0]))" ] }, { "cell_type": "markdown", "id": "3cc45164", "metadata": {}, "source": [ "To better illustrate it, let's plot the projection of these vectors on the YZ plane, to visualize the rotation:" ] }, { "cell_type": "code", "execution_count": null, "id": "13fecb51", "metadata": {}, "outputs": [], "source": [ "from matplotlib import pyplot as plt\n", "\n", "plt.scatter(rotated_vectors[:, 1], rotated_vectors[:, 2])\n", "plt.axis(\"equal\")\n", "plt.xlabel(\"Y\")\n", "plt.ylabel(\"Z\")\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "quatorch", "language": "python", "name": "python3" }, "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.13.3" } }, "nbformat": 4, "nbformat_minor": 5 }