# `Monty.Geometry.LinearAlgebra`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/geometry/linear_algebra.ex#L1)

The linear algebra Monty's sensors ask numpy for.

A sensor module fits a plane and then a quadric to a patch of points, which in
Python is `np.linalg.solve` and `np.linalg.eig`
(`frameworks/utils/sensor_processing.py`). There is no numpy here, and the fits
are small — three to six unknowns — so this module carries the three routines
those fits need and nothing else:

  * `solve/2` — Gaussian elimination with partial pivoting;
  * `symmetric_eigen/1` — Jacobi rotations, for a real symmetric matrix;
  * `eigen_2x2/1` — the closed form for a two-by-two matrix, which is what a
    shape operator is.

Nothing here is ported from Monty: Monty calls numpy, and this is the part of
numpy it calls. Matrices are row-major lists, the same shape the rest of this
port writes them in.

# `matrix`

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

A row-major matrix.

# `eigen_2x2`

```elixir
@spec eigen_2x2([[number()]]) :: [{float(), [float()]}]
```

The eigenvalues and eigenvectors of a two-by-two matrix, largest first.

The characteristic quadratic `λ² − tr λ + det = 0` solved in closed form, then
a null vector for each root. A shape operator is two-by-two, symmetric up to
rounding, and its eigenvalues order the principal curvatures — Monty sorts
them largest first, so this does too.

# `identity`

```elixir
@spec identity(pos_integer()) :: matrix()
```

The `count`×`count` identity matrix.

# `solve`

```elixir
@spec solve([[number()]], [number()]) :: {:ok, [float()]} | :singular
```

The vector `x` that solves `matrix · x = vector`, or `:singular`.

Gaussian elimination with partial pivoting: at each column the row with the
largest entry is moved into place, so a small pivot is only ever used when the
matrix genuinely has none bigger. `:singular` is answered rather than `NaN`s.

# `symmetric_eigen`

```elixir
@spec symmetric_eigen([[number()]]) :: [{float(), [float()]}]
```

The eigenvalues and eigenvectors of a real symmetric matrix, ascending.

Jacobi rotations: each pass zeroes the largest off-diagonal entry by turning
the matrix in its plane, and the turning is accumulated into the identity to
leave the eigenvectors as its columns. Ascending order is this module's
choice, not numpy's; callers that want the smallest or largest say so.

---

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