ootk
Version:
Orbital Object Toolkit including Multiple Propagators, Initial Orbit Determination, and Maneuver Calculations.
141 lines • 5.97 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';
// / Runge-Kutta 4 fixed numerical propagator.
export class RungeKutta4Propagator extends Propagator {
initState_;
forceModel_;
stepSize_;
cacheState_;
checkpoints_;
/**
* Create a new [RungeKutta4Propagator] object from an initial state vector and
* along with an optional [ForceModel] and [stepSize] in seconds.
* @param initState_ Initial state vector.
* @param forceModel_ Numerical integration force model.
* @param stepSize_ Integration step size _(seconds)_.
* @param cacheState_ Cached state vector.
* @param checkpoints_ Cached state vector checkpoints.
*/
constructor(initState_, forceModel_ = new ForceModel().setGravity(), stepSize_ = 15.0, cacheState_ = initState_, checkpoints_ = []) {
super();
this.initState_ = initState_;
this.forceModel_ = forceModel_;
this.stepSize_ = stepSize_;
this.cacheState_ = cacheState_;
this.checkpoints_ = checkpoints_;
this.stepSize_ = Math.abs(stepSize_);
}
// / Set the integrator step size to the provided number of [seconds].
setStepSize(seconds) {
this.stepSize_ = Math.abs(seconds);
}
// / Set numerical integration force model.
setForceModel(forceModel) {
this.forceModel_ = forceModel;
}
ephemerisManeuver(start, finish, maneuvers, interval = 60.0) {
const tMvr = maneuvers.slice(0).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);
}
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;
}
_kFn(state, hArg, kArg) {
const epoch = state.epoch.roll(hArg);
const posvel = state.position.join(state.velocity);
const result = posvel.add(kArg);
const sample = new J2000(epoch, result.toVector3D(0), result.toVector3D(3));
return this.forceModel_.derivative(sample);
}
_integrate(state, step) {
const k1 = this._kFn(state, 0, Vector.zero(6)).scale(step);
const k2 = this._kFn(state, 0.5 * step, k1.scale(0.5)).scale(step);
const k3 = this._kFn(state, 0.5 * step, k2.scale(0.5)).scale(step);
const k4 = this._kFn(state, step, k3).scale(step);
const v1 = k1;
const v2 = v1.add(k2.scale(2));
const v3 = v2.add(k3.scale(2));
const v4 = v3.add(k4);
const tNext = state.epoch.roll(step);
const posvel = state.position.join(state.velocity);
const result = posvel.add(v4.scale(1 / 6));
return new J2000(tNext, result.toVector3D(0), result.toVector3D(3));
}
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;
this.cacheState_ = this._integrate(this.cacheState_, dt);
delta = epoch.difference(this.cacheState_.epoch);
}
return this.cacheState_;
}
reset() {
this.cacheState_ = this.initState_;
}
get state() {
return this.cacheState_;
}
checkpoint() {
this.checkpoints_.push(this.cacheState_);
return this.checkpoints_.length - 1;
}
clearCheckpoints() {
this.checkpoints_ = [];
}
restore(index) {
this.cacheState_ = this.checkpoints_[index];
}
}
//# sourceMappingURL=RungeKutta4Propagator.js.map