# `AstroUtils.Matrix3`
[🔗](https://github.com/jakedjohnson/astro_utils/blob/v0.1.0/lib/astro_utils/matrix3.ex#L1)

3x3 matrix operations, primarily for rotations.

A matrix is a row-major list of three three-element lists, i.e.
`[[r11, r12, r13], [r21, r22, r23], [r31, r32, r33]]`. Every function here
assumes that exact shape; other shapes either raise `FunctionClauseError`
or produce garbage rather than a friendly error.

## Rotation convention

`rot_x/1`, `rot_y/1`, and `rot_z/1` return *active* (vector) rotations in a
right-handed frame: `multiply_vector/2` rotates the vector counter-clockwise
about the named axis by the given angle, as seen looking down that axis
toward the origin. Angles are in **radians**; use `AstroUtils.Angle.deg_to_rad/1`
for degree input.

Astronomy references often tabulate the opposite (frame) convention, where
the axes rotate and the vector stays put. `transpose/1` converts between the
two, since rotation matrices are orthogonal and their transpose is their
inverse.

    iex> alias AstroUtils.Matrix3
    iex> Matrix3.rot_z(:math.pi() / 2)
    ...> |> Matrix3.multiply_vector({1.0, 0.0, 0.0})
    ...> |> then(fn {x, y, z} -> {Float.round(x, 12), Float.round(y, 12), Float.round(z, 12)} end)
    {0.0, 1.0, 0.0}

# `t`

```elixir
@type t() :: [[float()]]
```

A 3x3 matrix as a row-major list of three rows.

# `vector`

```elixir
@type vector() :: {float(), float(), float()}
```

A 3D vector as an `{x, y, z}` tuple.

# `identity`

```elixir
@spec identity() :: t()
```

The 3x3 identity matrix.

## Examples

    iex> AstroUtils.Matrix3.identity()
    [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]

# `multiply`

```elixir
@spec multiply(t(), t()) :: t()
```

Matrix product `a · b`.

Matrix multiplication is not commutative. When composing rotations that
will be applied with `multiply_vector/2`, the right-most factor acts on the
vector first, so `multiply(rot_z(c), multiply(rot_x(b), rot_z(a)))` applies
`rot_z(a)`, then `rot_x(b)`, then `rot_z(c)`.

Works for any 3x3 matrices, not only rotations.

## Examples

    iex> m = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]
    iex> AstroUtils.Matrix3.multiply(m, AstroUtils.Matrix3.identity())
    [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]

# `multiply_vector`

```elixir
@spec multiply_vector(t(), vector()) :: vector()
```

Apply a matrix to a column vector, returning `m · v`.

## Examples

    iex> AstroUtils.Matrix3.multiply_vector(AstroUtils.Matrix3.identity(), {1.0, 2.0, 3.0})
    {1.0, 2.0, 3.0}

# `rot_x`

```elixir
@spec rot_x(number()) :: t()
```

Rotation of `theta` radians about the x-axis.

Leaves the x component unchanged and rotates y toward z.

## Examples

    iex> AstroUtils.Matrix3.rot_x(:math.pi())
    ...> |> AstroUtils.Matrix3.multiply_vector({0.0, 1.0, 0.0})
    ...> |> then(fn {x, y, z} -> {Float.round(x, 12), Float.round(y, 12), Float.round(z, 12)} end)
    {0.0, -1.0, 0.0}

# `rot_y`

```elixir
@spec rot_y(number()) :: t()
```

Rotation of `theta` radians about the y-axis.

Leaves the y component unchanged and rotates z toward x.

# `rot_z`

```elixir
@spec rot_z(number()) :: t()
```

Rotation of `theta` radians about the z-axis.

Leaves the z component unchanged and rotates x toward y.

# `transpose`

```elixir
@spec transpose(t()) :: t()
```

Swap rows and columns.

For a rotation matrix the transpose is also the inverse, so this is how you
invert a rotation without solving anything.

## Examples

    iex> AstroUtils.Matrix3.transpose([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
    [[1.0, 4.0, 7.0], [2.0, 5.0, 8.0], [3.0, 6.0, 9.0]]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
