From b9aeddadecdd5ee5d2b17c4ba5254790e46c3ab1 Mon Sep 17 00:00:00 2001 From: Kiana Sheibani Date: Mon, 6 May 2024 04:10:29 -0400 Subject: [PATCH] Finish section on transforms --- docs/DataTypes.md | 2 +- docs/Main.md | 2 +- docs/Transforms.md | 101 ++++++++++++++++++++++++++++++++++++++++ docs/VectorsMatrices.md | 4 +- 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/docs/DataTypes.md b/docs/DataTypes.md index a083eff..ea3f118 100644 --- a/docs/DataTypes.md +++ b/docs/DataTypes.md @@ -119,7 +119,7 @@ These are useful for clarity when working with both homogeneous and non-homogene ### Transforms -A transform is a wrapper type for a matrix with certain properties that can be used to transform points in space. +A transform is a wrapper type for a square matrix with certain properties that can be used to transform points in space. ```idris Transform : (ty : TransType) -> (n : Nat) -> (a : Type) -> Type diff --git a/docs/Main.md b/docs/Main.md index a9c99b9..8ace64d 100644 --- a/docs/Main.md +++ b/docs/Main.md @@ -9,7 +9,7 @@ If you're familiar with the Python library [NumPy](https://numpy.org/) or anythi 1. [Fundamental Data Types](DataTypes.md) 2. [Basic Operations on Arrays](Operations.md) 3. [Working with Vectors and Matrices](VectorsMatrices.md) -4. Transforms +4. [Transforms](Transforms.md) ### Advanced Concepts diff --git a/docs/Transforms.md b/docs/Transforms.md index d2e5e37..0f36860 100644 --- a/docs/Transforms.md +++ b/docs/Transforms.md @@ -1,4 +1,105 @@ # Transforms +In code that works with linear algebra, it is common to use matrices as _transformations_, i.e. functions that take in a vector and output a new vector. These matrices can often be divided into categories based on the operations they perform, such as a rotation matrix or an affine transformation matrix. + +The `Transform` wrapper exists to encode these differing properties on the type level, as well as to provide extra utilities for working with matrices in this fashion. + +## Types of Transforms + +`Transform` has the following type signature: + +```idris +Transform : (ty : TransType) -> (n : Nat) -> (a : Type) -> Type +``` + +A `Transform ty n a` is a wrapper over an `HMatrix' n a`. The `ty` parameter is the transform type, which dictates what properties the transform has. These eight options are currently available: + +**Affine Types:** +- `TAffine` +- `TIsometry` (rotation + reflection + translation) +- `TRigid` (rotation + translation) +- `TTranslation` + +**Linear Types:** +- `TLinear` +- `TOrthonormal` (rotation + reflection) +- `TRotation` +- `TTrivial` (always the `identity`) + +The capital T at the beginning of each of these names identifies it as a `TransType` value. To make working with transforms smoother, NumIdr provides synonyms for transforms of each type. For example, `Isometry n a` is a synonym for `Transform TIsometry n a`. + +### Linear and Affine + +Transform types are divided into linear and affine types. Linear transforms must preserve the origin point, whereas affine transforms do not have this restriction. + +Linear and affine transform types are in a one-to-one correspondence: a linear transform can be converted to and from an affine transform by adding or removing a translation component. + +``` +Linear <-> Affine +Orthonormal <-> Isometry +Rotation <-> Rigid +Trivial <-> Translation +``` + +The `setTranslation` and `linearize` functions perform these conversions. + +For simplicity, both categories of transform are wrappers over homogeneous matrices, even though linear transforms could be represented by non-homogeneous matrices. + +### Transform Type Casting + +Some transform types can be cast into other types. For example, a `Rotation` can be cast into an `Orthonormal`, as all rotation matrices are orthonormal. + +```idris +rot : Rotation 3 Double + +cast rot : Orthonormal 3 Double +``` + +In the diagram from the previous section, lower types can be cast into types higher up. Each linear type (on the left) can also be cast into the corresponding affine type (on the right). + +## Constructing Transforms + +There are multiple ways to construct transforms, either by wrapping a matrix or directly through constructor functions. + +For each transform type, `fromHMatrix` can be used to test if a homogeneous matrix satisfies the right properties, and converts it into a transform if it does. The `Rotation`, `Orthonormal` and `Linear` types also have `fromMatrix` for non-homogeneous matrices. + +> [!NOTE] +> The `fromHMatrix` and `fromMatrix` constructors use exact equality comparisons when testing matrices, which can be an issue if your element type is `Double` or a similar inexact number type. To remedy this, NumIdr provides a `WithEpsilon` named implementation that defines equality approximately. +> +> ```idris +> fromHMatrix @{WithEpsilon 1.0e-6} mat +> ``` + +There are also direct constructors for transforms, which are often more convenient as they do not have the possibility of failing. There are too many of these constructors to exhaustively list here, so I encourage you to look through the functions in the `Data.NumIdr.Transform.*` modules to see what is available. + +## Multiplication with Transforms + +Like most objects in NumIdr, transforms multiply with the generalized multiplication operator `(*.)`, and `identity` and `inverse` can also be used with transforms. There is no `tryInverse` function, as all transforms are required to be invertible. + +Transforms of any types can be multiplied. When two transforms of different types are multiplied, the resulting transform type is determined by taking the most specific type that both original types can be cast to. For example, an `Orthonormal` transform multiplied by a `Translation` returns an `Isometry`. + +### The Point Type + +Transforms behave differently from regular matrices when applied to vectors. When an affine transform is applied in this way, it is first linearized, so that vectors only have linear transforms applied to them. **This is not a bug!** + +In order to properly apply affine transforms, the `Point` type must be used, which is a wrapper around the `Vector` type that supports these transforms. A point can be constructed with the `point` function, which is used exactly the same as the `vector` constructor. + +```idris +point [4, 3, 6] +``` + +Points support most basic operations that vectors do, including indexing operations and standard library methods. However, a point cannot be added to another point. Instead, a vector must be added to a point: + +```idris +(+.) : Vector n a -> Point n a -> Point n a + +(.+) : Point n a -> Vector n a -> Point n a + +(-.) : Point n a -> Point n a -> Vector n a +``` + +To remember the distinction between the two addition operators, the dot is always on the side of the point, not the vector. + +This separation between points and vectors is intended to make working with affine transformations more convenient, as it mirrors the separation between points and vectors in affine algebra. These may feel like arbitrary restrictions, but you might be surprised by how convenient they are to work with! [Previous](VectorsMatrices.md) | [Contents](Intro.md) diff --git a/docs/VectorsMatrices.md b/docs/VectorsMatrices.md index b92d17e..fec2e3f 100644 --- a/docs/VectorsMatrices.md +++ b/docs/VectorsMatrices.md @@ -77,12 +77,14 @@ interface MultMonoid a => MultGroup a where inverse : a -> a ``` -The `identity` function returns an identity matrix, and `inverse` calculates a matrix's inverse. Note that `inverse` cannot tell you if an inverse of your matrix does not exist; if you want to handle that case, use `tryInverse` instead. +The `identity` function returns an identity matrix, and `inverse` calculates a matrix's inverse. Note that `inverse` cannot tell you if an inverse of your matrix does not exist; if you want to handle that possibility, use `tryInverse` instead. ```idris tryInverse : FieldCmp a => Matrix' n a -> Maybe (Matrix' n a) ``` +You can also use the `invertible` predicate to test if a matrix has an inverse. + #### LU and LUP Decomposition The functions `decompLU` and `decompLUP` compute LU and LUP decomposition on a matrix.