Code Sample |
The following code samples illustrate the various pieces required to set up, configure, and numerically propagate a state.
Note |
---|
The functionality described in this topic requires a license for the Orbit Propagation Library. |
First, we configure the context in DME Component Libraries to include EOP data, using EarthOrientationParameters, and loading JPL ephemerides using JplDE440. The EOP and JPL data are not required to run, but they will affect the accuracy of the force models during propagation.
EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); SunCentralBody sun = CentralBodiesFacet.getFromContext().getSun(); MoonCentralBody moon = CentralBodiesFacet.getFromContext().getMoon(); // Load EOP table into the library earth.setOrientationParameters(EarthOrientationParametersFile.readData(eopFilePath)); // Load JPL Ephemerides into the library JplDE440 ephem = new JplDE440(deFilePath); // The following updates the "CenterOfMassPoint" on each of the CentralBodies ephem.useForCentralBodyPositions(CentralBodiesFacet.getFromContext()); // The following is needed to accurately model the precise orientation of the Moon moon.setFixedFrame(ephem.getMoonTopographicFixedFrame());
Next, we decide which elements need to be integrated in the state and which should be evaluated as auxiliary data during propagation. A position which depends on complex force models needs to be integrated to predict its values. In contrast, a density is simply a function of position and has no differential equation associated with it. The differential equations will be filled in next.
NumericalPropagatorDefinition state = new NumericalPropagatorDefinition(); final String positionID = "MyVehiclePosition"; PropagationNewtonianPoint position = new PropagationNewtonianPoint(positionID, earth.getInertialFrame(), initialPosition, initialVelocity); state.setEpoch(epoch); state.getIntegrationElements().add(position); // state.getIntegrationElements().add(angleOfAttack); // etc. // Add auxiliary output AuxiliaryStateScalar densityOutput = new AuxiliaryStateScalar(); String densityID = "MyAtmosphericDensity"; densityOutput.setIdentification(densityID); state.getAuxiliaryElements().add(densityOutput);
In general, each PropagationStateElement will have some way of defining its derivative for the integrator. In the case of a PropagationVector, the derivative is simply a Vector whose value represents the derivative of the highest order of the represented vector state. However, the most common element will likely be the PropagationNewtonianPoint which models the position and velocity of a body subject to a list of ForceModels.
SphericalHarmonicGravity gravity = new SphericalHarmonicGravity(); gravity.setTargetPoint(position.getIntegrationPoint()); int degree = 41; int order = 41; SolidTideModel tideModel = null; gravity.setGravityField(new SphericalHarmonicGravityField( SphericalHarmonicGravityModel.readFrom(gravityEGM96FilePath), degree, order, true, tideModel)); ThirdBodyGravity thirdBody = new ThirdBodyGravity(); thirdBody.setCentralBody(earth); final double sunGm = 1.327122e20; // m^3 / sec^2 final double moonGm = 4.902801076e12; // m^3 / sec^2 thirdBody.addThirdBody(sun.getName(), sun.getCenterOfMassPoint(), sunGm); thirdBody.addThirdBody(moon.getName(), moon.getCenterOfMassPoint(), moonGm); thirdBody.setTargetPoint(position.getIntegrationPoint()); SimpleSolarRadiationForce srp = new SimpleSolarRadiationForce(); ScalarOccultation occultationFactor = new ScalarOccultationDualCone(); occultationFactor.setIlluminatingBody(sun); occultationFactor.getOccludingBodies().add(earth); occultationFactor.getOccludingBodies().add(moon); occultationFactor.setTargetPoint(position.getIntegrationPoint()); occultationFactor.setVectorType(RadiationVectorType.TRUE_POSITION); srp.setOccultationFactor(occultationFactor); srp.setCoefficientOfReflectivity(Scalar.toScalar(1.0)); srp.setReferenceArea(Scalar.toScalar(20.0)); AtmosphericDragForce drag = new AtmosphericDragForce(); ConstantSolarGeophysicalData geoData = new ConstantSolarGeophysicalData(150.0, 3.00); ScalarDensityJacchiaRoberts density = new ScalarDensityJacchiaRoberts(position.getIntegrationPoint(), geoData); drag.setDensity(density); drag.setCoefficientOfDrag(Scalar.toScalar(2.2)); drag.setReferenceArea(Scalar.toScalar(20.0)); position.getAppliedForces().add(gravity); position.getAppliedForces().add(thirdBody); position.getAppliedForces().add(srp); position.getAppliedForces().add(drag); position.setMass(mass); densityOutput.setAuxiliaryScalar(density);
The last step before creating the propagator is to choose which integration scheme to use in order to propagate the state and to configure its settings.
RungeKuttaFehlberg78Integrator integrator = new RungeKuttaFehlberg78Integrator(); integrator.setInitialStepSize(stepSize); integrator.setStepSizeBehavior(KindOfStepSize.RELATIVE); integrator.setMaximumStepSize(Double.MAX_VALUE); integrator.setMinimumStepSize(0.001); // This is STK's default and truncates each step to 3 decimal places integrator.setStepTruncationOrder(-3); state.setIntegrator(integrator);
After creating a NumericalPropagator, we use it to generate ephemeris over a given span of time, then create a PointInterpolator to represent the propagated ephemeris for further analysis in DME Component Libraries. Note that this point should be used to define the LocationPoint (get / set) of a platform, rather than the IntegrationPoint (get) which only represents the position during the integration process.
int outputSparsity = 1; // 1 indicates to output after every integration step, 2 every other step, etc, etc Duration propagationTime = Duration.fromSeconds(totalTime); NumericalPropagationStateHistory rawData = propagator.propagate(propagationTime, outputSparsity); // Recover the position, velocity, (and acceleration if specified on the position before creating the propagator) DateMotionCollection1<Cartesian> ephemeris = rawData.getDateMotionCollection(positionID); DateMotionCollection1<Double> densityData = rawData.getDateMotionCollection(densityID); Point platformPosition = new PointInterpolator(position.getIntegrationFrame(), InterpolationAlgorithmType.HERMITE, 6, ephemeris); // Perform analysis using other libraries...
The above code snippet illustrates a simple propagation over time, however, it is also possible to have the duration of propagation determined not by an overall time but by some other stopping condition which is a function of time and the state. This can be accomplished by attaching an event handler to the StepTaken (add / remove) event, which will fire every time the propagator takes a step, as shown below:
// The following allows us to propagate all at once or step the state manually final NumericalPropagator propagator = state.createPropagator(group); // Note: the "IntegrationPoint" from the PropagationNewtonianPoint cannot be evaluated // outside of the context of the NumericalPropagator. However, the StepTaken event // occurs inside the scope of propagation and the point is created in the same EvaluatorGroup. // So, the evaluation of the point will represent the current position and velocity in the state. // Similarly, any evaluator which was built off of the point will evaluate using the current // position and velocity. final PointEvaluator activePointEvaluator = GeometryTransformer.observePoint(position.getIntegrationPoint(), position.getIntegrationFrame(), group); propagator.addStepTaken(EventHandler.of((sender, e) -> { JulianDate t = e.getCurrentTime(); double[] x = e.getCurrentState(); StepSizeInformation info = e.getStepSizeInformation(); // The following two methods of retrieving the position are equally valid... Motion1<Cartesian> posVel = propagator.getConverter().convertState(positionID, x); // ...but this one is faster and eliminates the need to perform a transformation manually // if the position is needed in a different frame posVel = activePointEvaluator.evaluate(e.getCurrentTime(), 1); // Check for a "stopping condition" and if reached stop propagation if (posVel.getValue().getMagnitude() > 1e8) { e.setIndication(PropagationEventIndication.STOP_PROPAGATION_AFTER_STEP); } else { e.setIndication(PropagationEventIndication.CONTINUE_PROPAGATION); } }));
It is also possible to use the propagator in situations where surrounding code is iterating (or "shooting") to find some desired final state based on an initial guess. It's always possible to create a new propagator, but if the only things that change are the initial epoch and initial state, it's also possible to reset the propagator to a new set of initial conditions and try again. To do this, use the PropagationStateConverter in reverse to specify new initial conditions and update the initial state accordingly, then call the reset method on the propagator.
Without calling reset, each call to propagate will start where the previous call left off. This is designed to allow systems which propagate for extended periods of time to stop intermittently to verify the ephemeris, write or commit the data, or provide a means of specifying segments of propagation. However, note that if any of the differential equations defined on the state elements need to change, a new propagator will need to be created based on the new equations. However, to start the new propagator where the last one left off, simply set the new propagator's InitialState (get) to a copy of the old propagator's CurrentState (get).
// If conducting an iterative analysis with the same configuration (same state, force models, correctors, etc)... // Reinitialize the state and repropagate double[] newInitialState = propagator.getInitialState(); // Gets a copy Motion1<Cartesian> newInitialConditions = new Motion1<>(nextInitialPosition, nextInitialVelocity); propagator.getConverter().adjustState(positionID, newInitialState, newInitialConditions); propagator.reset(epoch, newInitialState); rawData = propagator.propagate(Duration.fromSeconds(totalTime), 1); // To use a different state or different force models, create a new NumericalPropagatorDefinition with new StateElements and ForceModels