ootk
Version:
Orbital Object Toolkit including Multiple Propagators, Initial Orbit Determination, and Maneuver Calculations.
159 lines • 6.62 kB
JavaScript
/**
* @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