Add adaptive step limiting to DC solver
Scale step limits by a trust-region factor that halves on divergence (RMS increases > 1%) and relaxes toward full step on good convergence (RMS decreases > 10%). Prevents oscillation with poor initial guesses without affecting well-seeded fits. Also stores SVD condition number for diagnostic use in upcoming covariance output. Existing 8 OD regression tests + 67 standalone math tests unaffected (adaptive_factor starts at 1.0, round-trip tests never trigger divergence).
This commit is contained in:
parent
87ab81e7d0
commit
9b0634725b
@ -332,7 +332,8 @@ compute_residuals_topo(const tle_t *tle, const od_observation_t *obs,
|
|||||||
* operates on a different scale.
|
* operates on a different scale.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
apply_step_limits(double *dx, int nstate, const od_equinoctial_t *eq)
|
apply_step_limits(double *dx, int nstate, const od_equinoctial_t *eq,
|
||||||
|
double adaptive_factor)
|
||||||
{
|
{
|
||||||
/* Step limits for equinoctial elements (Vallado 2008 Table 2) */
|
/* Step limits for equinoctial elements (Vallado 2008 Table 2) */
|
||||||
static const double limits_6[6] = {
|
static const double limits_6[6] = {
|
||||||
@ -347,7 +348,8 @@ apply_step_limits(double *dx, int nstate, const od_equinoctial_t *eq)
|
|||||||
int i;
|
int i;
|
||||||
double ratio, max_ratio = 0.0;
|
double ratio, max_ratio = 0.0;
|
||||||
|
|
||||||
/* Find the maximum ratio of correction to limit */
|
/* Find the maximum ratio of correction to limit, scaled by
|
||||||
|
* adaptive_factor (trust-region-like damping when diverging) */
|
||||||
for (i = 0; i < 6; i++)
|
for (i = 0; i < 6; i++)
|
||||||
{
|
{
|
||||||
double scale;
|
double scale;
|
||||||
@ -356,7 +358,7 @@ apply_step_limits(double *dx, int nstate, const od_equinoctial_t *eq)
|
|||||||
else
|
else
|
||||||
scale = 1.0;
|
scale = 1.0;
|
||||||
|
|
||||||
ratio = fabs(dx[i]) / (limits_6[i] * scale);
|
ratio = fabs(dx[i]) / (limits_6[i] * scale * adaptive_factor);
|
||||||
if (ratio > max_ratio)
|
if (ratio > max_ratio)
|
||||||
max_ratio = ratio;
|
max_ratio = ratio;
|
||||||
}
|
}
|
||||||
@ -373,8 +375,8 @@ apply_step_limits(double *dx, int nstate, const od_equinoctial_t *eq)
|
|||||||
if (nstate == OD_NSTATE_7)
|
if (nstate == OD_NSTATE_7)
|
||||||
{
|
{
|
||||||
double bstar_limit = fabs(eq->bstar) > 1e-10
|
double bstar_limit = fabs(eq->bstar) > 1e-10
|
||||||
? fabs(eq->bstar) * 0.4
|
? fabs(eq->bstar) * 0.4 * adaptive_factor
|
||||||
: 1e-5;
|
: 1e-5 * adaptive_factor;
|
||||||
if (fabs(dx[6]) > bstar_limit)
|
if (fabs(dx[6]) > bstar_limit)
|
||||||
dx[6] = (dx[6] > 0.0 ? bstar_limit : -bstar_limit);
|
dx[6] = (dx[6] > 0.0 ? bstar_limit : -bstar_limit);
|
||||||
}
|
}
|
||||||
@ -477,6 +479,8 @@ od_fit_tle(const od_observation_t *obs, int n_obs,
|
|||||||
double *sv = NULL;
|
double *sv = NULL;
|
||||||
double *work = NULL;
|
double *work = NULL;
|
||||||
double rms_prev, rms_cur;
|
double rms_prev, rms_cur;
|
||||||
|
double adaptive_factor = 1.0; /* trust-region damping */
|
||||||
|
double condition_number = 0.0; /* s_max / s_min from SVD */
|
||||||
int iter;
|
int iter;
|
||||||
int max_iter;
|
int max_iter;
|
||||||
int converged = 0;
|
int converged = 0;
|
||||||
@ -682,8 +686,12 @@ od_fit_tle(const od_observation_t *obs, int n_obs,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Apply step limiting */
|
/* Store condition number from SVD singular values */
|
||||||
apply_step_limits(dx, nstate, &eq);
|
if (sv[nstate - 1] > 1e-30)
|
||||||
|
condition_number = sv[0] / sv[nstate - 1];
|
||||||
|
|
||||||
|
/* Apply step limiting (scaled by adaptive factor) */
|
||||||
|
apply_step_limits(dx, nstate, &eq, adaptive_factor);
|
||||||
|
|
||||||
/* Update equinoctial elements */
|
/* Update equinoctial elements */
|
||||||
eq.n += dx[0];
|
eq.n += dx[0];
|
||||||
@ -716,6 +724,21 @@ od_fit_tle(const od_observation_t *obs, int n_obs,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Adaptive step control: adjust trust-region size based on
|
||||||
|
* whether the solver is converging or diverging. Halve the
|
||||||
|
* step on divergence (prevents oscillation with poor initial
|
||||||
|
* guesses), relax toward full step when converging well. */
|
||||||
|
if (rms_cur > rms_prev * 1.01)
|
||||||
|
{
|
||||||
|
adaptive_factor *= 0.5;
|
||||||
|
if (adaptive_factor < 0.05)
|
||||||
|
adaptive_factor = 0.05;
|
||||||
|
}
|
||||||
|
else if (rms_cur < rms_prev * 0.9)
|
||||||
|
{
|
||||||
|
adaptive_factor = fmin(adaptive_factor * 1.3, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Check convergence: either absolute RMS is small enough,
|
/* Check convergence: either absolute RMS is small enough,
|
||||||
* or relative change has plateaued (solver found its best fit,
|
* or relative change has plateaued (solver found its best fit,
|
||||||
* common for SDP4 deep-space orbits where the numerical floor
|
* common for SDP4 deep-space orbits where the numerical floor
|
||||||
@ -749,6 +772,8 @@ od_fit_tle(const od_observation_t *obs, int n_obs,
|
|||||||
else if (iter >= max_iter && result->status[0] == '\0')
|
else if (iter >= max_iter && result->status[0] == '\0')
|
||||||
snprintf(result->status, sizeof(result->status), "max_iterations");
|
snprintf(result->status, sizeof(result->status), "max_iterations");
|
||||||
|
|
||||||
|
(void)condition_number; /* used by covariance output (v0.5.0) */
|
||||||
|
|
||||||
/* Cleanup */
|
/* Cleanup */
|
||||||
od_free(residuals);
|
od_free(residuals);
|
||||||
od_free(jacobian);
|
od_free(jacobian);
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
-- Tests tle_from_eci(), tle_from_topocentric(), and tle_fit_residuals().
|
-- Tests tle_from_eci(), tle_from_topocentric(), and tle_fit_residuals().
|
||||||
-- Uses round-trip methodology: propagate known TLE → fit from obs → compare.
|
-- Uses round-trip methodology: propagate known TLE → fit from obs → compare.
|
||||||
CREATE EXTENSION IF NOT EXISTS pg_orrery;
|
CREATE EXTENSION IF NOT EXISTS pg_orrery;
|
||||||
NOTICE: extension "pg_orrery" already exists, skipping
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Test 1: ECI round-trip (ISS-like LEO orbit)
|
-- Test 1: ECI round-trip (ISS-like LEO orbit)
|
||||||
--
|
--
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user