UNPKG

ootk

Version:

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

159 lines 6.62 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 { J2000, Vector } from '../main.js'; import { ForceModel } from '../force/ForceModel.js'; import { VerletBlendInterpolator } from './../interpolator/VerletBlendInterpolator.js'; import { Propagator } from './Propagator.js'; import { RkCheckpoint } from './RkCheckpoint.js'; import { RkResult } from './RkResult.js'; // / Adaptive Runge-Kutta propagator base class. export class RungeKuttaAdaptive extends Propagator { initState_; forceModel_; tolerance_; /** * Create a new [RungeKuttaAdaptive] object from an initial state vector * along with an optional [ForceModel] and [tolerance]. * @param initState_ Initial state vector. * @param forceModel_ Numerical integration force model. * @param tolerance_ Minimum allowable local error tolerance. */ constructor(initState_, forceModel_ = new ForceModel().setGravity(), tolerance_ = 1e-9) { super(); this.initState_ = initState_; this.forceModel_ = forceModel_; this.tolerance_ = tolerance_; this._cacheState = this.initState_; this.tolerance_ = Math.max(RungeKuttaAdaptive._minTolerance, Math.abs(tolerance_)); } // / Initial state vector. _cacheState; _checkpoints = []; // / Integration step size _(seconds)_. _stepSize = 60.0; // / Minimum allowable local error tolerance. static _minTolerance = 1e-15; get state() { return this._cacheState; } reset() { this._cacheState = this.initState_; this._stepSize = 60.0; } // / Set numerical integration force model. setForceModel(forceModel) { this.forceModel_ = forceModel; } kfn_(epoch, rv, hArg, kArg, step) { const t = epoch.roll(hArg * step); const rvNew = rv.add(kArg); const sample = new J2000(t, rvNew.toVector3D(0), rvNew.toVector3D(3)); return this.forceModel_.derivative(sample).scale(step); } integrate_(state, step) { const k = new Array(this.a.length).fill(Vector.origin3); const y = state.position.join(state.velocity); for (let i = 0; i < this.a.length; i++) { let kArg = Vector.origin6; if (i !== 0) { for (let j = 0; j < i; j++) { kArg = kArg.add(k[j].scale(this.b[i][j])); } } k[i] = this.kfn_(state.epoch, y, this.a[i], kArg, step); } let y1 = y; let y2 = y; for (let i = 0; i < k.length; i++) { y1 = y1.add(k[i].scale(this.ch[i])); y2 = y2.add(k[i].scale(this.c[i])); } const teVal = y1.distance(y2); let hNew = 0.9 * step * (this.tolerance_ / teVal) ** (1.0 / this.order); const hOld = Math.abs(step); hNew = Math.max(0.2 * hOld, Math.min(5.0 * hOld, hNew)); hNew = Math.max(1e-5, Math.min(1000.0, hNew)); return new RkResult(new J2000(state.epoch.roll(step), y1.toVector3D(0), y1.toVector3D(3)), teVal, hNew); } propagate(epoch) { let delta = epoch.difference(this._cacheState.epoch); while (delta !== 0) { const direction = delta >= 0 ? 1 : -1; const dt = Math.min(Math.abs(delta), this._stepSize) * direction; const result = this.integrate_(this._cacheState, dt); this._stepSize = result.newStep; if (result.error > this.tolerance_) { continue; } this._cacheState = result.state; delta = epoch.difference(this._cacheState.epoch); } return this._cacheState; } maneuver(maneuver, interval = 60.0) { if (maneuver.isImpulsive) { this._cacheState = maneuver.apply(this.propagate(maneuver.center)); return [this._cacheState]; } let tState = this.propagate(maneuver.start); this.forceModel_.loadManeuver(maneuver); const ephemeris = [tState]; while (tState.epoch < maneuver.stop) { const step = Math.min(maneuver.stop.difference(tState.epoch), interval); tState = this.propagate(tState.epoch.roll(step)); ephemeris.push(tState); } this.forceModel_.clearManeuver(); return ephemeris; } ephemerisManeuver(start, finish, maneuvers, interval = 60.0) { const tMvr = maneuvers.filter((mvr) => mvr.start >= start || mvr.stop <= finish); const ephemeris = []; if (tMvr[0].start > start) { ephemeris.push(this.propagate(start)); } for (const mvr of tMvr) { while (this._cacheState.epoch < mvr.start) { const step = Math.min(mvr.start.difference(this._cacheState.epoch), interval); this.propagate(this._cacheState.epoch.roll(step)); if (this._cacheState.epoch.posix !== mvr.start.posix) { ephemeris.push(this._cacheState); } } ephemeris.push(...this.maneuver(mvr, interval)); } while (this._cacheState.epoch.posix < finish.posix) { const step = Math.min(finish.difference(this._cacheState.epoch), interval); this.propagate(this._cacheState.epoch.roll(step)); ephemeris.push(this._cacheState); } return new VerletBlendInterpolator(ephemeris); } checkpoint() { this._checkpoints.push(new RkCheckpoint(this._cacheState, this._stepSize)); return this._checkpoints.length - 1; } clearCheckpoints() { this._checkpoints.length = 0; } restore(index) { const checkpoint = this._checkpoints[index]; this._cacheState = checkpoint.cacheState; this._stepSize = checkpoint.stepSize; } } //# sourceMappingURL=RungeKuttaAdaptive.js.map