FieldShortPeriodicsInterpolatedCoefficient.java

/* Copyright 2002-2022 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.semianalytical.dsst.utilities;

import java.util.ArrayList;

import org.hipparchus.Field;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
import org.hipparchus.util.FastMath;
import org.orekit.time.FieldAbsoluteDate;

/** Interpolated short periodics coefficients.
 * <p>
 * Representation of a coefficient that need to be interpolated over time.
 * </p><p>
 * The short periodics coefficients can be interpolated for faster computation.
 * This class stores computed values of the coefficients through the method
 * {@link #addGridPoint} and gives an interpolated result through the method
 * {@link #value}.
 * </p>
 * @author Nicolas Bernard
 *
 */
public class FieldShortPeriodicsInterpolatedCoefficient <T extends CalculusFieldElement<T>> {

    /**Values of the already computed coefficients.*/
    private ArrayList<T[]> values;

    /**Grid points.*/
    private ArrayList<FieldAbsoluteDate<T>> abscissae;

    /**Number of points used in the interpolation.*/
    private int interpolationPoints;

    /**Index of the latest closest neighbor.*/
    private int latestClosestNeighbor;

    /**Simple constructor.
     * @param interpolationPoints number of points used in the interpolation
     */
    public FieldShortPeriodicsInterpolatedCoefficient(final int interpolationPoints) {
        this.interpolationPoints = interpolationPoints;
        this.abscissae = new ArrayList<FieldAbsoluteDate<T>>();
        this.values = new ArrayList<T[]>();
        this.latestClosestNeighbor = 0;
    }

    /**Compute the value of the coefficient.
     * @param date date at which the coefficient should be computed
     * @return value of the coefficient
     */
    public T[] value(final FieldAbsoluteDate<T> date) {

        //Field used by default
        final Field<T> field = date.getField();
        final T zero = field.getZero();

        //Get the closest points from the input date
        final int[] neighbors = getNeighborsIndices(date);

        //Creation and set up of the interpolator
        final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
        for (int i : neighbors) {
            interpolator.addSamplePoint(abscissae.get(i).durationFrom(date), values.get(i));
        }

        //interpolation
        return interpolator.value(zero);

    }

    /**Find the closest available points from the specified date.
     * @param date date of interest
     * @return indices corresponding to the closest points on the time scale
     */
    private int[] getNeighborsIndices(final FieldAbsoluteDate<T> date) {
        final int sizeofNeighborhood = FastMath.min(interpolationPoints, abscissae.size());
        final int[] neighborsIndices = new int[sizeofNeighborhood];

        //If the size of the complete sample is less than
        //the desired number of interpolation points,
        //then the entire sample is considered as the neighborhood
        if (interpolationPoints >= abscissae.size()) {
            for (int i = 0; i < sizeofNeighborhood; i++) {
                neighborsIndices[i] = i;
            }
        } else {
            // get indices around closest neighbor
            int inf = getClosestNeighbor(date);
            int sup = inf + 1;

            while (sup - inf < interpolationPoints) {
                if (inf == 0) { //This means that we have reached the earliest date
                    sup++;
                } else if (sup >= abscissae.size()) { //This means that we have reached the latest date
                    inf--;
                } else { //the choice is made between the two next neighbors
                    final T lowerNeighborDistance = FastMath.abs(abscissae.get(inf - 1).durationFrom(date));
                    final T upperNeighborDistance = FastMath.abs(abscissae.get(sup).durationFrom(date));

                    if (lowerNeighborDistance.getReal() <= upperNeighborDistance.getReal()) {
                        inf--;
                    } else {
                        sup++;
                    }
                }
            }

            for (int i = 0; i < interpolationPoints; ++i) {
                neighborsIndices[i] = inf + i;
            }

        }

        return neighborsIndices;
    }

    /**Find the closest point from a specific date amongst the available points.
     * @param date date of interest
     * @return index of the closest abscissa from the date of interest
     */
    private int getClosestNeighbor(final FieldAbsoluteDate<T> date) {
        //the starting point is the latest result of a call to this method.
        //Indeed, as this class is meant to be called during an integration process
        //with an input date evolving often continuously in time, there is a high
        //probability that the result will be the same as for last call of
        //this method.
        int closestNeighbor = latestClosestNeighbor;

        //case where the date is before the available points
        if (date.compareTo(abscissae.get(0)) <= 0) {
            closestNeighbor = 0;
        }
        //case where the date is after the available points
        else if (date.compareTo(abscissae.get(abscissae.size() - 1)) >= 0) {
            closestNeighbor = abscissae.size() - 1;
        }
        //general case: one is looking for the two consecutives entries that surround the input date
        //then one choose the closest one
        else {
            int lowerBorder = latestClosestNeighbor;
            int upperBorder = latestClosestNeighbor;

            final int searchDirection = date.compareTo(abscissae.get(latestClosestNeighbor));
            if (searchDirection > 0) {
                upperBorder++;
                while (date.compareTo(abscissae.get(upperBorder)) > 0) {
                    upperBorder++;
                    lowerBorder++;
                }
            }
            else {
                lowerBorder--;
                while (date.compareTo(abscissae.get(lowerBorder)) < 0) {
                    upperBorder--;
                    lowerBorder--;
                }
            }

            final T lowerDistance = FastMath.abs(date.durationFrom(abscissae.get(lowerBorder)));
            final T upperDistance = FastMath.abs(date.durationFrom(abscissae.get(upperBorder)));

            closestNeighbor = (lowerDistance.getReal() < upperDistance.getReal()) ? lowerBorder : upperBorder;
        }

        //The result is stored in order to speed up the next call to the function
        //Indeed, it is highly likely that the requested result will be the same
        this.latestClosestNeighbor = closestNeighbor;
        return closestNeighbor;
    }

    /** Clear the recorded values from the interpolation grid.
     */
    public void clearHistory() {
        abscissae.clear();
        values.clear();
    }

    /** Add a point to the interpolation grid.
     * @param date abscissa of the point
     * @param value value of the element
     */
    public void addGridPoint(final FieldAbsoluteDate<T> date, final T[] value) {
        //If the grid is empty, the value is directly added to both arrays
        if (abscissae.isEmpty()) {
            abscissae.add(date);
            values.add(value);
        }
        //If the grid already contains this point, only its value is changed
        else if (abscissae.contains(date)) {
            values.set(abscissae.indexOf(date), value);
        }
        //If the grid does not contain this point, the position of the point
        //in the grid is computed first
        else {
            final int closestNeighbor = getClosestNeighbor(date);
            final int index = (date.compareTo(abscissae.get(closestNeighbor)) < 0) ? closestNeighbor : closestNeighbor + 1;
            abscissae.add(index, date);
            values.add(index, value);
        }
    }

}