AbstractPropagator.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import org.hipparchus.linear.RealMatrix;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.propagation.sampling.StepHandlerMultiplexer;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.DoubleArrayDictionary;
import org.orekit.utils.TimeSpanMap;
/** Common handling of {@link Propagator} methods for analytical propagators.
* <p>
* This abstract class allows to provide easily the full set of {@link Propagator}
* methods, including all propagation modes support and discrete events support for
* any simple propagation method.
* </p>
* @author Luc Maisonobe
*/
public abstract class AbstractPropagator implements Propagator {
/** Multiplexer for step handlers. */
private final StepHandlerMultiplexer multiplexer;
/** Start date. */
private AbsoluteDate startDate;
/** Attitude provider. */
private AttitudeProvider attitudeProvider;
/** Providers for additional states. */
private final List<AdditionalStateProvider> additionalStateProviders;
/** States managed by no generators. */
private final Map<String, TimeSpanMap<double[]>> unmanagedStates;
/** Initial state. */
private SpacecraftState initialState;
/** Harvester for State Transition Matrix and Jacobian matrix. */
private AbstractMatricesHarvester harvester;
/** Build a new instance.
*/
protected AbstractPropagator() {
multiplexer = new StepHandlerMultiplexer();
additionalStateProviders = new ArrayList<>();
unmanagedStates = new HashMap<>();
harvester = null;
}
/** Set a start date.
* @param startDate start date
*/
protected void setStartDate(final AbsoluteDate startDate) {
this.startDate = startDate;
}
/** Get the start date.
* @return start date
*/
protected AbsoluteDate getStartDate() {
return startDate;
}
/** {@inheritDoc} */
public AttitudeProvider getAttitudeProvider() {
return attitudeProvider;
}
/** {@inheritDoc} */
public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
this.attitudeProvider = attitudeProvider;
}
/** {@inheritDoc} */
public SpacecraftState getInitialState() {
return initialState;
}
/** {@inheritDoc} */
public Frame getFrame() {
return initialState.getFrame();
}
/** {@inheritDoc} */
public void resetInitialState(final SpacecraftState state) {
initialState = state;
setStartDate(state.getDate());
}
/** {@inheritDoc} */
public StepHandlerMultiplexer getMultiplexer() {
return multiplexer;
}
/** {@inheritDoc} */
@Override
public void addAdditionalStateProvider(final AdditionalStateProvider provider) {
// check if the name is already used
if (isAdditionalStateManaged(provider.getName())) {
// this additional state is already registered, complain
throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_NAME_ALREADY_IN_USE,
provider.getName());
}
// this is really a new name, add it
additionalStateProviders.add(provider);
}
/** {@inheritDoc} */
@Override
public List<AdditionalStateProvider> getAdditionalStateProviders() {
return Collections.unmodifiableList(additionalStateProviders);
}
/** {@inheritDoc} */
@Override
public MatricesHarvester setupMatricesComputation(final String stmName, final RealMatrix initialStm,
final DoubleArrayDictionary initialJacobianColumns) {
if (stmName == null) {
throw new OrekitException(OrekitMessages.NULL_ARGUMENT, "stmName");
}
harvester = createHarvester(stmName, initialStm, initialJacobianColumns);
return harvester;
}
/** Create the harvester suitable for propagator.
* @param stmName State Transition Matrix state name
* @param initialStm initial State Transition Matrix ∂Y/∂Y₀,
* if null (which is the most frequent case), assumed to be 6x6 identity
* @param initialJacobianColumns initial columns of the Jacobians matrix with respect to parameters,
* if null or if some selected parameters are missing from the dictionary, the corresponding
* initial column is assumed to be 0
* @return harvester to retrieve computed matrices during and after propagation
* @since 11.1
*/
protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
final DoubleArrayDictionary initialJacobianColumns) {
// FIXME: not implemented as of 11.1
throw new UnsupportedOperationException();
}
/** Get the harvester.
* @return harvester, or null if it was not created
* @since 11.1
*/
protected AbstractMatricesHarvester getHarvester() {
return harvester;
}
/** Update state by adding unmanaged states.
* @param original original state
* @return updated state, with unmanaged states included
* @see #updateAdditionalStates(SpacecraftState)
*/
protected SpacecraftState updateUnmanagedStates(final SpacecraftState original) {
// start with original state,
// which may already contain additional states, for example in interpolated ephemerides
SpacecraftState updated = original;
// update the states not managed by providers
for (final Map.Entry<String, TimeSpanMap<double[]>> entry : unmanagedStates.entrySet()) {
updated = updated.addAdditionalState(entry.getKey(),
entry.getValue().get(original.getDate()));
}
return updated;
}
/** Update state by adding all additional states.
* @param original original state
* @return updated state, with all additional states included
* (including {@link #updateUnmanagedStates(SpacecraftState) unmanaged} states)
* @see #addAdditionalStateProvider(AdditionalStateProvider)
* @see #updateUnmanagedStates(SpacecraftState)
*/
protected SpacecraftState updateAdditionalStates(final SpacecraftState original) {
// start with original state and unmanaged states
SpacecraftState updated = updateUnmanagedStates(original);
// set up queue for providers
final Queue<AdditionalStateProvider> pending = new LinkedList<>(getAdditionalStateProviders());
// update the additional states managed by providers, taking care of dependencies
int yieldCount = 0;
while (!pending.isEmpty()) {
final AdditionalStateProvider provider = pending.remove();
if (provider.yields(updated)) {
// this generator has to wait for another one,
// we put it again in the pending queue
pending.add(provider);
if (++yieldCount >= pending.size()) {
// all pending providers yielded!, they probably need data not yet initialized
// we let the propagation proceed, if these data are really needed right now
// an appropriate exception will be triggered when caller tries to access them
break;
}
} else {
// we can use this provider right now
updated = provider.update(updated);
yieldCount = 0;
}
}
return updated;
}
/**
* Initialize the additional state providers at the start of propagation.
* @param target date of propagation. Not equal to {@code initialState.getDate()}.
* @since 11.2
*/
protected void initializeAdditionalStates(final AbsoluteDate target) {
for (final AdditionalStateProvider provider : additionalStateProviders) {
provider.init(initialState, target);
}
}
/** {@inheritDoc} */
public boolean isAdditionalStateManaged(final String name) {
for (final AdditionalStateProvider provider : additionalStateProviders) {
if (provider.getName().equals(name)) {
return true;
}
}
return false;
}
/** {@inheritDoc} */
public String[] getManagedAdditionalStates() {
final String[] managed = new String[additionalStateProviders.size()];
for (int i = 0; i < managed.length; ++i) {
managed[i] = additionalStateProviders.get(i).getName();
}
return managed;
}
/** {@inheritDoc} */
public SpacecraftState propagate(final AbsoluteDate target) {
if (startDate == null) {
startDate = getInitialState().getDate();
}
return propagate(startDate, target);
}
/** Initialize propagation.
* @since 10.1
*/
protected void initializePropagation() {
unmanagedStates.clear();
if (initialState != null) {
// there is an initial state
// (null initial states occur for example in interpolated ephemerides)
// copy the additional states present in initialState but otherwise not managed
for (final DoubleArrayDictionary.Entry initial : initialState.getAdditionalStatesValues().getData()) {
if (!isAdditionalStateManaged(initial.getKey())) {
// this additional state is in the initial state, but is unknown to the propagator
// we store it in a way event handlers may change it
unmanagedStates.put(initial.getKey(), new TimeSpanMap<>(initial.getValue()));
}
}
}
}
/** Notify about a state change.
* @param state new state
*/
protected void stateChanged(final SpacecraftState state) {
final AbsoluteDate date = state.getDate();
final boolean forward = date.durationFrom(getStartDate()) >= 0.0;
for (final DoubleArrayDictionary.Entry changed : state.getAdditionalStatesValues().getData()) {
final TimeSpanMap<double[]> tsm = unmanagedStates.get(changed.getKey());
if (tsm != null) {
// this is an unmanaged state
if (forward) {
tsm.addValidAfter(changed.getValue(), date, false);
} else {
tsm.addValidBefore(changed.getValue(), date, false);
}
}
}
}
}