pg_orrery/src/od_solver.h
Ryan Malloy 59fd8ba743 Add multi-observer support for topocentric fitting
Extend od_observation_t with observer_idx so each observation can
reference a different ground station. Config now holds an array of
observers instead of a single pointer. The existing single-observer
tle_from_topocentric() is unchanged (sets observer_idx=0 for all obs).

New overload: tle_from_topocentric(topo[], ts[], observer[], int4[], ...)
accepts parallel observer_ids array indexing into the observers array.
PG function overloading resolves by argument types.

Tests 9-11: two-station fit converges, single-station via multi-observer
API matches, out-of-range observer_id raises error.
2026-02-17 15:59:11 -07:00

103 lines
3.0 KiB
C

/*
* od_solver.h -- TLE fitting via weighted least-squares differential correction
*
* Implements Vallado & Crawford (2008) AIAA 2008-6770:
* - Equinoctial element perturbation
* - SGP4 as the propagation engine
* - Jacobian by finite differencing
* - SVD solve via LAPACK dgelss_()
* - Tiered step limiting (Vallado 2008 Section V.B)
* - Brouwer-to-Kozai Newton-Raphson for TLE output
*/
#ifndef PG_ORRERY_OD_SOLVER_H
#define PG_ORRERY_OD_SOLVER_H
#include "norad.h"
/* Maximum number of DC iterations */
#define OD_MAX_ITER 25
/* Convergence thresholds (Vallado 2008 Section V.A) */
#define OD_RMS_REL_TOL 0.0002
#define OD_RMS_ABS_TOL 0.0002
/* Number of equinoctial states (6 orbital + optional B*) */
#define OD_NSTATE_6 6
#define OD_NSTATE_7 7
/*
* Observation type flag
*/
typedef enum
{
OD_OBS_ECI = 0, /* 6-component: x, y, z, vx, vy, vz (km, km/s) */
OD_OBS_TOPO = 1 /* 3-component: az, el, range (rad, rad, km) */
} od_obs_type_t;
/*
* Single observation for the DC solver
*/
typedef struct
{
double jd; /* Julian date of observation */
double data[6]; /* ECI: [x,y,z,vx,vy,vz], topo: [az,el,range,...] */
int observer_idx; /* index into config->observers[] (topo mode) */
} od_observation_t;
/*
* Observer location (needed for topocentric mode)
*/
typedef struct
{
double lat; /* radians */
double lon; /* radians */
double alt_m; /* meters */
double ecef[3]; /* pre-computed ECEF position (km) */
} od_observer_t;
/*
* Solver configuration
*/
typedef struct
{
od_obs_type_t obs_type; /* ECI or topocentric */
int fit_bstar; /* include B* as 7th state */
int max_iter; /* iteration limit */
od_observer_t *observers; /* array of observers (topo mode) */
int n_observers; /* count (0 for ECI mode) */
} od_config_t;
/*
* Solver result
*/
typedef struct
{
tle_t fitted_tle; /* resulting TLE (Kozai mean motion) */
int iterations; /* iterations performed */
double rms_initial; /* RMS residual before fitting (km) */
double rms_final; /* RMS residual after fitting (km) */
int converged; /* 1 if converged, 0 if hit max_iter */
char status[64]; /* human-readable status */
} od_result_t;
/*
* Primary entry point: fit a TLE from observations.
*
* obs: array of N observations
* n_obs: number of observations (must be >= 6, or >= 7 if fit_bstar)
* seed: initial TLE guess (if NULL, computed from first/last obs)
* config: solver configuration
* result: output (caller allocates)
*
* Returns 0 on success, -1 on error.
*
* Memory: all working arrays allocated via palloc() (when used from
* PostgreSQL) or malloc() (standalone). Freed before return.
*/
int od_fit_tle(const od_observation_t *obs, int n_obs,
const tle_t *seed, const od_config_t *config,
od_result_t *result);
#endif /* PG_ORRERY_OD_SOLVER_H */