Click or drag to resize

Stopping Conditions

Propagation in STK Components usually stops after a span of time. For example, a NumericalPropagator propagates for a given duration. However, what if you want to stop based on some other criteria? Maybe you want to stop after your aircraft reaches a certain amount of fuel, or maybe stop propagating a satellite at a certain true anomaly so that you can perform a maneuver. Stopping conditions enable you to detect these kinds of events to stop your propagator.

Note Note

The functionality described in this topic requires a license for the Segment Propagation Library.

Overview

Stopping Conditions will evaluate a value at every step of propagation. That value is treated as a function and sampled by a JulianDateFunctionExplorer. The propagator will continue to step until the function explorer detects extrema or threshold crossings. See the Exploring Functions topic for more information on the function explorer. When events are detected, the function explorer will then drive the propagator, forcing it to take steps to the exact event (within the specified tolerances). Additional checks can be done (such as a counter or additional constraints), either when the event is first detected, or when it is precisely found.

Let's say you want to stop when your satellite reaches a certain altitude with a two-body propagator. To do this, you start by configuring your TwoBodyStoppablePropagator. The following code sample demonstrates this:

Java
EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth();

TwoBodyStoppablePropagator stoppablePropagator = new TwoBodyStoppablePropagator();

// configure basic settings
stoppablePropagator.setStep(Duration.fromSeconds(120.0));
stoppablePropagator.setPropagationFrame(earth.getInertialFrame());
stoppablePropagator.setGravitationalParameter(WorldGeodeticSystem1984.GravitationalParameter);
stoppablePropagator.setPropagationPointIdentification("SatelliteMotion");

// add the altitude stopping condition
ScalarCartographicElement altitudeScalar = new ScalarCartographicElement(
        earth, 
        stoppablePropagator.getPropagationPoint(), 
        CartographicElement.HEIGHT);
ScalarStoppingCondition altitudeStoppingCondition = new ScalarStoppingCondition(
        altitudeScalar, 
        500000.0, // 500 km threshold (since this is a cartographic location though, it is measured in the fixed frame)
        0.01,  // 0.01 meter tolerance
        StopType.ANY_THRESHOLD);
altitudeStoppingCondition.setName("Altitude_stopping_condition");
stoppablePropagator.getStoppingConditions().add(altitudeStoppingCondition);

// it is good to add a maximum duration stopping condition just in case
DurationStoppingCondition maximumDuration = new DurationStoppingCondition(Duration.fromDays(2.0));
maximumDuration.setName("Maximum_duration_stopping_condition");
stoppablePropagator.getStoppingConditions().add(maximumDuration);

// get the propagator
SinglePointStoppablePropagator propagator = stoppablePropagator.getSinglePointPropagator();

// create the initial conditions
JulianDate initialDate = TimeConstants.J2000;
Motion1<Cartesian> initialState = new Motion1<>(new Cartesian(6600000.0, 0.0, 0.0), new Cartesian(0.0, 5800.0, 5800.0));

// create propagator results
SinglePointStoppablePropagatorResults results = propagator.propagateUntilStop(initialDate, initialState, IntegrationSense.INCREASING, null);

Motion1<Cartesian> finalEphemerisPoint = results.getFinalState().getMotion(stoppablePropagator.getPropagationPointIdentification());
JulianDate finalDate = results.getFinalDate();
Cartographic finalCartographic = earth.getShape().cartesianToCartographic(convertFromInertialToFixed(finalDate, finalEphemerisPoint).getValue());
// the height will be 500 km

There is also a StoppableNumericalPropagator. Using this type is similar to the two-body example above, but take care to not lose track of the IntegrationPoint (get) you are using. The following code sample demonstrates this:

Java
EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth();

StoppableNumericalPropagator stoppablePropagator = new StoppableNumericalPropagator();

PropagationNewtonianPoint propagationPoint = new PropagationNewtonianPoint();
propagationPoint.setIdentification("SatelliteMotion");
// initial values must be set, but these will get reset with the passed in initial state.
propagationPoint.setInitialPosition(new Cartesian(500.0, 0.0, 0.0));
propagationPoint.setInitialVelocity(new Cartesian(0.0, 100.0, 100.0));
propagationPoint.setMass(new ScalarFixed(500));

TwoBodyGravity twoBodyForceModel = new TwoBodyGravity(propagationPoint.getIntegrationPoint(), earth, WorldGeodeticSystem1984.GravitationalParameter);
propagationPoint.getAppliedForces().add(twoBodyForceModel);

RungeKutta4Integrator integrator = new RungeKutta4Integrator();
integrator.setInitialStepSize(30.0);

NumericalPropagatorDefinition numericalPropagator = new NumericalPropagatorDefinition();
numericalPropagator.setEpoch(TimeConstants.J2000);
numericalPropagator.getIntegrationElements().add(propagationPoint);
numericalPropagator.setIntegrator(integrator);
stoppablePropagator.setPropagatorDefinition(numericalPropagator);

// add the altitude stopping condition
ScalarCartographicElement altitudeScalar = new ScalarCartographicElement(
        earth, 
        propagationPoint.getIntegrationPoint(), // the integration point must be used here. It will get updated with every evaluation of the numerical propagator
        CartographicElement.HEIGHT);
ScalarStoppingCondition altitudeStoppingCondition = new ScalarStoppingCondition(
        altitudeScalar, 
        500000.0, // 500 km threshold (since this is a cartographic location though, it is measured in the fixed frame)
        0.01, // 0.01 meter tolerance 
        StopType.ANY_THRESHOLD);
altitudeStoppingCondition.setName("Altitude_stopping_condition");
stoppablePropagator.getStoppingConditions().add(altitudeStoppingCondition);

// it is good to add a maximum duration stopping condition just in case
DurationStoppingCondition maximumDuration = new DurationStoppingCondition(Duration.fromDays(2.0));
maximumDuration.setName("Maximum_duration_stopping_condition");
stoppablePropagator.getStoppingConditions().add(maximumDuration);

// get the propagator
StoppablePropagator propagator = stoppablePropagator.getStoppablePropagator();

Motion1<Cartesian> initialMotion = new Motion1<>(new Cartesian(6600000.0, 0.0, 0.0), new Cartesian(0.0, 5800.0, 5800.0));
BasicState initialState = new BasicState();
initialState.addStateElementMotion(propagationPoint.getIdentification(), initialMotion);
initialState.setCurrentDate(TimeConstants.J2000);

// create propagator results
StoppablePropagatorResults results = propagator.propagateUntilStop(initialState, null);

Motion1<Cartesian> finalEphemerisPoint = results.getFinalState().getMotion(propagationPoint.getIdentification());       
JulianDate finalDate = results.getFinalDate();
Cartographic finalCartographic = earth.getShape().cartesianToCartographic(convertFromInertialToFixed(finalDate, finalEphemerisPoint).getValue());
// the height will be 500 km

In both cases, you can confirm that the altitude of the satellite at the end of its propagation is indeed at the requested altitude.

Notice that the definition of the altitude scalar and stopping condition relied on a placeholder point in both examples. Because the altitude will need to be computed based on the propagated state at each step (so as to check if the threshold has been crossed), the placeholder point will need to be used. As a result, individual StoppingConditions are often specific to a particular StoppablePropagator instance.

Adapters

StoppablePropagators are generally meant to propagate starting from a given ITimeBasedState. However, many of the actual StoppablePropagators can be configured with an optional initial state. If an initial state is passed in, the default initial state will be ignored, unless otherwise stated.

Since these propagators propagate from a passed in state, StateElementAdapters can - and in some cases must - be specified for the elements getting propagated. These adapters will perform the necessary ReferenceFrame transformations, or other transformations. If the passed in state is already in the correct frame, then adapters may not be needed. Adapters will also be ignored if the initial state that the propagator was configured with is used.

Constraints

You are able to provide a list of stopping conditions to the propagator, but all it takes is one condition to be satisfied for propagation to stop. If you want to define other criteria for when to stop propagating, you can add StoppingConditionConstraints to your condition. Using the above example, if you wanted to stop propagating at a 500km altitude, but only after having propagated for 5 days, you could do it with two propagate segments, but it is simpler to add a DurationStoppingConditionConstraint to the altitude condition, as shown in the following code sample:

Java
propagatorDefinition.getAuxiliaryElements().add(propagatedAltitude);
ValueInStateStoppingCondition stoppingCondition = new ValueInStateStoppingCondition(
        propagatedAltitude.getIdentification(), 
        500000, // threshold meters
        0.1,    // tolerance, meters
        StopType.ANY_THRESHOLD);

DurationStoppingConditionConstraint constraint = new DurationStoppingConditionConstraint(
        Duration.fromDays(5.0),    // threshold
        Duration.fromSeconds(1.0), // tolerance
        WhenToCheckConstraint.WHEN_EVENT_IS_DETECTED,
        InequalityCondition.GREATER_THAN, 
        false); // use the absolute value of the propagation duration

stoppingCondition.getConstraints().add(constraint);

Constraints can be checked at one of two times: when a stop is detected or when the stop is exactly found. Checking the constraint when a stop is first detected can improve performance if finding the exact event of the stopping condition is costly. However, if the relevant value of your constraint may change significantly between when the event is detected and the time at its exact solution, checking it at the exact stop will ensure that the constraint is applied more precisely.

Note that StoppingConditions have a property that indicates whether to stop after an event has been found more than once. If you wanted to stop on the second time your satellite's altitude was 500 km, set StopOnEventNumber (get / set) to 2. When a StoppingConditionConstraint prevents a StoppingCondition from stopping propagation, that event that was detected will not be added to the count of events found.

There are some cases where StoppingConditions will be automatically added to a StoppablePropagator. If the availability of the underlying evaluator or propagator that is generating states is not infinite, a pair of EpochSecondsStoppingConditions can be added by calling addEndOfAvailabilityStoppingConditions, in order to prevent going outside of the availability.

Available StoppablePropagators

There are several StoppablePropagators that are included with STK Components.

Stopping Conditions and Segment Propagation

The provided PropagateSegments all use stopping conditions to end their propagation. PropagateSegment simply wraps and handles configuration and propagation of the wrapped StoppablePropagator (get / set). All adapters defined on the segment will get passed to the wrapped StoppablePropagator.

The StoppingConditions (get) are the same as the StoppingConditions (get) on the wrapped StoppablePropagatorDefinition. However, if the PropagateSegment is configured with a MaximumDuration (get / set), that will be added automatically to the wrapped stoppable propagator.

Each stopping condition in the PropagateSegment can execute an optional segment after it. By configuring an auto-segment by calling setStoppingConditionAutoSegment, you can treat the stopping conditions as an 'if' condition, propagating a different segment based on which condition was satisfied. The following code sample demonstrates this:

Java
EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
PropagateSegment geoOrbit = createAndConfigurePropagateSegment();

// we want to stay in our Geo Box, so add a stopping condition for when we violate our latitude limits
Scalar verticalLimitStoppingConditionScalar = 
        new ScalarAbsoluteValue(new ScalarCartographicElement(earth, 
                                                              parameterizedPoint, 
                                                              CartographicElement.LATITUDE));

ScalarStoppingCondition verticalStoppingCondition = 
        new ScalarStoppingCondition(verticalLimitStoppingConditionScalar, 
                                    0.1, 
                                    0.001, 
                                    StopType.ANY_THRESHOLD);

geoOrbit.getStoppingConditions().add(verticalStoppingCondition);
// and repeat this stopping condition 5 times
geoOrbit.setStoppingConditionAutoSegment(verticalStoppingCondition, getVerticalCorrectionSegmentList(), 5);

// in the same way, do the longitude violation stopping condition
Scalar horizontalLimitStoppingCondition = 
        new ScalarAbsoluteValue(Scalar.subtract(Scalar.toScalar(Trig.degreesToRadians(-140.0)),
                                                new ScalarCartographicElement(earth, 
                                                                              parameterizedPoint, 
                                                                              CartographicElement.LONGITUDE)));

ScalarStoppingCondition horizontalStoppingCondition = 
        new ScalarStoppingCondition(horizontalLimitStoppingCondition, 
                                    0.1, 
                                    0.001, 
                                    StopType.ANY_THRESHOLD);
geoOrbit.getStoppingConditions().add(horizontalStoppingCondition);

// repeat the condition 5 times too
geoOrbit.setStoppingConditionAutoSegment(horizontalStoppingCondition, getHorizontalCorrectionSegmentList(), 5);

// propagate
SegmentPropagator propagator = geoOrbit.getSegmentPropagator();
PropagateSegmentResults results = (PropagateSegmentResults) propagator.propagate();
// notice that results has many items in its SegmentResults

Tips for Using Stopping Conditions
  • Unless you know for sure that a stopping condition will be satisfied, you should generally include a DurationStoppingCondition that is guaranteed to not continue on forever. Also note that the PropagateSegment has a MaximumDuration (get / set) property to ensure propagation will indeed stop. It can be turned off if desired.

  • Scalars with discontinuities can confuse the system. A common example is stopping based on some true anomaly. The true anomaly will have a discontinuity at one of the nodes and that would technically be a threshold crossing. One way to get around this would be to square the scalar and adjust your threshold accordingly:

    Java
    double trueAnomalyIWantToStopAt = Math.PI / 4.0;
    ScalarModifiedKeplerianElement trueAnomalyScalar = new ScalarModifiedKeplerianElement(
            gravity, 
            satellitesPoint, 
            KeplerianElement.TRUE_ANOMALY, 
            earth.getInternationalCelestialReferenceFrame());
    
    ScalarExponent trueAnomalySquaredScalar = new ScalarExponent(
            trueAnomalyScalar, 
            Scalar.toScalar(2.0));
    
    AuxiliaryStateScalar propagatedTrueAnomalySquared = new AuxiliaryStateScalar();
    propagatedTrueAnomalySquared.setIdentification("true anomaly squared");
    propagatedTrueAnomalySquared.setAuxiliaryScalar(trueAnomalySquaredScalar);
    
    ValueInStateStoppingCondition trueAnomalySquaredStoppingCondition = new ValueInStateStoppingCondition(
            propagatedTrueAnomalySquared.getIdentification(), 
            0.0,   // threshold, radians squared
            0.01,  // tolerance, radians squared
            StopType.ANY_THRESHOLD);
    
    trueAnomalySquaredStoppingCondition.setThreshold(ValueDefinition.toValueDefinition(Math.pow(trueAnomalyIWantToStopAt, 2.0)));
    
    
  • Or, you can set the AngularSetting (get / set) to move the discontinuity away from the threshold. That will also let the system know to ignore the discontinuity.

    Java
    ScalarStoppingCondition periapsisStoppingCondition = new ScalarStoppingCondition(
            trueAnomalyScalar, 
            0.0,   // threshold, radians
            0.001, // value tolerance, radians
            StopType.ANY_THRESHOLD);
    periapsisStoppingCondition.setAngularSetting(CircularRange.NEGATIVE_PI_TO_PI);
    
    
  • When using a DurationStoppingCondition or a DurationStoppingConditionConstraint, the sign of the Duration matters. If you are propagating backwards, you must set your Duration to be negative as well, or else those conditions and constraints will never be satisfied.

Advanced - Using Stopping Conditions With the NumericalPropagator Directly

NumericalPropagator has a propagateUntilStop method that takes a list of stopping conditions. Generally it is best to use a NumericalPropagatorSegment, but if you want to manage the actual stopping conditions and NumericalPropagator yourself, you can. In this example, the altitude stopping condition from the first example above is passed into the NumericalPropagator directly:

Java
ArrayList<StoppingConditionEvaluator> conditions = new ArrayList<>();
EvaluatorGroup group = new EvaluatorGroup();
NumericalPropagator propagator = numericalPropagator.createPropagator(group);
StoppingConditionEvaluator condition = stoppingCondition.getEvaluator(group);
conditions.add(condition);

StoppableNumericalPropagatorResults results = propagator.propagateUntilStop(conditions, null);

DateMotionCollection1<Cartesian> ephemeris = 
        results.getPropagationHistory().getDateMotionCollection(satelliteMotionIdentifier);