orbitforge target
Synopsis
orbitforge target --plan <PATH> --problem <PATH> [OPTIONS]Description
Solves the inverse of maneuver. Instead of stating
burn magnitudes and seeing where you end up, you state where you want to end up
and the corrector adjusts the burns until you get there.
It uses the classic Vary and Achieve formulation: nominate free variables to adjust, nominate goals to hit, and let a differential corrector iterate.
The Jacobian is built by finite differences: each free variable is perturbed by its stated amount, the plan is re-propagated, and the change in each goal is measured. That is why the perturbation size is a parameter you supply.
Options
| Parameter | Type | Unit | Default | Required | Description |
|---|---|---|---|---|---|
--plan | path | n/a | — | Yes | Mission-plan JSON used as the initial guess. Same schema as `maneuver`. |
--problem | path | n/a | — | Yes | Targeting-problem JSON: vary, achieve, and max_iterations. |
--duration-hours | float | h | 6 | No | Propagation duration, which is also the goal-evaluation window. |
--step-seconds | float | s | 60 | No | Output sampling step. |
--coast | string | n/a | two-body | No | Coast-arc force model: `two-body`, `j2`, `j4`, or `egm96[:DxO]`. |
--solved-plan | path | n/a | — | No | Write the solved mission plan, with the varies applied. |
--czml | path | n/a | — | No | Write CZML of the solved trajectory. |
--json | path | n/a | — | No | Write the full solution: plan, residuals, and result. |
Problem schema
{
"vary": [
{ "burn": 0, "component": "dv1", "perturbation": 0.1 },
{ "burn": 1, "component": "dv1", "perturbation": 0.1 }
],
"achieve": [
{ "goal": "apoapsis_km", "value": 7100.0, "tolerance": 0.5 },
{ "goal": "periapsis_km", "value": 7080.0, "tolerance": 0.5 }
],
"max_iterations": 25
}Vary
| Field | Meaning |
|---|---|
burn | Zero-based index into the plan’s burn list |
component | dv0, dv1, dv2 for delta-v components in the burn’s frame, or time_s for firing time |
perturbation | Finite-difference step used to build the Jacobian, in the variable’s own units |
Selecting time_s rewrites that burn’s trigger to an elapsed-time trigger, since
the solver needs a continuous variable to adjust.
Achieve
goal | Unit |
|---|---|
sma_km | km |
ecc | dimensionless |
inc_deg | deg |
raan_deg | deg |
arg_perigee_deg | deg |
apoapsis_km | km, radius as |
periapsis_km | km, radius as |
Count your variables and goals. Two free variables and two goals is a square system with a unique solution. More goals than variables is over-constrained and generally has no exact solution; more variables than goals is under-constrained, and the corrector will find one of many.
The example below is deliberately square: two burns, one component each, two goals.
Worked example
Taking the two-burn plan from maneuver and solving
for a near-circular orbit at roughly 7090 km:
orbitforge target \
--plan plan.json \
--problem problem.json \
--duration-hours 4 --step-seconds 60 \
--solved-plan solved.jsonConverged in 2 iterations: 2 varies, 2 goals.
goal ApoapsisKm: target 7100.0000, residual +0.0002 (tol 0.5000) [ok]
goal PeriapsisKm: target 7080.0000, residual +0.0002 (tol 0.5000) [ok]
Solved mission: total delta-v 114.67 m/s, propellant 14.493 kg, final mass 265.507 kg.
Wrote solved plan -> solved.jsonReading the output
| Figure | Value | Meaning |
|---|---|---|
| Iterations | 2 | Corrections applied before convergence |
| Residual | +0.0002 km | Distance from the goal, against a 0.5 km tolerance |
| Total delta-v | 114.67 m/s | Cost of the solved plan |
Two iterations is the expected behavior
The initial guess asked for 30 and 29 m/s and reached a lower orbit than the goals required; the solver raised both burns to a total of 114.67 m/s. It took two iterations because the problem is close to linear over this range: the Jacobian computed at the first step predicted the correction almost exactly, and the second iteration confirmed convergence.
A problem needing many iterations is telling you it is strongly non-linear, and
one that reaches max_iterations has not solved at all.
Residuals are far tighter than the tolerance
Both residuals came in at 0.0002 km against a 0.5 km tolerance, roughly 2500 times better than requested. That is normal for a well-conditioned square system: the corrector does not stop at the tolerance, it converges and the tolerance merely defines success.
Tight residuals describe the model, not reality. A solved plan achieves its goals under the coast force model you selected, which defaults to two-body. Under J2 the same burns produce a different orbit, and a real execution adds pointing and magnitude error on top.
Re-run the solved plan through
maneuver at higher coast fidelity before treating
it as a burn specification.
Choosing the perturbation
The perturbation sets the finite-difference step for the Jacobian, and both
extremes fail:
| Too small | Too large |
|---|---|
| The goal change is lost in numerical noise, and the Jacobian is garbage | The linear approximation breaks down, and the predicted correction overshoots |
A useful starting point is a value that changes the goal by appreciably more than its tolerance but stays small compared with the variable itself. The 0.1 m/s above, against burns of tens of m/s, is a reasonable ratio.
See also
maneuverto propagate a plan directly, and to re-verify a solved plan at higher fidelity.transfer lambertfor a closed-form two-body transfer, which makes a good initial guess.
main (pre-release)