AbstractAnalyticalPropagator.java
/* Copyright 2002-2016 CS Systèmes d'Information
* Licensed to CS Systèmes d'Information (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.propagation.analytical;
import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.math3.exception.NoBracketingException;
import org.apache.commons.math3.exception.TooManyEvaluationsException;
import org.apache.commons.math3.util.FastMath;
import org.orekit.attitudes.Attitude;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitInternalError;
import org.orekit.errors.PropagationException;
import org.orekit.frames.Frame;
import org.orekit.orbits.Orbit;
import org.orekit.propagation.AbstractPropagator;
import org.orekit.propagation.AdditionalStateProvider;
import org.orekit.propagation.BoundedPropagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.EventState;
import org.orekit.propagation.sampling.OrekitStepInterpolator;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.PVCoordinatesProvider;
import org.orekit.utils.TimeStampedPVCoordinates;
/** Common handling of {@link org.orekit.propagation.Propagator} methods for analytical propagators.
* <p>
* This abstract class allows to provide easily the full set of {@link
* org.orekit.propagation.Propagator Propagator} methods, including all propagation
* modes support and discrete events support for any simple propagation method. Only
* two methods must be implemented by derived classes: {@link #propagateOrbit(AbsoluteDate)}
* and {@link #getMass(AbsoluteDate)}. The first method should perform straightforward
* propagation starting from some internally stored initial state up to the specified target date.
* </p>
* @author Luc Maisonobe
*/
public abstract class AbstractAnalyticalPropagator extends AbstractPropagator {
/** Internal steps interpolator. */
private final BasicStepInterpolator interpolator;
/** Provider for attitude computation. */
private PVCoordinatesProvider pvProvider;
/** Start date of last propagation. */
private AbsoluteDate lastPropagationStart;
/** End date of last propagation. */
private AbsoluteDate lastPropagationEnd;
/** Initialization indicator of events states. */
private boolean statesInitialized;
/** Indicator for last step. */
private boolean isLastStep;
/** Event steps. */
private final Collection<EventState<?>> eventsStates;
/** Build a new instance.
* @param attitudeProvider provider for attitude computation
*/
protected AbstractAnalyticalPropagator(final AttitudeProvider attitudeProvider) {
setAttitudeProvider(attitudeProvider);
interpolator = new BasicStepInterpolator();
pvProvider = new LocalPVProvider();
lastPropagationStart = AbsoluteDate.PAST_INFINITY;
lastPropagationEnd = AbsoluteDate.FUTURE_INFINITY;
statesInitialized = false;
eventsStates = new ArrayList<EventState<?>>();
}
/** {@inheritDoc} */
public BoundedPropagator getGeneratedEphemeris() {
return new BoundedPropagatorView(lastPropagationStart, lastPropagationEnd);
}
/** {@inheritDoc} */
public <T extends EventDetector> void addEventDetector(final T detector) {
eventsStates.add(new EventState<T>(detector));
}
/** {@inheritDoc} */
public Collection<EventDetector> getEventsDetectors() {
final List<EventDetector> list = new ArrayList<EventDetector>();
for (final EventState<?> state : eventsStates) {
list.add(state.getEventDetector());
}
return Collections.unmodifiableCollection(list);
}
/** {@inheritDoc} */
public void clearEventsDetectors() {
eventsStates.clear();
}
/** {@inheritDoc} */
public SpacecraftState propagate(final AbsoluteDate start, final AbsoluteDate target)
throws PropagationException {
try {
lastPropagationStart = start;
final double dt = target.durationFrom(start);
final double epsilon = FastMath.ulp(dt);
interpolator.storeDate(start);
SpacecraftState state = interpolator.getInterpolatedState();
// evaluate step size
final double stepSize;
if (getMode() == MASTER_MODE) {
if (Double.isNaN(getFixedStepSize())) {
stepSize = FastMath.copySign(state.getKeplerianPeriod() / 100, dt);
} else {
stepSize = FastMath.copySign(getFixedStepSize(), dt);
}
} else {
stepSize = dt;
}
// initialize event detectors
for (final EventState<?> es : eventsStates) {
es.init(state, target);
}
// initialize step handler
if (getStepHandler() != null) {
getStepHandler().init(state, target);
}
// iterate over the propagation range
statesInitialized = false;
isLastStep = false;
do {
// go ahead one step size
interpolator.shift();
final AbsoluteDate t = interpolator.getCurrentDate().shiftedBy(stepSize);
if ((dt == 0) || ((dt > 0) ^ (t.compareTo(target) <= 0))) {
// current step exceeds target
interpolator.storeDate(target);
} else {
// current step is within range
interpolator.storeDate(t);
}
// accept the step, trigger events and step handlers
state = acceptStep(target, epsilon);
} while (!isLastStep);
// return the last computed state
lastPropagationEnd = state.getDate();
setStartDate(state.getDate());
return state;
} catch (PropagationException pe) {
throw pe;
} catch (OrekitException oe) {
throw PropagationException.unwrap(oe);
} catch (TooManyEvaluationsException tmee) {
throw PropagationException.unwrap(tmee);
} catch (NoBracketingException nbe) {
throw PropagationException.unwrap(nbe);
}
}
/** Accept a step, triggering events and step handlers.
* @param target final propagation time
* @param epsilon threshold for end date detection
* @return state at the end of the step
* @exception OrekitException if the switching function cannot be evaluated
* @exception TooManyEvaluationsException if an event cannot be located
* @exception NoBracketingException if bracketing cannot be performed
*/
protected SpacecraftState acceptStep(final AbsoluteDate target, final double epsilon)
throws OrekitException, TooManyEvaluationsException, NoBracketingException {
AbsoluteDate previousT = interpolator.getGlobalPreviousDate();
AbsoluteDate currentT = interpolator.getGlobalCurrentDate();
// initialize the events states if needed
if (!statesInitialized) {
if (!eventsStates.isEmpty()) {
// initialize the events states
final AbsoluteDate t0 = interpolator.getPreviousDate();
interpolator.setInterpolatedDate(t0);
final SpacecraftState y = interpolator.getInterpolatedState();
for (final EventState<?> state : eventsStates) {
state.reinitializeBegin(y, interpolator.isForward());
}
}
statesInitialized = true;
}
// search for next events that may occur during the step
final List<EventState<?>> occurringEvents = new ArrayList<EventState<?>>();
for (final EventState<?> state : eventsStates) {
if (state.evaluateStep(interpolator)) {
// the event occurs during the current step
occurringEvents.add(state);
}
}
// chronological or reverse chronological sorter, according to propagation direction
final int orderingSign = interpolator.isForward() ? +1 : -1;
final Comparator<EventState<?>> sorter = new Comparator<EventState<?>>() {
/** {@inheritDoc} */
public int compare(final EventState<?> es0, final EventState<?> es1) {
return orderingSign * es0.getEventTime().compareTo(es1.getEventTime());
}
};
while (!occurringEvents.isEmpty()) {
// handle the chronologically first event
Collections.sort(occurringEvents, sorter);
final Iterator<EventState<?>> iterator = occurringEvents.iterator();
final EventState<?> currentEvent = iterator.next();
iterator.remove();
// restrict the interpolator to the first part of the step, up to the event
final AbsoluteDate eventT = currentEvent.getEventTime();
interpolator.setSoftPreviousDate(previousT);
interpolator.setSoftCurrentDate(eventT);
// trigger the event
interpolator.setInterpolatedDate(eventT);
final SpacecraftState eventY = interpolator.getInterpolatedState();
currentEvent.stepAccepted(eventY);
isLastStep = currentEvent.stop();
// handle the first part of the step, up to the event
if (getStepHandler() != null) {
getStepHandler().handleStep(interpolator, isLastStep);
}
if (isLastStep) {
// the event asked to stop integration
return eventY;
}
final SpacecraftState resetState = currentEvent.reset(eventY);
if (resetState != null) {
resetIntermediateState(resetState, interpolator.isForward());
return resetState;
}
// prepare handling of the remaining part of the step
previousT = eventT;
interpolator.setSoftPreviousDate(eventT);
interpolator.setSoftCurrentDate(currentT);
// check if the same event occurs again in the remaining part of the step
if (currentEvent.evaluateStep(interpolator)) {
// the event occurs during the current step
occurringEvents.add(currentEvent);
}
}
final double remaining = target.durationFrom(currentT);
if (interpolator.isForward()) {
isLastStep = remaining < epsilon;
} else {
isLastStep = remaining > -epsilon;
}
if (isLastStep) {
currentT = target;
}
interpolator.setInterpolatedDate(currentT);
final SpacecraftState currentY = interpolator.getInterpolatedState();
for (final EventState<?> state : eventsStates) {
state.stepAccepted(currentY);
isLastStep = isLastStep || state.stop();
}
// handle the remaining part of the step, after all events if any
if (getStepHandler() != null) {
getStepHandler().handleStep(interpolator, isLastStep);
}
return currentY;
}
/** Get the mass.
* @param date target date for the orbit
* @return mass mass
* @exception PropagationException if some parameters are out of bounds
*/
protected abstract double getMass(final AbsoluteDate date)
throws PropagationException;
/** Get PV coordinates provider.
* @return PV coordinates provider
*/
public PVCoordinatesProvider getPvProvider() {
return pvProvider;
}
/** {@inheritDoc} */
public void resetInitialState(final SpacecraftState state)
throws PropagationException {
super.resetInitialState(state);
interpolator.globalCurrentDate = state.getDate();
interpolator.softCurrentDate = interpolator.globalCurrentDate;
}
/** Reset an intermediate state.
* @param state new intermediate state to consider
* @param forward if true, the intermediate state is valid for
* propagations after itself
* @exception PropagationException if initial state cannot be reset
*/
protected void resetIntermediateState(final SpacecraftState state, final boolean forward)
throws PropagationException {
interpolator.globalCurrentDate = state.getDate();
interpolator.softCurrentDate = interpolator.globalCurrentDate;
}
/** Extrapolate an orbit up to a specific target date.
* @param date target date for the orbit
* @return extrapolated parameters
* @exception PropagationException if some parameters are out of bounds
*/
protected abstract Orbit propagateOrbit(final AbsoluteDate date)
throws PropagationException;
/** Propagate an orbit without any fancy features.
* <p>This method is similar in spirit to the {@link #propagate} method,
* except that it does <strong>not</strong> call any handler during
* propagation, nor any discrete events, not additional states. It always
* stop exactly at the specified date.</p>
* @param date target date for propagation
* @return state at specified date
* @exception PropagationException if propagation cannot reach specified date
*/
protected SpacecraftState basicPropagate(final AbsoluteDate date) throws PropagationException {
try {
// evaluate orbit
final Orbit orbit = propagateOrbit(date);
// evaluate attitude
final Attitude attitude =
getAttitudeProvider().getAttitude(pvProvider, date, orbit.getFrame());
// build raw state
return new SpacecraftState(orbit, attitude, getMass(date));
} catch (OrekitException oe) {
throw new PropagationException(oe);
}
}
/** Internal PVCoordinatesProvider for attitude computation. */
private class LocalPVProvider implements PVCoordinatesProvider {
/** {@inheritDoc} */
public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame)
throws OrekitException {
return propagateOrbit(date).getPVCoordinates(frame);
}
}
/** {@link BoundedPropagator} view of the instance. */
private class BoundedPropagatorView
extends AbstractAnalyticalPropagator
implements BoundedPropagator, Serializable {
/** Serializable UID. */
private static final long serialVersionUID = 20151117L;
/** Min date. */
private final AbsoluteDate minDate;
/** Max date. */
private final AbsoluteDate maxDate;
/** Simple constructor.
* @param startDate start date of the propagation
* @param endDate end date of the propagation
*/
BoundedPropagatorView(final AbsoluteDate startDate, final AbsoluteDate endDate) {
super(AbstractAnalyticalPropagator.this.getAttitudeProvider());
if (startDate.compareTo(endDate) <= 0) {
minDate = startDate;
maxDate = endDate;
} else {
minDate = endDate;
maxDate = startDate;
}
try {
// copy the same additional state providers as the original propagator
for (AdditionalStateProvider provider : AbstractAnalyticalPropagator.this.getAdditionalStateProviders()) {
addAdditionalStateProvider(provider);
}
} catch (OrekitException oe) {
// as the providers are already compatible with each other,
// this should never happen
throw new OrekitInternalError(null);
}
}
/** {@inheritDoc} */
public AbsoluteDate getMinDate() {
return minDate;
}
/** {@inheritDoc} */
public AbsoluteDate getMaxDate() {
return maxDate;
}
/** {@inheritDoc} */
protected Orbit propagateOrbit(final AbsoluteDate target)
throws PropagationException {
return AbstractAnalyticalPropagator.this.propagateOrbit(target);
}
/** {@inheritDoc} */
public double getMass(final AbsoluteDate date) throws PropagationException {
return AbstractAnalyticalPropagator.this.getMass(date);
}
/** {@inheritDoc} */
public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame)
throws OrekitException {
return propagate(date).getPVCoordinates(frame);
}
/** {@inheritDoc} */
public void resetInitialState(final SpacecraftState state) throws PropagationException {
AbstractAnalyticalPropagator.this.resetInitialState(state);
}
/** {@inheritDoc} */
protected void resetIntermediateState(final SpacecraftState state, final boolean forward)
throws PropagationException {
AbstractAnalyticalPropagator.this.resetIntermediateState(state, forward);
}
/** {@inheritDoc} */
public SpacecraftState getInitialState() throws PropagationException {
return AbstractAnalyticalPropagator.this.getInitialState();
}
/** {@inheritDoc} */
public Frame getFrame() {
return AbstractAnalyticalPropagator.this.getFrame();
}
/** Replace the instance with a data transfer object for serialization.
* @return data transfer object that will be serialized
* @exception NotSerializableException if attitude provider or additional
* state provider is not serializable
*/
private Object writeReplace() throws NotSerializableException {
return new DataTransferObject(minDate, maxDate, AbstractAnalyticalPropagator.this);
}
}
/** Internal class used only for serialization. */
private static class DataTransferObject implements Serializable {
/** Serializable UID. */
private static final long serialVersionUID = 20151117L;
/** Min date. */
private final AbsoluteDate minDate;
/** Max date. */
private final AbsoluteDate maxDate;
/** Underlying propagator. */
private final AbstractAnalyticalPropagator propagator;
/** Simple constructor.
* @param minDate min date
* @param maxDate max date
* @param propagator underlying propagator
*/
DataTransferObject(final AbsoluteDate minDate, final AbsoluteDate maxDate,
final AbstractAnalyticalPropagator propagator) {
this.minDate = minDate;
this.maxDate = maxDate;
this.propagator = propagator;
}
/** Replace the deserialized data transfer object with an {@link BoundedPropagatorView}.
* @return replacement {@link BoundedPropagatorView}
*/
private Object readResolve() {
propagator.lastPropagationStart = minDate;
propagator.lastPropagationEnd = maxDate;
return propagator.getGeneratedEphemeris();
}
}
/** Internal class for local propagation. */
private class BasicStepInterpolator implements OrekitStepInterpolator {
/** Global previous date. */
private AbsoluteDate globalPreviousDate;
/** Global current date. */
private AbsoluteDate globalCurrentDate;
/** Soft previous date. */
private AbsoluteDate softPreviousDate;
/** Soft current date. */
private AbsoluteDate softCurrentDate;
/** Interpolated state. */
private SpacecraftState interpolatedState;
/** Forward propagation indicator. */
private boolean forward;
/** Build a new instance from a basic propagator.
*/
BasicStepInterpolator() {
globalPreviousDate = AbsoluteDate.PAST_INFINITY;
globalCurrentDate = AbsoluteDate.PAST_INFINITY;
softPreviousDate = AbsoluteDate.PAST_INFINITY;
softCurrentDate = AbsoluteDate.PAST_INFINITY;
}
/** Restrict step range to a limited part of the global step.
* <p>
* This method can be used to restrict a step and make it appear
* as if the original step was smaller. Calling this method
* <em>only</em> changes the value returned by {@link #getPreviousDate()},
* it does not change any other property
* </p>
* @param softPreviousDate start of the restricted step
*/
public void setSoftPreviousDate(final AbsoluteDate softPreviousDate) {
this.softPreviousDate = softPreviousDate;
}
/** Restrict step range to a limited part of the global step.
* <p>
* This method can be used to restrict a step and make it appear
* as if the original step was smaller. Calling this method
* <em>only</em> changes the value returned by {@link #getCurrentDate()},
* it does not change any other property
* </p>
* @param softCurrentDate end of the restricted step
*/
public void setSoftCurrentDate(final AbsoluteDate softCurrentDate) {
this.softCurrentDate = softCurrentDate;
}
/**
* Get the previous global grid point time.
* @return previous global grid point time
*/
public AbsoluteDate getGlobalPreviousDate() {
return globalPreviousDate;
}
/**
* Get the current global grid point time.
* @return current global grid point time
*/
public AbsoluteDate getGlobalCurrentDate() {
return globalCurrentDate;
}
/** {@inheritDoc} */
public AbsoluteDate getCurrentDate() {
return softCurrentDate;
}
/** {@inheritDoc} */
public AbsoluteDate getInterpolatedDate() {
return interpolatedState.getDate();
}
/** {@inheritDoc} */
public SpacecraftState getInterpolatedState() throws OrekitException {
return interpolatedState;
}
/** {@inheritDoc} */
public AbsoluteDate getPreviousDate() {
return softPreviousDate;
}
/** {@inheritDoc} */
public boolean isForward() {
return forward;
}
/** {@inheritDoc} */
public void setInterpolatedDate(final AbsoluteDate date) throws PropagationException {
// compute the basic spacecraft state
final SpacecraftState basicState = basicPropagate(date);
// add the additional states
interpolatedState = updateAdditionalStates(basicState);
}
/** Shift one step forward.
* Copy the current date into the previous date, hence preparing the
* interpolator for future calls to {@link #storeDate storeDate}
*/
public void shift() {
globalPreviousDate = globalCurrentDate;
softPreviousDate = globalPreviousDate;
softCurrentDate = globalCurrentDate;
}
/** Store the current step date.
* @param date current date
* @exception PropagationException if the state cannot be propagated at specified date
*/
public void storeDate(final AbsoluteDate date)
throws PropagationException {
globalCurrentDate = date;
softCurrentDate = globalCurrentDate;
forward = globalCurrentDate.compareTo(globalPreviousDate) >= 0;
setInterpolatedDate(globalCurrentDate);
}
}
}