# `Sidereon.SGP4`
[🔗](https://github.com/neilberkman/sidereon-ex/blob/main/lib/sidereon/sgp4.ex#L1)

SGP4/SDP4 orbit propagation from Two-Line Element sets.

# `element_error`

```elixir
@type element_error() :: {:missing_field, atom()} | {:invalid_field, atom(), term()}
```

# `propagation_error`

```elixir
@type propagation_error() :: element_error() | String.t() | {:nif_error, String.t()}
```

# `fit_tle`

```elixir
@spec fit_tle(
  [map()],
  keyword()
) :: {:ok, Sidereon.SGP4.Fit.t()} | {:error, term()}
```

Fit a TLE to TEME position samples using the core inverse-SGP4 solver.

# `propagate`

```elixir
@spec propagate(Sidereon.Elements.t(), DateTime.t()) ::
  {:ok, Sidereon.TemeState.t()} | {:error, propagation_error()}
```

Propagate a TLE to a specific datetime, returning a TEME state vector.

Uses the sgp4 Rust crate in AFSPC compatibility mode. Elements are
passed as individual fields, so this works for both TLE and OMM inputs.

Returns `{:ok, %Sidereon.TemeState{}}` with position in km and velocity in km/s,
or `{:error, reason}`.

# `propagate_batch`

```elixir
@spec propagate_batch([Sidereon.Elements.t()], [number()], keyword()) ::
  {:ok, ok: [Sidereon.TemeState.t()], error: term()} | {:error, term()}
```

Propagate many satellites across a shared list of times, in one NIF call.

Each time is **minutes since that satellite's own epoch** (the core batch
convention), so element `i` of the result is the arc for `satellites |> Enum.at(i)`
evaluated at every offset in `times_minutes`. This is the throughput primitive
over `sidereon_core::astro::sgp4::propagate_batch`; one bad satellite never
collapses the batch.

`satellites` is a list of `%Sidereon.Elements{}`. Options:

  * `:opsmode` - `:afspc` (default, matching `propagate/2`) or `:improved`.
  * `:parallel` - when `true`, fans the per-satellite arcs across a thread pool
    (`propagate_batch_parallel`); the results are bit-identical to the serial
    path. Defaults to `false`.

Returns `{:ok, arcs}` where each arc is `{:ok, [%Sidereon.TemeState{}, ...]}`
(one state per time, in order) or `{:error, reason}` for a satellite that failed
to propagate. Returns `{:error, {:invalid_elements, index, reason}}` if an input
element set cannot be marshalled.

# `propagate_with_decay_latch`

```elixir
@spec propagate_with_decay_latch(Sidereon.Elements.t(), [number()], keyword()) ::
  {:ok, ok: Sidereon.TemeState.t(), error: term()} | {:error, term()}
```

Propagate one satellite through a stateful decay latch over ordered offsets.

`times_minutes` are minutes since the TLE epoch. The latch is scoped to this
call: after the first decay-like SGP4 failure, later requests at the same or a
greater offset return `{:error, {:decayed, first_minutes, requested_minutes}}`
instead of a raw post-failure state.

---

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