ConstantThrustManeuver.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.forces.maneuvers;

import org.hipparchus.analysis.differentiation.DerivativeStructure;
import org.hipparchus.geometry.euclidean.threed.FieldRotation;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitInternalError;
import org.orekit.errors.OrekitMessages;
import org.orekit.forces.AbstractForceModel;
import org.orekit.frames.Frame;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.DateDetector;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.handlers.EventHandler;
import org.orekit.propagation.numerical.TimeDerivativesEquations;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.Constants;
import org.orekit.utils.ParameterDriver;
import org.orekit.utils.ParameterObserver;

/** This class implements a simple maneuver with constant thrust.
 * <p>The maneuver is defined by a direction in satellite frame.
 * The current attitude of the spacecraft, defined by the current
 * spacecraft state, will be used to compute the thrust direction in
 * inertial frame. A typical case for tangential maneuvers is to use a
 * {@link org.orekit.attitudes.LofOffset LOF aligned} attitude provider
 * for state propagation and a velocity increment along the +X satellite axis.</p>
 * @author Fabien Maussion
 * @author V&eacute;ronique Pommier-Maurussane
 * @author Luc Maisonobe
 */
public class ConstantThrustManeuver extends AbstractForceModel {

    /** Parameter name for thrust. */
    private static final String THRUST = "thrust";

    /** Parameter name for flow rate. */
    private static final String FLOW_RATE = "flow rate";

    /** Thrust scaling factor.
     * <p>
     * We use a power of 2 to avoid numeric noise introduction
     * in the multiplications/divisions sequences.
     * </p>
     */
    private static final double THRUST_SCALE = FastMath.scalb(1.0, -5);

    /** Flow rate scaling factor.
     * <p>
     * We use a power of 2 to avoid numeric noise introduction
     * in the multiplications/divisions sequences.
     * </p>
     */
    private static final double FLOW_RATE_SCALE = FastMath.scalb(1.0, -12);

    /** Drivers for maneuver parameters. */
    private final ParameterDriver[] parametersDrivers;

    /** State of the engine. */
    private boolean firing;

    /** Start of the maneuver. */
    private final AbsoluteDate startDate;

    /** End of the maneuver. */
    private final AbsoluteDate endDate;

    /** Engine thrust. */
    private double thrust;

    /** Engine flow-rate. */
    private double flowRate;

    /** Direction of the acceleration in satellite frame. */
    private final Vector3D direction;

    /** Simple constructor for a constant direction and constant thrust.
     * @param date maneuver date
     * @param duration the duration of the thrust (s) (if negative,
     * the date is considered to be the stop date)
     * @param thrust the thrust force (N)
     * @param isp engine specific impulse (s)
     * @param direction the acceleration direction in satellite frame.
     */
    public ConstantThrustManeuver(final AbsoluteDate date, final double duration,
                                  final double thrust, final double isp,
                                  final Vector3D direction) {

        if (duration >= 0) {
            this.startDate = date;
            this.endDate   = date.shiftedBy(duration);
        } else {
            this.endDate   = date;
            this.startDate = endDate.shiftedBy(duration);
        }

        this.thrust    = thrust;
        this.flowRate  = -thrust / (Constants.G0_STANDARD_GRAVITY * isp);
        this.direction = direction.normalize();
        firing = false;

        this.parametersDrivers = new ParameterDriver[2];
        try {
            parametersDrivers[0] = new ParameterDriver(THRUST, thrust, THRUST_SCALE,
                                                       0.0, Double.POSITIVE_INFINITY);
            parametersDrivers[0].addObserver(new ParameterObserver() {
                /** {@inheritDoc} */
                @Override
                public void valueChanged(final double previousValue, final ParameterDriver driver) {
                    ConstantThrustManeuver.this.thrust = driver.getValue();
                }
            });
            parametersDrivers[1] = new ParameterDriver(FLOW_RATE, flowRate, FLOW_RATE_SCALE,
                                                       0.0, Double.POSITIVE_INFINITY);
            parametersDrivers[1].addObserver(new ParameterObserver() {
                /** {@inheritDoc} */
                @Override
                public void valueChanged(final double previousValue, final ParameterDriver driver) {
                    ConstantThrustManeuver.this.flowRate = driver.getValue();
                }
            });
        } catch (OrekitException oe) {
            // this should never occur as valueChanged above never throws an exception
            throw new OrekitInternalError(oe);
        };

    }

    @Override
    public void init(final SpacecraftState s0, final AbsoluteDate t) {
        // set the initial value of firing
        final AbsoluteDate sDate = s0.getDate();
        final boolean isForward = sDate.compareTo(t) < 0;
        final boolean isBetween =
                startDate.compareTo(sDate) < 0 && endDate.compareTo(sDate) > 0;
        final boolean isOnStart = startDate.compareTo(sDate) == 0;
        final boolean isOnEnd = endDate.compareTo(sDate) == 0;

        firing = isBetween || (isForward && isOnStart) || (!isForward && isOnEnd);
    }

    /** Get the thrust.
     * @return thrust force (N).
     */
    public double getThrust() {
        return thrust;
    }

    /** Get the specific impulse.
     * @return specific impulse (s).
     */
    public double getISP() {
        return -thrust / (Constants.G0_STANDARD_GRAVITY * flowRate);
    }

    /** Get the flow rate.
     * @return flow rate (negative, kg/s).
     */
    public double getFlowRate() {
        return flowRate;
    }

    /** {@inheritDoc} */
    public void addContribution(final SpacecraftState s, final TimeDerivativesEquations adder)
        throws OrekitException {

        if (firing) {

            // compute thrust acceleration in inertial frame
            adder.addAcceleration(new Vector3D(thrust / s.getMass(),
                                               s.getAttitude().getRotation().applyInverseTo(direction)),
                                  s.getFrame());

            // compute flow rate
            adder.addMassDerivative(flowRate);

        }

    }

    /** {@inheritDoc} */
    public FieldVector3D<DerivativeStructure> accelerationDerivatives(final AbsoluteDate date, final Frame frame,
                                                                      final FieldVector3D<DerivativeStructure> position,
                                                                      final FieldVector3D<DerivativeStructure> velocity,
                                                                      final FieldRotation<DerivativeStructure> rotation,
                                                                      final DerivativeStructure mass)
        throws OrekitException {
        if (firing) {
            return new FieldVector3D<DerivativeStructure>(mass.reciprocal().multiply(thrust),
                    rotation.applyInverseTo(direction));
        } else {
            // constant (and null) acceleration when not firing
            final int parameters = mass.getFreeParameters();
            final int order      = mass.getOrder();
            final DerivativeStructure zero = new DerivativeStructure(parameters, order, 0.0);
            return new FieldVector3D<DerivativeStructure>(zero, zero, zero);
        }
    }

    /** {@inheritDoc} */
    public FieldVector3D<DerivativeStructure> accelerationDerivatives(final SpacecraftState s, final String paramName)
        throws OrekitException {

        complainIfNotSupported(paramName);

        if (firing) {

            if (THRUST.equals(paramName)) {
                final DerivativeStructure thrustDS = new DerivativeStructure(1, 1, 0, thrust);
                return new FieldVector3D<DerivativeStructure>(thrustDS.divide(s.getMass()),
                                                              s.getAttitude().getRotation().applyInverseTo(direction));
            } else if (FLOW_RATE.equals(paramName)) {
                // acceleration does not depend on flow rate (only mass decrease does)
                final DerivativeStructure zero = new DerivativeStructure(1, 1, 0.0);
                return new FieldVector3D<DerivativeStructure>(zero, zero, zero);
            } else {
                throw new OrekitException(OrekitMessages.UNSUPPORTED_PARAMETER_NAME, paramName,
                                          THRUST + ", " + FLOW_RATE);
            }

        } else {
            // constant (and null) acceleration when not firing
            final DerivativeStructure zero = new DerivativeStructure(1, 1, 0.0);
            return new FieldVector3D<DerivativeStructure>(zero, zero, zero);
        }

    }

    /** {@inheritDoc} */
    public EventDetector[] getEventsDetectors() {
        return new EventDetector[] {
            new DateDetector(startDate).withHandler(new FiringStartHandler()),
            new DateDetector(endDate).withHandler(new FiringStopHandler())
        };
    }

    /** {@inheritDoc} */
    public ParameterDriver[] getParametersDrivers() {
        return parametersDrivers.clone();
    }

    /** Handler for start of maneuver. */
    private class FiringStartHandler implements EventHandler<DateDetector> {

        /** {@inheritDoc} */
        @Override
        public EventHandler.Action eventOccurred(final SpacecraftState s,
                                                 final DateDetector detector,
                                                 final boolean increasing) {
            if (detector.isForward()) {
                // we are in the forward direction,
                // starting now, the maneuver is ON as it has just started
                firing = true;
            } else {
                // we are in the backward direction,
                // starting now, the maneuver is OFF as it has not started yet
                firing = false;
            }
            return EventHandler.Action.RESET_DERIVATIVES;
        }

    }

    /** Handler for end of maneuver. */
    private class FiringStopHandler implements EventHandler<DateDetector> {

        /** {@inheritDoc} */
        public EventHandler.Action eventOccurred(final SpacecraftState s,
                                                 final DateDetector detector,
                                                 final boolean increasing) {
            if (detector.isForward()) {
                // we are in the forward direction,
                // starting now, the maneuver is OFF as it has just ended
                firing = false;
            } else {
                // we are in the backward direction,
                // starting now, the maneuver is ON as it has not ended yet
                firing = true;
            }
            return EventHandler.Action.RESET_DERIVATIVES;
        }

    }

}