Using NRLMSISE-00 with numerical propagator

You don’t need to, just transform the initial state and use propagator.resetInitialState(myNewState)

As you read it it’s not recommended so I would avoid it.
I wasn’t aware of that, thank you for pointing it (the explanation is page 57 of the CCSDS ODM blue book mentioned in the Javadoc)
However, ince the frame is ambiguous, your initial state converted to EME2000 will still bear this ambiguity (since we’re not 100% sure how the original TEME frame you used should be defined). So this small uncertainty will be propagated through.
That being said, I would still follow CCSDS and Orekit recommendation of propagating in a non-ambiguous inertial frame like EME2000 or GCRF

@MaximeJ

Thank you for this tip Maxime, I’ve implemented it that way and it worked. I tried to replicate the procedure with the DSST propagator using the JB2008 atmospheric model but i get the following error.
“Exception in thread “main” org.orekit.errors.OrekitException: altitude (-1,031,992.212 m) is below the 90,000 m allowed threshold”
I have an altitude detector implemented that’s meant to stop the propagation when the altitude gets below 120km but it doesn’t seem to work with the DSST propagator. My code is shown below.

    // Set path to orekit data folder
    File orekitData = new File("Path to Orekit Data folder");
    DataProvidersManager manager = DataContext.getDefault().getDataProvidersManager();
    manager.addProvider(new DirectoryCrawler(orekitData));

    // TimeScale
    TimeScale timescale = TimeScalesFactory.getUTC();

    // Enter TLE
    String line1 = "1 23757U 95074A   12012.49666365  .00007180  00000-0  20301-3 0  9995";
    String line2 = "2 23757 022.9885 165.8605 0007544 261.0176 098.9451 15.32981482889705";
    TLE inputTLE = new TLE(line1, line2, timescale);
    System.out.println(inputTLE.getDate());

    // Retrieve Propagator used for TLE
    Propagator tlePropagator = TLEPropagator.selectExtrapolator(inputTLE);
    Orbit initialOrbit = new CartesianOrbit(tlePropagator.getInitialState().getOrbit());
    SpacecraftState initialState = new SpacecraftState(initialOrbit, mass);

    // Initial reference Orbit for the builder
    Orbit referenceOrbit = tlePropagator.getInitialState().getOrbit();
    System.out.println("Initial orbit");
    System.out.println(initialState.getDate());
    System.out.println(initialState.getOrbit());

    // Set up Integrator Parameters
    double minStep = 360;  // Minimum TimeStep
    double maxStep = 86400;  // Maximum TimeStep
    double dP = 10;  // Position Tolerance

    // Builder
    DormandPrince853IntegratorBuilder builder = new DormandPrince853IntegratorBuilder(minStep, maxStep, dP); // Propagator Factory
    DSSTPropagatorBuilder dsstPropbuilder = new DSSTPropagatorBuilder(referenceOrbit,builder,dP, PropagationType.MEAN,PropagationType.MEAN);
    dsstPropbuilder.setMass(mass);

    // Add Force models to Fitted Propagator Builder
    // Initialize Solar Activity Provider Data Provider
    final JB2008SpaceEnvironmentData jbdata = new JB2008SpaceEnvironmentData(JB2008SpaceEnvironmentData.DEFAULT_SUPPORTED_NAMES_SOLFSMY,JB2008SpaceEnvironmentData.DEFAULT_SUPPORTED_NAMES_DTC,manager,timescale);

    // Initialize JB2008 Atmospheric Model
    final JB2008 jb2008  = new JB2008(jbdata,sun, earth);

    dsstPropagatorBuilder.addForceModel(new DSSTAtmosphericDrag(jb2008, Cd, area, provider.getMu()));

    //  SGP4 Propagator Converter
    FiniteDifferencePropagatorConverter dsstFitter = new FiniteDifferencePropagatorConverter(dsstPropBuilder, 1.0e-3, 1000);

    // Fitting Parameters
    double duration = 86400.0; // Fitting Timespan
    double stepSize = 300.0;   // Fitting Stepsize
    Double points = duration / stepSize;  // Number of fitting points

    // Convert SGP4 Propagator to DSST Propagator
    System.out.println("START DSST PROPAGATOR FITTING");
    dsstFitter.convert(tlePropagator, duration, points.intValue()); // Conversion from SGP4 to DSST Propagator
    System.out.println("END DSST PROPAGATOR FITTING");
    DSSTPropagator dsstPropagator = (DSSTPropagator) dsstFitter.getAdaptedPropagator();  // Retrieving the converted numerical propagator
    System.out.println(dsstPropagator.getInitialState().getOrbit());

    // Converting Initial State to EME2000 Frame
    TimeStampedPVCoordinates tspvc1 = dsstPropagator.getInitialState().getPVCoordinates(FramesFactory.getEME2000());
    CartesianOrbit corbit = new CartesianOrbit(tspvc1,FramesFactory.getEME2000(),dsstPropagator.getMu());
    SpacecraftState newState = new SpacecraftState(corbit,mass);
    dsstPropagator.resetInitialState(newState);


    // Create Earth Model
    OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING,
            FramesFactory.getITRF(IERSConventions.IERS_2010, false));

    // Re-entry Detector
    EventDetector altdet = new AltitudeDetector(maxCheck, threshold, alt, earth).withHandler(((s, detector, decreasing) -> {
        System.out.println("Altitude is " + FastMath.min(((s.getA() * (1 + s.getE())) - Constants.WGS84_EARTH_EQUATORIAL_RADIUS) / 1000, ((s.getA() * (1 - s.getE())) - Constants.WGS84_EARTH_EQUATORIAL_RADIUS) / 1000) + " km and the spacecraft will re-enter soon");
        System.out.println("Re-entry event occurs on " + s.getDate().toString());
        return Action.STOP;
    }));


    // Add Event detector
    dsstPropagator.addEventDetector(altdet);

    // Setup Step Handlers
    final StatesHandler FittedStepHandler   = new StatesHandler();

    // Add fixed step handlers to the propagator
    dsstPropagator.getMultiplexer().add(3600., FittedStepHandler);

    // Get Final States
    final SpacecraftState finalState = dsstPropagator.propagate(initialState.getDate().shiftedBy(10. * Constants.JULIAN_YEAR));

    // Retrieve the states
    final List<SpacecraftState>  FittedStates = FittedStepHandler.getStates();

    final File output = new File("Output path" + name + ".txt");
    try (PrintStream stream = new PrintStream(output, StandardCharsets.UTF_8)) {
        stream.println("          Date                   SMA        Ecc        Incl        RAAN         AoP          MAN   ");
        stream.println("   [YYYY-MM-DD hh:mm:ss]         (km)                  (deg)       (deg)        (deg)        (deg)   ");

        for (SpacecraftState numstate: FittedStates){
            KeplerianOrbit Orb = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(numstate.getOrbit());
            String text = " %s   %.4f   %.7f   %.5f   %.5f   %.5f   %.5f";
            text = String.format(text,Orb.getDate(),Orb.getA()/1000,Orb.getE(),FastMath.toDegrees(Orb.getI()),FastMath.toDegrees(Orb.getRightAscensionOfAscendingNode()),FastMath.toDegrees(Orb.getPerigeeArgument()),FastMath.toDegrees(Orb.getMeanAnomaly()));
            stream.println(text);
        }
    }
    final String resultSavedAs = "Output file saved to file " + name + ".txt in specified path";
    System.out.println(resultSavedAs);

    KeplerianOrbit Neworbit = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(finalState.getOrbit());
    System.out.println("Final Fitted orbit keplerian elements");
    System.out.println(Neworbit.toString());
    double years = (finalState.getDate().durationFrom((initialState.getDate()))) / (Constants.JULIAN_YEAR);
    System.out.println("The remaining Lifetime is " + years + "years");
    System.out.println("The remaining Lifetime with a 5% margin is " + years * 1.05 + "years");



}

private static class StatesHandler implements OrekitFixedStepHandler {

    // Points
    private final List<SpacecraftState> states;

    // Simple Constructor//
    StatesHandler() {
        // prepare an empty list of SpacecraftStates
        states = new ArrayList<SpacecraftState>();
    }

    public void handleStep(final SpacecraftState currentState) {
        // add the current state
        states.add(currentState);
    }

    /**
     * Get the list of handled orbital elements.
     *
     * @return orbital elements list
     */
    public List<SpacecraftState> getStates() {
        return states;
    }
}

Hi @dl00718,

I reckon getting such a negative altitude is weird, I’ll try to look into it.

I don’t have data for JB2008 so I’ll use NRLMSISE-00 instead, hoping to raise the same issue.

Could you please provide the data used for mass, Cd, area, the degree/order used for the gravity potential and the maxCheck and threshold used for the altitude detector ?

Hi Maxime,
Thanks for the timely response, the values are provided below.
Mass = 3007.93kg
Cd = 2.2
Area = 11.3162
Gravity potential degree and order = 6 , 6
I also had Sun and Moon third body perturbations
maxCheck = 60
threshold = 0.001

Thank you

Thank you for the clarifications !

Does the problem occur during the conversion or after during the 10 years propagation ?

If it occurs during the propagation could you simply share the converted initial state of the propagator (the conversion does not seem to work very well with MSIS00 here)

Hi Maxime,
The problem occured during propagation. I changed the maxCheck time to 7200 and that seems to fix it.
The initial state in the EME2000 frame is
SpacecraftState{orbit=Cartesian parameters: {P(-6638439.662248914, 1692150.1913326886, 8046.025780697497), V(-1733.267305451603, -6804.2916712986425, 2979.0833184996322)}, attitude=org.orekit.attitudes.Attitude@5fd4f8f5, mass=3007.93, additional={}, additionalDot={}}

By the way is my method for converting the initial state from TEME to EME2000 correct?
Thank you.

Hi,

Thanks for the info on the initial state.

Great, I suspected something related to a bad configuration of the altitude detector. Although I don’t see why increasing the maximum check interval would fix the problem. I’ll try to take a look when I have time.

It seems fine yes.

@MaximeJ
Actually I take that back. The error still occurs depending on the value of Position Tolerance i set when implementing the DormandPrinceIntegratorBuilder and the DSSTPropagator Builder. Using a value of 1e-3m works but switching to 1m or 10m as you used in an example on another thread, doesn’t work and leads to the negative altitude error.

Ok thanks for the update.

Quick question, we noted that you didn’t add any zonal or tesseral forces to your DSST propagator.
Is that true or does it just doesn’t appear on the snippet of code you sent ?

Could you send the last orbital elements written in your output file before the crash. So we don’t have to run the whole 10 years propagation to inspect the potential bug ?
And do you have a stack trace of the exception ?

My guess is that the integrator is trying steps that are too big and crashes when it evaluates the next state because the altitude is too low.
When reentering, if you’re flying downward at a few km/s it takes only tens of seconds to go from an altitude above 120km to an altitude below 90km.
So before the crash, the previous integration step has its previous and current state (start and end of the integration step) above 120km.
But when evaluating the next step, it tries to compute a state with an altitude below 90km and crashes because of the atmospheric model.
And that happens before the altitude detector is activated on this step.
That would explain why lowering the position tolerance solves the problem.

That being said, I’m not very familiar with the interconnection between integration steps and event detection, @luc and @evan.ward, do you have any ideas on this ?

I’ll take a look, do we have a failing test case?

I’ve another question. Does the error occur using a numerical propagator?

I agree that for 10 years propagation, the DSST is interesting. But I just would like to know if the error also occurs with the numerical method.

Bryan

I tried using the mass of code from above, but it was missing some variable definitions and other data.

I’ll take a look at the issue if you can make a JUnit test case that runs and fails. Preferably a minimal test case that only includes what is necessary to reproduce the issue and runs quickly. (That greatly helps with debugging!) I saw that you’re using JB2008. If that is necessary to reproduce the issue then you’ll also need to include those input files. Attaching a patch is a great way to include everything.

Back trace from the exception would also be useful.

I looked through the open issues, but nothing jumped out as being immediately related. Maxime’s theory sounds plausible, but I’m not sure why changing the maxCheck for the event detector would fix it. The step is computed before the event detector is called. Perhaps there is something extra going on with the DSST?

Hi @MaximeJ @bcazabonne @evan.ward,

Thank you all for helping out. I think the time step is the issue as @MaximeJ suggests, because reducing the step-size and position tolerance makes it work, but then we lose the advantange of the semi-analytical propagator.

@bcazabonne The error doesn’t occur when i use a numerical propagator, but it does with the DSST propagator using both Harris Priester and JB2008 atmospheric models. @evan.ward I believe this user had a similar issue https://forum.orekit.org/t/dsst-propagator-issues-at-low-altitudes/640

Here’s the last set of orbital elements written to my output file (Angles in degrees, sma in Km).
Date : 2021-06-16T11:55:08.73936Z
SMA : 6552.9290
Ecc: 0.0003071
Incl: 22.97438
RAAN: 162.40034
AoP: -185.05898
MAN: 58.98348

@evan.ward I have attached the test case and a unit test, and heres a copy of the backtrace from the exception.

Exception in thread "main" org.orekit.errors.OrekitException: altitude (-5,432,513.404 m) is below the 100,000 m allowed threshold
	at org.orekit.models.earth.atmosphere.HarrisPriester.getDensity(HarrisPriester.java:283)
	at org.orekit.models.earth.atmosphere.HarrisPriester.getDensity(HarrisPriester.java:393)
	at org.orekit.forces.drag.DragForce.acceleration(DragForce.java:80)
	at org.orekit.propagation.semianalytical.dsst.forces.AbstractGaussianContribution$IntegrableFunction.value(AbstractGaussianContribution.java:1003)
	at org.orekit.propagation.semianalytical.dsst.forces.AbstractGaussianContribution$GaussQuadrature.basicIntegrate(AbstractGaussianContribution.java:1472)
	at org.orekit.propagation.semianalytical.dsst.forces.AbstractGaussianContribution$GaussQuadrature.integrate(AbstractGaussianContribution.java:1383)
	at org.orekit.propagation.semianalytical.dsst.forces.AbstractGaussianContribution.getMeanElementRate(AbstractGaussianContribution.java:388)
	at org.orekit.propagation.semianalytical.dsst.forces.AbstractGaussianContribution.getMeanElementRate(AbstractGaussianContribution.java:300)
	at org.orekit.propagation.semianalytical.dsst.DSSTPropagator$Main.elementRates(DSSTPropagator.java:1173)
	at org.orekit.propagation.semianalytical.dsst.DSSTPropagator$Main.computeDerivatives(DSSTPropagator.java:1152)
	at org.orekit.propagation.integration.AbstractIntegratedPropagator$ConvertedMainStateEquations.computeDerivatives(AbstractIntegratedPropagator.java:758)
	at org.hipparchus.ode.ExpandableODE.computeDerivatives(ExpandableODE.java:134)
	at org.hipparchus.ode.AbstractIntegrator.computeDerivatives(AbstractIntegrator.java:265)
	at org.hipparchus.ode.nonstiff.EmbeddedRungeKuttaIntegrator.integrate(EmbeddedRungeKuttaIntegrator.java:252)
	at org.orekit.propagation.integration.AbstractIntegratedPropagator.integrateDynamics(AbstractIntegratedPropagator.java:477)
	at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:425)
	at org.orekit.propagation.integration.AbstractIntegratedPropagator.propagate(AbstractIntegratedPropagator.java:385)
	at test.reentry(test.java:100)
	at test.main(test.java:106)

Thank you
test.java (5.7 KB)
testTest.java (429 Bytes)

Thanks for the test case! That works well.

I made maxCheck smaller and it did not change the result of the test. So I believe that means we can rule out the event detector as the issue.

From running the test and the back trace I think Maxime’s theory is correct. I’m guessing it is not an issue for NumericalPropagator because the step sizes are much smaller.

So how to fix it? One idea would be to borrow an idea from how maneuvers are modeled. For maneuvers a whole integration step is taken with the thruster on, then the step is later truncated to the time the thruster turned off. This produces better behavior with the integrator. We could do the same for drag forces. Do not throw an exception when computing acceleration. Perhaps just continue using the density at the minimum altitude of the model for the altitudes below it. Then include a force model event detector that will truncate the step. This would allow propagation down to the minimum altitude. Or the event detector could throw an exception to end with an error. Any user event detectors would be processed first because events are handled in chronological order.

Regards,
Evan

Hi @evan.ward, thanks for the feedback.

I believe so too, changing the maxCheck doesn’t have an effect and it depends on the step sizes, which is why it works with the numerical propagator.

How would I go about implementing this, Is there an example you could reference?

Thank you

Good question. The contributing guide is here: Orekit – Contributing to Orekit

Making an issue on GitLab is a good first step. I think you would modify the the getEventDetectors() in DragForce and DsstDragForce to return an event detector for the minimum altitude. Then modify all the implementations of Atmosphere to return a density value instead of throwing an exception for invalid altitudes. Might need to add a method to Atmosphere to query the minimum altitude. Alternatively one could modify the DragForce implementations to catch altitude too low exceptions and return a reasonable acceleration.

I’ll review your merge request when you submit it. Thanks for your contribution!

Thank you @evan.ward for the investigation and @dl00718 for the test and information,

Evan, your solution would indeed fix the problem !

I’ve played around a bit with dl00718 code and found the following info that can help.
I added a step handler

        dsstProp.getMultiplexer().add(new OrekitStepHandler() {
            
            int nStep = 0;
            
            @Override
            public void handleStep(OrekitStepInterpolator interpolator) {
                SpacecraftState s = interpolator.getCurrentState();
                double stepSize = s.getDate().durationFrom(interpolator.getPreviousState().getDate());
                System.out.format(Locale.US, "Step %d - dt: %10.3f days / step: %10.3f s /  Alt: %10.3f km\n", 
                                  nStep++, s.getDate().durationFrom(initDate) / Constants.JULIAN_DAY, stepSize,
                                  (s.getPVCoordinates().getPosition().getNorm() - Constants.WGS84_EARTH_EQUATORIAL_RADIUS) / 1000.);                
            }
        });

Then I’ve tried some (minStep / position tolerance) configurations for the integrator:

minStep = 3600.
dP = 10.

→ (default config) Crashes with HarrisPriester minimum altitude exception

minStep = 3600.
dP = 0.1

→ Crashes with Hipparchus ODE message: MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION. The integrator tries to reduce the step to fulfill the constraint of 0.1m on the position tolerance but cannot because the minStep is too big.

minStep = 60.
dP = 0.001

→ Working combo: After around 1540 days, the altitude detector kicks in with no crash. Last step is around 60s and truncated at ~41s by the detector.

So, ultimately, it is a configuration issue of the integrator.
Reducing minStep and dP does not impair DSST performances because the maximum step size of 2 days is used until altitude gets below ~270km (after 1513 days of propagation) and the integrator starts reducing the steps.

As a user, I would like the integrator to reduce the step duration until the minimum step size is reached and throw an exception like in the second case above.
That way I would know what to do to improve the configuration, which I found is not explicit with the first case (altitude is too low).

To me, this looks like the multiple questions we had on the forum with an exception thrown due to a negative eccentricity (see this thread for example, though it is related to initial time step only and can be fixed differently).

So I was wondering if, on top of Evan’s proposition, we couldn’t implement a mechanism that would invalidate an integrator step given some exceptions (altitude too low, negative eccentricity etc.).
That way the integrator would try to reduce the step until the minimum is reached instead of blindly throwing the exception and leaving the user in the dark.
That would require implementing a specific exception type extending OrekitException and catching it at integrator level.
I haven’t checked if it is doable with current Hipparchus or Orekit APIs though.
Do you think that it makes sense ?

Interesting proposition. How would the integrator know where to end the step?

If it is guess and check that would be slow. Perhaps include that information in the specialized exception? But the case above the end of the step is based on altitude and to compute the time of the altitude crossing an event detector would be needed. In either case the new time to end the step may cause the step duration to be less than minStep, which would cause the integrator to throw an exception. I don’t know the answers…

Hi @MaximeJ,

This configuration works for me, thank you very much.

@evan.ward I’ll give this a try and see if i can implement it. Thank you

Hi @dl00718,

You are welcome !
And thank you for the upcoming contribution !

Good question, I don’t have the answer yet…

I think that is the best way to do it !

That would be, in my opinion, a proper behavior. The exception is explicit and the user can easily fix it by lowering the minimum step.

It’s true that we will probably need a detector to find the proper step. This won’t be the case for the “negative eccentricty” exception for example.
Maybe we could imagine a mix of your solution and something along the lines of what I’ve tried below.

Probably yes, still I’ve tried a very rough implementation of this in EmbeddedRungeKuntaIntegrator#integrate where I divide current step by 2 every time computeDerivatives throws an ALTITUDE_BELOW_ALLOWED_THRESHOLD exception.
Until it gets below minimum step. In that case, I try the minimum step once and if the altitude is still too low, then it throws a MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION exception.

This gives the following results:

minStep = 3600.
dP = 10.

→ (default config) This one still crashes but this time with a MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION exception.
Log below (“Model Exception” in that case is the ALTITUDE_BELOW_ALLOWED_THRESHOLD exception)

Step 771 - dt:   1540.339 days / step: 172800.000 s /  Alt:    176.307 km
	Model Exception caught - Previous step: 172800.0 / New step: 86400.0
Step 772 - dt:   1541.196 days / step:  74006.216 s /  Alt:    152.422 km
	Model Exception caught - Previous step: 106601.41359396781 / New step: 53300.706796983904
	Model Exception caught - Previous step: 53300.706796983904 / New step: 26650.353398491952
	Model Exception caught - Previous step: 26650.353398491952 / New step: 13325.176699245976
Step 773 - dt:   1541.350 days / step:  13325.177 s /  Alt:    144.398 km
	Model Exception caught - Previous step: 21702.721715022282 / New step: 10851.360857511141
Step 774 - dt:   1541.434 days / step:   7279.631 s /  Alt:    131.044 km
	Model Exception caught - Previous step: 12364.10248376542 / New step: 6182.05124188271
	Model Exception caught - Previous step: 6182.05124188271 / New step: 3091.025620941355
	Minimum step size (3600.0) reached !
	Model Exception caught - Previous step: 3600.0 / New step: 1800.0
Exception in thread "main" org.orekit.errors.OrekitException: pas minimal (3,60E03) atteint, l'intégration nécessite 1,80E03

The exception is easier to fix for the user but he won’t know that it originates from the atmosphere model anymore.

minStep = 1200.
dP = 10.

→ Works with several catching of the atmosphere model exception

Step 771 - dt:   1539.038 days / step: 172800.000 s /  Alt:    196.793 km
Step 772 - dt:   1541.038 days / step: 172800.000 s /  Alt:    162.573 km
	Model Exception caught - Previous step: 163465.3780505176 / New step: 81732.6890252588
	Model Exception caught - Previous step: 81732.6890252588 / New step: 40866.3445126294
	Model Exception caught - Previous step: 40866.3445126294 / New step: 20433.1722563147
Step 773 - dt:   1541.275 days / step:  20433.172 s /  Alt:    150.145 km
	Model Exception caught - Previous step: 32637.238297095602 / New step: 16318.619148547801
Step 774 - dt:   1541.440 days / step:  14328.235 s /  Alt:    130.133 km
	Model Exception caught - Previous step: 15690.604490723932 / New step: 7845.302245361966
	Model Exception caught - Previous step: 7845.302245361966 / New step: 3922.651122680983
	Model Exception caught - Previous step: 3922.651122680983 / New step: 1961.3255613404915
Step 775 - dt:   1541.463 days / step:   1961.326 s /  Alt:    124.568 km
	Model Exception caught - Previous step: 2357.391082375647 / New step: 1178.6955411878234
	Minimum step size (1200.0) reached !
Step 776 - dt:   1541.472 days / step:    785.688 s /  Alt:    118.806 km
2016-04-01T23:15:10.93562067578793Z - Altitude is 117.89798498219903 km and the spacecraft will re-enter soon
Re-entry event occurs on 2016-04-01T23:15:10.93562067578793Z
Step 777 - dt:   1541.472 days / step:      0.000 s /  Alt:    118.806 km
Spacecraft Re-entered