# Utils/Ellipsoids

Conversions between 2D ellipse parameters and covariance matrices, CEP/SEP error metrics, and confidence scaling utilities.

This module bridges two representations of uncertainty: geometric ellipse parameters (semi-major axis, semi-minor axis, orientation) and statistical covariance matrices. It also provides Circular Error Probable (CEP) and Spherical Error Probable (SEP) computation from covariance matrices, and chi-squared scaling for converting between sigma and 95% confidence levels.

## Import and Use

```python
from gri_utils import ellipsoids

params = ellipsoids.cov_to_params(cov_2x2)
```

Or with a unique name:

```python
from gri_utils import ellipsoids as gri_ell

params = gri_ell.cov_to_params(cov_2x2)
```

## Module Overview

| Symbol | Purpose |
| ------ | ------- |
| `EllipseParams` | NamedTuple: `sma`, `smi`, `ori_deg` |
| `EllipseParams95` | NamedTuple: `sma_95`, `smi_95`, `ori_deg` |
| `CovSlice` | NamedTuple: `cov_2x2`, `center` (slice height) |
| `params_to_cov` | 1-sigma ellipse params -> 2x2 (or 3x3) covariance |
| `params_95_to_cov` | 95% ellipse params -> 2x2 (or 3x3) covariance |
| `cov_to_params` | 2x2 covariance -> 1-sigma `EllipseParams` |
| `cov_to_params_95` | 2x2 covariance -> 95% `EllipseParams95` |
| `cov_to_alt_param` | 3x3 covariance -> max altitude (up-axis) value |
| `cov_projection` | 3x3 -> 2x2 projection onto first two axes |
| `cov_slice` | 3x3 -> 2x2 conditional slice at a given height |
| `scale_chi2` | Scale scalars, vectors, or matrices between sigma and 95% |
| `cep` | CEP radius at any confidence level from a 2x2 (or 3x3) covariance |
| `cep_50` | CEP at 50% confidence (median) |
| `cep_90` | CEP at 90% confidence |
| `cep_95` | CEP at 95% confidence |
| `cep_sigma` | CEP at a given number of standard deviations |
| `cep_2sigma` | CEP at 2-sigma confidence |
| `sep` | SEP radius at any confidence level from a 3x3 covariance |
| `sep_50` | SEP at 50% confidence (median) |
| `sep_90` | SEP at 90% confidence |
| `sep_95` | SEP at 95% confidence |
| `sep_sigma` | SEP at a given number of standard deviations |
| `sep_2sigma` | SEP at 2-sigma confidence |

## Ellipse Parameters <-> Covariance

### Parameters to Covariance

```python
import numpy as np
from gri_utils import ellipsoids

# 1-sigma parameters -> 2x2 covariance
cov_2x2 = ellipsoids.params_to_cov(sma=100.0, smi=50.0, ori_deg=45.0)

# With altitude -> 3x3 "pancake" covariance (east/north plane + up)
cov_3x3 = ellipsoids.params_to_cov(sma=100.0, smi=50.0, ori_deg=45.0, alt=200.0)

# From 95% parameters
cov_2x2 = ellipsoids.params_95_to_cov(sma_95=245.0, smi_95=122.0, ori_deg=45.0)
```

### Covariance to Parameters

```python
from gri_utils import ellipsoids

cov = np.array([[100.0, 25.0], [25.0, 50.0]])

# 1-sigma ellipse parameters
params = ellipsoids.cov_to_params(cov)
# params.sma, params.smi, params.ori_deg

# 95% ellipse parameters
params_95 = ellipsoids.cov_to_params_95(cov)
# params_95.sma_95, params_95.smi_95, params_95.ori_deg
```

## 3D Covariance Operations

```python
from gri_utils import ellipsoids

cov_3x3 = np.diag([100.0, 200.0, 300.0])

# Project 3x3 -> 2x2 (upper-left block, east/north plane)
cov_2x2 = ellipsoids.cov_projection(cov_3x3)

# Conditional slice at a given height (Schur complement)
result = ellipsoids.cov_slice(cov_3x3, height=1.5)
# result.cov_2x2: conditional 2x2 covariance
# result.center: conditional mean shift in east/north

# Maximum altitude value (positive-up)
alt = ellipsoids.cov_to_alt_param(cov_3x3)
```

## Chi-Squared Scaling

Convert between 1-sigma and 95% confidence for scalars, vectors, or matrices:

```python
from gri_utils import ellipsoids

# Scale a 1-sigma scalar to 95%
val_95 = ellipsoids.scale_chi2(50.0, from_sigma=True)

# Scale a 95% scalar back to 1-sigma
val_sigma = ellipsoids.scale_chi2(98.0, from_sigma=False)

# Works on vectors (uses 2D scaling) and matrices (uses appropriate dimension)
cov_95 = ellipsoids.scale_chi2(cov_2x2, from_sigma=True)
```

## Circular Error Probable (CEP)

CEP is the radius of a circle centered at the mean that contains a given fraction of the probability mass of a 2D Gaussian. Convenience functions are provided for common confidence levels.

```python
import numpy as np
from gri_utils import ellipsoids

cov_2x2 = np.array([[9.0, 0.0], [0.0, 100.0]])

# CEP at 50%, 90%, 95%
r50 = ellipsoids.cep_50(cov_2x2)
r90 = ellipsoids.cep_90(cov_2x2)
r95 = ellipsoids.cep_95(cov_2x2)

# CEP at an arbitrary confidence level
r99 = ellipsoids.cep(cov_2x2, conf=0.99)

# CEP at a number of standard deviations
r_1sig = ellipsoids.cep_sigma(cov_2x2, sigma=1.0)
r_2sig = ellipsoids.cep_2sigma(cov_2x2)

# Also accepts 3x3 covariance (uses upper-left 2x2 block)
cov_3x3 = np.diag([9.0, 100.0, 400.0])
r50 = ellipsoids.cep_50(cov_3x3)
```

Numerically stable for arbitrary sigma ratios (tested up to 1:1000) using the exponentially-scaled Bessel function `i0e`.

## Spherical Error Probable (SEP)

SEP is the radius of a sphere centered at the mean that contains a given fraction of the probability mass of a 3D Gaussian.

```python
import numpy as np
from gri_utils import ellipsoids

cov_3x3 = np.diag([1.0, 25.0, 10000.0])

# SEP at 50%, 90%, 95%
r50 = ellipsoids.sep_50(cov_3x3)
r90 = ellipsoids.sep_90(cov_3x3)
r95 = ellipsoids.sep_95(cov_3x3)

# SEP at an arbitrary confidence level
r99 = ellipsoids.sep(cov_3x3, conf=0.99)

# SEP at a number of standard deviations
r_1sig = ellipsoids.sep_sigma(cov_3x3, sigma=1.0)
r_2sig = ellipsoids.sep_2sigma(cov_3x3)
```

Numerically stable for arbitrary sigma ratios (tested up to 1:1:100 and 20:20:1) using `i0e` and optimal axis assignment for the spherical coordinate integration.
