Commit 48cc7739 authored by Luc Maisonobe's avatar Luc Maisonobe
Browse files

Started work on maneuver duration estimation/optimization.

parent da389790
......@@ -331,7 +331,8 @@ public enum OrekitMessages implements Localizable {
ATTEMPT_TO_GENERATE_MALFORMED_FILE("attempt to generate file {0} with a formatting error"),
FIND_ROOT("{0} failed to find root between {1} (g={2,number,0.0##############E0}) and {3} (g={4,number,0.0##############E0})\nLast iteration at {5} (g={6,number,0.0##############E0})"),
BACKWARD_PROPAGATION_NOT_ALLOWED("backward propagation not allowed here"),
NO_STATION_ECCENTRICITY_FOR_EPOCH("no station eccentricity values for the given epoch {0}, validity interval is between {1} and {2}");
NO_STATION_ECCENTRICITY_FOR_EPOCH("no station eccentricity values for the given epoch {0}, validity interval is between {1} and {2}"),
INCONSISTENT_SELECTION("inconsistent parameters selection between pairs {0}/{1} and {2}{3}");
// CHECKSTYLE: resume JavadocVariable check
......
/* Copyright 2002-2021 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;
/** Generator for one column of a Jacobian matrix for special case of maneuver duration.
* <p>
* Typical use cases for this are estimation of maneuver duration during
* either orbit determination or maneuver optimization.
* </p>
* @author Luc Maisonobe
* @since 11.1
* @see MedianDateJacobianColumnGenerator
* @see TriggerDateJacobianColumnGenerator
*/
public class DurationJacobianColumnGenerator implements AdditionalStateProvider {
/** Name of the parameter corresponding to the start date. */
private final String startName;
/** Name of the parameter corresponding to the stop date. */
private final String stopName;
/** Name of the parameter corresponding to the column. */
private final String columnName;
/** Simple constructor.
* @param startName name of the parameter corresponding to the start date
* @param stopName name of the parameter corresponding to the stop date
* @param columnName name of the parameter corresponding to the column
*/
public DurationJacobianColumnGenerator(final String startName, final String stopName, final String columnName) {
this.startName = startName;
this.stopName = stopName;
this.columnName = columnName;
}
/** {@inheritDoc} */
@Override
public String getName() {
return columnName;
}
/** {@inheritDoc}
* <p>
* The column state can be computed only if the start and stop dates columns are available.
* </p>
*/
@Override
public boolean yield(final SpacecraftState state) {
return !(state.hasAdditionalState(startName) && state.hasAdditionalState(stopName));
}
/** {@inheritDoc} */
@Override
public double[] getAdditionalState(final SpacecraftState state) {
// compute partial derivatives with respect to start and stop dates
final double[] dYdT0 = state.getAdditionalState(startName);
final double[] dYdT1 = state.getAdditionalState(stopName);
// combine derivatives to get partials with respect to duration
final double[] dYdTm = new double[dYdT0.length];
for (int i = 0; i < dYdTm.length; ++i) {
dYdTm[i] = 0.5 * (dYdT1[i] - dYdT0[i]);
}
return dYdTm;
}
}
/* Copyright 2002-2021 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;
/** Generator for one column of a Jacobian matrix for special case of maneuver median date.
* <p>
* Typical use cases for this are estimation of maneuver median date during
* either orbit determination or maneuver optimization.
* </p>
* @author Luc Maisonobe
* @since 11.1
* @see DurationJacobianColumnGenerator
* @see TriggerDateJacobianColumnGenerator
*/
public class MedianDateJacobianColumnGenerator implements AdditionalStateProvider {
/** Name of the parameter corresponding to the start date. */
private final String startName;
/** Name of the parameter corresponding to the stop date. */
private final String stopName;
/** Name of the parameter corresponding to the column. */
private final String columnName;
/** Simple constructor.
* @param startName name of the parameter corresponding to the start date
* @param stopName name of the parameter corresponding to the stop date
* @param columnName name of the parameter corresponding to the column
*/
public MedianDateJacobianColumnGenerator(final String startName, final String stopName, final String columnName) {
this.startName = startName;
this.stopName = stopName;
this.columnName = columnName;
}
/** {@inheritDoc} */
@Override
public String getName() {
return columnName;
}
/** {@inheritDoc}
* <p>
* The column state can be computed only if the start and stop dates columns are available.
* </p>
*/
@Override
public boolean yield(final SpacecraftState state) {
return !(state.hasAdditionalState(startName) && state.hasAdditionalState(stopName));
}
/** {@inheritDoc} */
@Override
public double[] getAdditionalState(final SpacecraftState state) {
// compute partial derivatives with respect to start and stop dates
final double[] dYdT0 = state.getAdditionalState(startName);
final double[] dYdT1 = state.getAdditionalState(stopName);
// combine derivatives to get partials with respect to median date
final double[] dYdTm = new double[dYdT0.length];
for (int i = 0; i < dYdTm.length; ++i) {
dYdTm[i] = dYdT0[i] + dYdT1[i];
}
return dYdTm;
}
}
......@@ -71,6 +71,8 @@ import org.orekit.forces.maneuvers.trigger.ManeuverTriggersResetter;
* </p>
* @author Luc Maisonobe
* @since 11.1
* @see MedianDateJacobianColumnGenerator
* @see DurationJacobianColumnGenerator
*/
public class TriggerDateJacobianColumnGenerator
implements AdditionalStateProvider, ManeuverTriggersResetter {
......
......@@ -17,13 +17,24 @@
package org.orekit.propagation.events;
import org.hipparchus.util.FastMath;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.handlers.EventHandler;
import org.orekit.propagation.events.handlers.StopOnDecreasing;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.DateDriver;
import org.orekit.utils.ParameterDriver;
import org.orekit.utils.ParameterObserver;
/** Detector for date intervals that may be offset thanks to parameter drivers.
* <p>
* {@link #getStartDriver() start}/{@link #getStopDriver() stop} drivers and
* {@link #getMedianDriver() median}/{@link #getDurationDriver() duration} drivers
* work in pair. Both drivers in one par can be selected and their changes will
* be propagated to the other pair, but attempting to select drivers in both
* pairs at the same time will trigger an exception.
* </p>
* @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
* @author Luc Maisonobe
* @since 11.1
......@@ -36,12 +47,24 @@ public class ParameterDrivenDateIntervalDetector extends AbstractDetector<Parame
/** Default suffix for stop driver. */
public static final String STOP_SUFFIX = "_STOP";
/** Default suffix for median driver. */
public static final String MEDIAN_SUFFIX = "_MEDIAN";
/** Default suffix for duration driver. */
public static final String DURATION_SUFFIX = "_DURATION";
/** Reference interval start driver. */
private DateDriver start;
/** Reference interval stop driver. */
private DateDriver stop;
/** Median date driver. */
private DateDriver median;
/** Duration driver. */
private ParameterDriver duration;
/** Build a new instance.
* @param prefix prefix to use for parameter drivers names
* @param refStart reference interval start date
......@@ -52,7 +75,9 @@ public class ParameterDrivenDateIntervalDetector extends AbstractDetector<Parame
this(0.5 * refStop.durationFrom(refStart), 1.0e-10, DEFAULT_MAX_ITER,
new StopOnDecreasing<ParameterDrivenDateIntervalDetector>(),
new DateDriver(refStart, prefix + START_SUFFIX, true),
new DateDriver(refStop, prefix + STOP_SUFFIX, false));
new DateDriver(refStop, prefix + STOP_SUFFIX, false),
new DateDriver(refStart.shiftedBy(0.5 * refStop.durationFrom(refStart)), prefix + MEDIAN_SUFFIX, true),
new ParameterDriver(prefix + DURATION_SUFFIX, refStop.durationFrom(refStart), 1.0, 0.0, Double.POSITIVE_INFINITY));
}
/** Private constructor with full parameters.
......@@ -67,13 +92,25 @@ public class ParameterDrivenDateIntervalDetector extends AbstractDetector<Parame
* @param handler event handler to call at event occurrences
* @param start reference interval start driver
* @param stop reference interval stop driver
* @param median median date driver
* @param duration duration driver
*/
private ParameterDrivenDateIntervalDetector(final double maxCheck, final double threshold, final int maxIter,
final EventHandler<? super ParameterDrivenDateIntervalDetector> handler,
final DateDriver start, final DateDriver stop) {
final DateDriver start, final DateDriver stop,
final DateDriver median, final ParameterDriver duration) {
super(maxCheck, threshold, maxIter, handler);
this.start = start;
this.stop = stop;
this.start = start;
this.stop = stop;
this.median = median;
this.duration = duration;
// set up delegation between drivers
start.addObserver(new StartObserver());
stop.addObserver(new StopObserver());
median.addObserver(new MedianObserver());
duration.addObserver(new DurationObserver());
}
/** {@inheritDoc} */
......@@ -81,10 +118,15 @@ public class ParameterDrivenDateIntervalDetector extends AbstractDetector<Parame
protected ParameterDrivenDateIntervalDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter,
final EventHandler<? super ParameterDrivenDateIntervalDetector> newHandler) {
return new ParameterDrivenDateIntervalDetector(newMaxCheck, newThreshold, newMaxIter, newHandler,
start, stop);
start, stop, median, duration);
}
/** Get the driver for start date.
* <p>
* Note that the start date is automatically adjusted if either
* {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
* are {@link ParameterDriver#isSelected() selected} and changed.
* </p>
* @return driver for start date
*/
public DateDriver getStartDriver() {
......@@ -92,12 +134,41 @@ public class ParameterDrivenDateIntervalDetector extends AbstractDetector<Parame
}
/** Get the driver for stop date.
* <p>
* Note that the stop date is automatically adjusted if either
* {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
* are {@link ParameterDriver#isSelected() selected} changed.
* </p>
* @return driver for stop date
*/
public DateDriver getStopDriver() {
return stop;
}
/** Get the driver for median date.
* <p>
* Note that the median date is automatically adjusted if either
* {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
* are {@link ParameterDriver#isSelected() selected} changed.
* </p>
* @return driver for median date
*/
public DateDriver getMedianDriver() {
return median;
}
/** Get the driver for duration.
* <p>
* Note that the duration is automatically adjusted if either
* {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
* are {@link ParameterDriver#isSelected() selected} changed.
* </p>
* @return driver for duration
*/
public ParameterDriver getDurationDriver() {
return duration;
}
/** Compute the value of the switching function.
* <p>
* The function is positive for dates within the interval defined
......@@ -115,4 +186,73 @@ public class ParameterDrivenDateIntervalDetector extends AbstractDetector<Parame
stop.getDate().durationFrom(s.getDate()));
}
/** Base observer. */
private abstract class Observer implements ParameterObserver {
/** {@inheritDoc} */
@Override
public void valueChanged(final double previousValue, final ParameterDriver driver) {
if (driver.isSelected()) {
setDelta(driver.getValue() - previousValue);
}
}
/** {@inheritDoc} */
@Override
public void selectionChanged(final boolean previousSelection, final ParameterDriver driver) {
if ((start.isSelected() || stop.isSelected()) &&
(median.isSelected() || duration.isSelected())) {
throw new OrekitException(OrekitMessages.INCONSISTENT_SELECTION,
start.getName(), stop.getName(),
median.getName(), duration.getName());
}
}
/** Change a value.
* @param delta change of value
*/
protected abstract void setDelta(double delta);
}
/** Observer for start date. */
private class StartObserver extends Observer {
/** {@inheritDoc} */
@Override
protected void setDelta(final double delta) {
median.setValue(median.getValue() + 0.5 * delta);
duration.setValue(duration.getValue() - delta);
}
}
/** Observer for stop date. */
private class StopObserver extends Observer {
/** {@inheritDoc} */
@Override
protected void setDelta(final double delta) {
median.setValue(median.getValue() + 0.5 * delta);
duration.setValue(duration.getValue() + delta);
}
}
/** Observer for median date. */
private class MedianObserver extends Observer {
/** {@inheritDoc} */
@Override
protected void setDelta(final double delta) {
start.setValue(start.getValue() + delta);
stop.setValue(stop.getValue() + delta);
}
}
/** Observer for duration. */
private class DurationObserver extends Observer {
/** {@inheritDoc} */
@Override
protected void setDelta(final double delta) {
start.setValue(start.getValue() - 0.5 * delta);
stop.setValue(stop.getValue() + 0.5 * delta);
}
}
}
......@@ -49,7 +49,9 @@ import org.orekit.orbits.OrbitType;
import org.orekit.orbits.PositionAngle;
import org.orekit.propagation.AbstractMatricesHarvester;
import org.orekit.propagation.AdditionalStateProvider;
import org.orekit.propagation.DurationJacobianColumnGenerator;
import org.orekit.propagation.MatricesHarvester;
import org.orekit.propagation.MedianDateJacobianColumnGenerator;
import org.orekit.propagation.PropagationType;
import org.orekit.propagation.Propagator;
import org.orekit.propagation.SpacecraftState;
......@@ -61,7 +63,6 @@ import org.orekit.propagation.integration.AdditionalDerivativesProvider;
import org.orekit.propagation.integration.StateMapper;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.AbsolutePVCoordinates;
import org.orekit.utils.DateDriver;
import org.orekit.utils.DoubleArrayDictionary;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.ParameterDriver;
......@@ -501,16 +502,26 @@ public class NumericalPropagator extends AbstractIntegratedPropagator {
filter(d -> d instanceof ParameterDrivenDateIntervalDetector).
map (d -> (ParameterDrivenDateIntervalDetector) d).
forEach(d -> {
final TriggerDateJacobianColumnGenerator start =
manageDateDriver(stmName, maneuver, amt, d.getStartDriver(), true, d.getThreshold());
if (start != null) {
if (d.getStartDriver().isSelected() || d.getMedianDriver().isSelected() || d.getDurationDriver().isSelected()) {
final TriggerDateJacobianColumnGenerator start =
manageTriggerDate(stmName, maneuver, amt, d.getStartDriver().getName(), true, d.getThreshold());
names.add(start.getName());
}
final TriggerDateJacobianColumnGenerator stop =
manageDateDriver(stmName, maneuver, amt, d.getStopDriver(), false, d.getThreshold());
if (stop != null) {
if (d.getStopDriver().isSelected() || d.getMedianDriver().isSelected() || d.getDurationDriver().isSelected()) {
final TriggerDateJacobianColumnGenerator stop =
manageTriggerDate(stmName, maneuver, amt, d.getStopDriver().getName(), false, d.getThreshold());
names.add(stop.getName());
}
if (d.getMedianDriver().isSelected()) {
final MedianDateJacobianColumnGenerator median =
manageMedianDate(d.getStartDriver().getName(), d.getStopDriver().getName(), d.getMedianDriver().getName());
names.add(median.getName());
}
if (d.getDurationDriver().isSelected()) {
final DurationJacobianColumnGenerator duration =
manageManeuverDuration(d.getStartDriver().getName(), d.getStopDriver().getName(), d.getDurationDriver().getName());
names.add(duration.getName());
}
});
}
......@@ -521,53 +532,124 @@ public class NumericalPropagator extends AbstractIntegratedPropagator {
}
/** Manage a maneuver date driver.
/** Manage a maneuver trigger date.
* @param stmName name of the State Transition Matrix state
* @param maneuver maneuver force model
* @param amt trigger to which the driver is bound
* @param driver date driver
* @param driverName name of the date driver
* @param start if true, the driver is a maneuver start
* @param threshold event detector threshold
* @return generator for the date driver (null if driver not selected)
* @return generator for the date driver
* @since 11.1
*/
private TriggerDateJacobianColumnGenerator manageDateDriver(final String stmName,
final Maneuver maneuver,
final AbstractManeuverTriggers amt,
final DateDriver driver,
final boolean start,
final double threshold) {
TriggerDateJacobianColumnGenerator triggerGenerator = null;
private TriggerDateJacobianColumnGenerator manageTriggerDate(final String stmName,
final Maneuver maneuver,
final AbstractManeuverTriggers amt,
final String driverName,
final boolean start,
final double threshold) {
if (driver.isSelected()) {
TriggerDateJacobianColumnGenerator triggerGenerator = null;
// check if we already have set up the provider
for (final AdditionalStateProvider provider : getAdditionalStateProviders()) {
if (provider instanceof TriggerDateJacobianColumnGenerator &&
provider.getName().equals(driver.getName())) {
// the Jacobian column generator has already been set up in a previous propagation
triggerGenerator = (TriggerDateJacobianColumnGenerator) provider;
break;
}
// check if we already have set up the provider
for (final AdditionalStateProvider provider : getAdditionalStateProviders()) {
if (provider instanceof TriggerDateJacobianColumnGenerator &&
provider.getName().equals(driverName)) {
// the Jacobian column generator has already been set up in a previous propagation
triggerGenerator = (TriggerDateJacobianColumnGenerator) provider;
break;
}
}
if (triggerGenerator == null) {
// this is the first time we need the Jacobian column generator, create it
triggerGenerator = new TriggerDateJacobianColumnGenerator(stmName, driver.getName(),
start, maneuver, threshold);
amt.addResetter(triggerGenerator);
addAdditionalStateProvider(triggerGenerator);
if (triggerGenerator == null) {
// this is the first time we need the Jacobian column generator, create it
triggerGenerator = new TriggerDateJacobianColumnGenerator(stmName, driverName,
start, maneuver, threshold);
amt.addResetter(triggerGenerator);
addAdditionalStateProvider(triggerGenerator);
}
if (!getInitialIntegrationState().hasAdditionalState(driverName)) {
// add the initial Jacobian column if it is not already there
// (perhaps due to a previous propagation)
setInitialColumn(driverName, getHarvester().getInitialJacobianColumn(driverName));
}
return triggerGenerator;
}
/** Manage a maneuver median date.
* @param startName name of the start driver
* @param stopName name of the stop driver
* @param medianName name of the median driver
* @return generator for the median driver
* @since 11.1
*/
private MedianDateJacobianColumnGenerator manageMedianDate(final String startName, final String stopName, final String medianName) {
MedianDateJacobianColumnGenerator medianGenerator = null;
// check if we already have set up the provider
for (final AdditionalStateProvider provider : getAdditionalStateProviders()) {
if (provider instanceof MedianDateJacobianColumnGenerator &&
provider.getName().equals(medianName)) {
// the Jacobian column generator has already been set up in a previous propagation
medianGenerator = (MedianDateJacobianColumnGenerator) provider;
break;
}
}
if (!getInitialIntegrationState().hasAdditionalState(driver.getName())) {
// add the initial Jacobian column if it is not already there
// (perhaps due to a previous propagation)
setInitialColumn(driver.getName(), getHarvester().getInitialJacobianColumn(driver.getName()));
if (medianGenerator == null) {
// this is the first time we need the Jacobian column generator, create it
medianGenerator = new MedianDateJacobianColumnGenerator(startName, stopName, medianName);
addAdditionalStateProvider(medianGenerator);
}
if (!getInitialIntegrationState().hasAdditionalState(medianName)) {
// add the initial Jacobian column if it is not already there