Skip to content

Commit

Permalink
Add a brief tutorial on using clifford.transformations (#307)
Browse files Browse the repository at this point in the history
Thanks to @arsenovic for the suggestion.
  • Loading branch information
eric-wieser committed Apr 9, 2020
1 parent 81e93ae commit f1e2677
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 6 deletions.
19 changes: 13 additions & 6 deletions clifford/transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,35 @@
.. versionadded:: 1.3.0
This module provides some base classes:
This module may in future become the home for optimized rotor transformations,
or non-linear transformations.
See the :ref:`/tutorials/linear-transformations.ipynb` tutorial for an
introduction to how to use parts of this module.
Base classes
------------
.. autodata:: Transformation
.. autoclass:: Linear
.. autoclass:: FixedLayout
And some matrix-backed implementations:
Matrix-backed implementations
-----------------------------
.. autoclass:: LinearMatrix
:members:
.. autoclass:: OutermorphismMatrix
It also provides a helper function for the most common use case, converting
between basis vectors of similar algebras:
Helper functions
----------------
.. autofunction:: between_basis_vectors
This module may in future become the home for optimized rotor transformations,
or non-linear transformations
"""
from typing import Dict, Any, Callable
from abc import abstractmethod
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Scalars, vectors, and higher-grade entities can be mixed freely and consistently
tutorials/InterfacingOtherMathSystems
tutorials/PerformanceCliffordTutorial
tutorials/cga/index
tutorials/linear-transformations
tutorials/apollonius-cga-augmented

.. toctree::
Expand Down
233 changes: 233 additions & 0 deletions docs/tutorials/linear-transformations.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Linear transformations\n",
"\n",
"When working in regular vector spaces, a common tool is a linear transformation, typically in the form of a matrix.\n",
"\n",
"While geometric algebra already provides the rotors as a means of describing transformations (see [the CGA tutorial section](cga/index.ipynb#Operations)), there are types of linear transformation that are not suitable for this representation.\n",
"\n",
"This tutorial leans heavily on the explanation of linear transformations in GA4CS, chapter 4. It explores the [clifford.transformations](../api/transformations.rst) submodule."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Vector transformations in linear algebra\n",
"\n",
"As a brief reminder, we can represent transforms in $\\mathbb{R}^3$ using the matrices in $\\mathbb{R}^{3 \\times 3}$:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"rot_and_scale_x = np.array([\n",
" [1, 0, 0],\n",
" [0, 1, -1],\n",
" [0, 1, 1],\n",
"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can read this as a table, where each column corresponds to a component of the input vector, and each row a component of the output:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def show_table(data, cols, rows):\n",
" # trick to get a nice looking table in a notebook\n",
" import pandas as pd; return pd.DataFrame(data, columns=cols, index=rows)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"show_table(rot_and_scale_x, [\"$\\mathit{in}_%s$\" % c for c in \"xyz\"], [\"$\\mathit{out}_%s$\" % c for c in \"xyz\"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can apply it to some vectors using the `@` matrix multiply operator:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"v1 = np.array([1, 0, 0])\n",
"v2 = np.array([0, 1, 0])\n",
"v3 = np.array([0, 0, 1])\n",
"\n",
"(\n",
" rot_and_scale_x @ v1,\n",
" rot_and_scale_x @ v2,\n",
" rot_and_scale_x @ v3,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We say this transformation is linear because $f(a + b) = f(a) + f(b)$: "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert np.array_equal(\n",
" rot_and_scale_x @ (2*v1 + 3*v2),\n",
" 2 * (rot_and_scale_x @ v1) + 3 * (rot_and_scale_x @ v2)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Multivector transformations in geometric algebra\n",
"\n",
"How would we go about applying `rot_and_scale_x` in a geometric algebra? Clearly we can apply it to vectors in the same way as before, which we can do by unpacking coefficients and repacking them:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from clifford.g3 import *\n",
"\n",
"v = 2*e1 + 3*e2\n",
"v_trans = layout.MultiVector()\n",
"v_trans[1,], v_trans[2,], v_trans[3,] = rot_and_scale_x @ [v[1,], v[2,], v[3,]]\n",
"v_trans"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, in geometric algebra we don't only care about the vectors, we want to transform the the higher-order blades too. This can be done via an outermorphism, which extends $f(a)$ to $f(a \\wedge b) = f(a) \\wedge f(b)$. This is where the `clifford.transformations` submodule comes in handy:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from clifford import transformations\n",
"\n",
"rot_and_scale_x_ga = transformations.OutermorphismMatrix(rot_and_scale_x, layout)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To apply these transformations, we use the `()` operator, rather than `@`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"rot_and_scale_x_ga(e12)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# check it's an outermorphism\n",
"rot_and_scale_x_ga(e1) ^ rot_and_scale_x_ga(e2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It shouldn't come as a surprise that applying the transformation to the psuedoscalar will tell us the determinant of our original matrix - the determinant tells us how a transformation scales volumes, and `layout.I` is a representation of the unit volume element!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"np.linalg.det(rot_and_scale_x), rot_and_scale_x_ga(layout.I)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Matrix representation\n",
"\n",
"Under the hood, clifford implements this using a matrix too - it's just now a matrix operating over all of the basis blades, not just over the vectors. We can see this by looking at the _private_ `_matrix` attribute:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"show_table(rot_and_scale_x_ga._matrix, [\"$\\mathit{in}_{%s}$\" % c for c in layout.names], [\"$\\mathit{out}_{%s}$\" % c for c in layout.names])"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.5.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

0 comments on commit f1e2677

Please sign in to comment.