UNPKG

ootk

Version:

Orbital Object Toolkit including Multiple Propagators, Initial Orbit Determination, and Maneuver Calculations.

179 lines 9.09 kB
/** * @author @thkruz Theodore Kruczek * @license AGPL-3.0-or-later * @copyright (c) 2025 Kruczek Labs LLC * * Orbital Object ToolKit is free software: you can redistribute it and/or modify it under the * terms of the GNU Affero General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * Orbital Object ToolKit is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * Orbital Object ToolKit. If not, see <http://www.gnu.org/licenses/>. */ import { RIC, Vector3D, } from '../main.js'; import { Thrust } from '../force/Thrust.js'; import { ForceModel } from './../force/ForceModel.js'; import { DownhillSimplex } from './../optimize/DownhillSimplex.js'; import { LambertIOD } from './../orbit_determination/LambertIOD.js'; import { RungeKutta89Propagator } from './../propagator/RungeKutta89Propagator.js'; // / Relative waypoint targeting. export class Waypoint { epoch; relativePosition; // / Create a new [Waypoint] object. constructor(epoch, relativePosition) { this.epoch = epoch; this.relativePosition = relativePosition; } /** * Return the perturbed error in a [maneuver] when compared against the * target [waypoint] given an initial [state], [forceModel], * [target] interpolator, and speculative relative maneuver * [components] _(m/s)_. * @param waypoint The waypoint to target. * @param maneuver The maneuver to perturb. * @param state The initial state of the interceptor. * @param forceModel The force model to use for propagation. * @param target The target interpolator. * @param components The speculative maneuver components. * @returns The perturbed error in the maneuver. */ static _error(waypoint, maneuver, state, forceModel, target, components) { const testManeuver = new Thrust(maneuver.center, components[0], components[1], components[2], maneuver.durationRate); const propagator = new RungeKutta89Propagator(state, forceModel); const maneuverSteps = propagator.maneuver(testManeuver); const postManeuver = maneuverSteps[maneuverSteps.length - 1]; const interceptor = new RungeKutta89Propagator(postManeuver, forceModel).propagate(waypoint.epoch); const targetState = target.interpolate(waypoint.epoch); if (targetState === null) { throw new Error('Error calculation failed; epoch is outside the target interpolator ephemeris window.'); } const expected = waypoint.relativePosition; const actual = RIC.fromJ2000(interceptor, targetState); return actual.position.distance(expected); } /** * Generate a score function for refining perturbed [waypoint] maneuvers. * * The score function takes an array of speculative radial, intrack, and * crosstrack components _(m/s)_ and returns the propagated error from the * desired waypoint target. * @param waypoint The waypoint to target. * @param maneuver The maneuver to perturb. * @param state The initial state of the interceptor. * @param forceModel The force model to use for propagation. * @param target The target interpolator. * @returns A score function for refining maneuvers. */ static _refineManeuverScore(waypoint, maneuver, state, forceModel, target) { return (components) => Waypoint._error(waypoint, maneuver, state, forceModel, target, components); } /** * Convert an array of [waypoints] into a maneuver sequence given an * [interceptor] state, [pivot] epoch for the first burn to arrive at the * first waypoint, and [target] ephemeris interpolator. * * Optional arguments are as follows: * - `preManeuvers`: maneuvers to execute before the pivot burn * - `postManeuvers`: maneuvers to execute after the last pivot burn * - `durationRate`: thruster duration rate _(s/m/s)_ * - `forceModel`: interceptor force model, defaults to two-body * - `refine`: refine maneuvers to account for perturbations if `true` * - `maxIter`: maximum refinement iterations per maneuver * - `printIter`: print debug information on each refinement iteration * @param interceptor The interceptor state. * @param pivot The epoch of the first burn. * @param waypoints The waypoints to target. * @param target The target interpolator. * @param preManeuvers The maneuvers to execute before the pivot burn. * @param postManeuvers The maneuvers to execute after the last pivot burn. * @param root0 The optional arguments. * @param root0.durationRate The thruster duration rate. * @param root0.forceModel The interceptor force model. * @param root0.refine Whether to refine maneuvers to account for perturbations. * @param root0.maxIter The maximum refinement iterations per maneuver. * @param root0.printIter Whether to print debug information on each refinement iteration. * @returns An array of maneuvers. */ static toManeuvers(interceptor, pivot, waypoints, target, preManeuvers, postManeuvers, { durationRate = 0.0, forceModel, refine = false, maxIter = 500, printIter = false, } = {}) { const preMnv = preManeuvers ?? []; const postMnv = postManeuvers ?? []; let state = interceptor; if (preMnv.length > 0) { for (const maneuver of preMnv) { const mvrStep = new RungeKutta89Propagator(state, forceModel).maneuver(maneuver); state = mvrStep[mvrStep.length - 1]; } } state = new RungeKutta89Propagator(state, forceModel).propagate(pivot); const pivotState = state; let waypointManeuvers = []; for (const wp of waypoints) { const targetWp = new RIC(wp.relativePosition, Vector3D.origin); const targetState = target.interpolate(wp.epoch); if (targetState === null) { throw new Error('Waypoint outside target interpolator window.'); } const wpState = targetWp.toJ2000(targetState); const tof = wp.epoch.difference(state.epoch); const revs = Math.floor(tof / state.period); const shortPath = LambertIOD.useShortPath(state, targetState); const lambert = new LambertIOD(); const components = lambert.estimate(state.position, wpState.position, state.epoch, wp.epoch, { posigrade: shortPath, nRev: revs, }); if (components === null) { throw new Error('Lambert solve result is null.'); } const componentsRel = RIC.fromJ2000(components, state).velocity.scale(1e3); const maneuver = new Thrust(state.epoch, componentsRel.x, componentsRel.y, componentsRel.z, durationRate); const tempProp = new RungeKutta89Propagator(state, forceModel); tempProp.maneuver(maneuver); state = tempProp.propagate(wp.epoch); waypointManeuvers.push(maneuver); } if (refine) { forceModel ??= new ForceModel().setGravity(); waypointManeuvers = this._refineManeuvers(waypoints, waypointManeuvers, pivotState, forceModel, target, { maxIter, printIter, }); } const output = []; output.push(...preMnv); output.push(...waypointManeuvers); output.push(...postMnv); return output; } static _refineManeuvers(waypoints, maneuvers, interceptor, forceModel, target, { maxIter = 500, printIter = false, } = {}) { let state = interceptor; const output = []; for (let i = 0; i < maneuvers.length; i++) { const maneuver = maneuvers[i]; const waypoint = waypoints[i]; const guess = maneuver.deltaV.scale(1e3).toArray(); const simplex = DownhillSimplex.generateSimplex(guess, 1e-1); const scoreFn = this._refineManeuverScore(waypoint, maneuver, state, forceModel, target); const results = DownhillSimplex.solveSimplex(scoreFn, simplex, { maxIter, xTolerance: 1e-6, fTolerance: 1e-6, printIter, }); const tR = results[0]; const tI = results[1]; const tC = results[2]; const newManeuver = new Thrust(maneuver.center, tR, tI, tC, maneuver.durationRate); output.push(newManeuver); const mvrStep = new RungeKutta89Propagator(state, forceModel).maneuver(newManeuver); state = mvrStep[mvrStep.length - 1]; } return output; } } //# sourceMappingURL=Waypoint.js.map