FieldGridAxis.java

  1. /*
  2.  * Licensed to the Hipparchus project under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The Hipparchus project licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.hipparchus.analysis.interpolation;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.exception.LocalizedCoreFormats;
  20. import org.hipparchus.exception.MathIllegalArgumentException;
  21. import org.hipparchus.util.FastMath;
  22. import org.hipparchus.util.MathArrays;

  23. import java.util.concurrent.atomic.AtomicInteger;

  24. /**
  25.  * Helper for finding interpolation nodes along one axis of grid data.
  26.  * <p>
  27.  * This class is intended to be used for interpolating inside grids.
  28.  * It works on any sorted data without duplication and size at least
  29.  * {@code n} where {@code n} is the number of points required for
  30.  * interpolation (i.e. 2 for linear interpolation, 3 for quadratic...)
  31.  * </p>
  32.  * <p>
  33.  * The method uses linear interpolation to select the nodes indices.
  34.  * It should be O(1) for sufficiently regular data, therefore much faster
  35.  * than bisection. It also features caching, which improves speed when
  36.  * interpolating several points in row in the close locations, i.e. when
  37.  * successive calls have a high probability to return the same interpolation
  38.  * nodes. This occurs for example when scanning with small steps a loose
  39.  * grid. The method also works on non-regular grids, but may be slower in
  40.  * this case.
  41.  * </p>
  42.  * <p>
  43.  * This class is thread-safe.
  44.  * </p>
  45.  * @param <T> Type of the field elements.
  46.  * @since 4.0
  47.  */
  48. public class FieldGridAxis<T extends CalculusFieldElement<T>> {

  49.     /** All the coordinates of the interpolation points, sorted in increasing order. */
  50.     private final T[] grid;

  51.     /** Number of points required for interpolation. */
  52.     private final int n;

  53.     /** Cached value of last x index. */
  54.     private final AtomicInteger cache;

  55.     /** Simple constructor.
  56.      * @param grid coordinates of the interpolation points, sorted in increasing order
  57.      * @param n number of points required for interpolation, i.e. 2 for linear, 3
  58.      * for quadratic...
  59.      * @exception MathIllegalArgumentException if grid size is smaller than {@code n}
  60.      * or if the grid is not sorted in strict increasing order
  61.      */
  62.     public FieldGridAxis(final T[] grid, final int n)
  63.         throws MathIllegalArgumentException {

  64.         // safety checks
  65.         if (grid.length < n) {
  66.             throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DIMENSION,
  67.                                                    grid.length, n);
  68.         }
  69.         MathArrays.checkOrder(grid);

  70.         this.grid  = grid.clone();
  71.         this.n     = n;
  72.         this.cache = new AtomicInteger(0);

  73.     }

  74.     /** Get the number of points of the grid.
  75.      * @return number of points of the grid
  76.      */
  77.     public int size() {
  78.         return grid.length;
  79.     }

  80.     /** Get the number of points required for interpolation.
  81.      * @return number of points required for interpolation
  82.      */
  83.     public int getN() {
  84.         return n;
  85.     }

  86.     /** Get the interpolation node at specified index.
  87.      * @param index node index
  88.      * @return coordinate of the node at specified index
  89.      */
  90.     public T node(final int index) {
  91.         return grid[index];
  92.     }

  93.     /** Get the index of the first interpolation node for some coordinate along the grid.
  94.      * <p>
  95.      * The index return is the one for the lowest interpolation node suitable for
  96.      * {@code t}. This means that if {@code i} is returned the nodes to use for
  97.      * interpolation at coordinate {@code t} are at indices {@code i}, {@code i+1},
  98.      * ..., {@code i+n-1}, where {@code n} is the number of points required for
  99.      * interpolation passed at construction.
  100.      * </p>
  101.      * <p>
  102.      * The index is selected in order to have the subset of nodes from {@code i} to
  103.      * {@code i+n-1} as balanced as possible around {@code t}:
  104.      * </p>
  105.      * <ul>
  106.      *   <li>
  107.      *     if {@code t} is inside the grid and sufficiently far from the endpoints
  108.      *     <ul>
  109.      *       <li>
  110.      *         if {@code n} is even, the returned nodes will be perfectly balanced:
  111.      *         there will be {@code n/2} nodes smaller than {@code t} and {@code n/2}
  112.      *         nodes larger than {@code t}
  113.      *       </li>
  114.      *       <li>
  115.      *         if {@code n} is odd, the returned nodes will be slightly unbalanced by
  116.      *         one point: there will be {@code (n+1)/2} nodes smaller than {@code t}
  117.      *         and {@code (n-1)/2} nodes larger than {@code t}
  118.      *       </li>
  119.      *     </ul>
  120.      *   </li>
  121.      *   <li>
  122.      *     if {@code t} is inside the grid and close to endpoints, the returned nodes
  123.      *     will be unbalanced: there will be less nodes on the endpoints side and
  124.      *     more nodes on the interior side
  125.      *   </li>
  126.      *   <li>
  127.      *     if {@code t} is outside of the grid, the returned nodes will completely
  128.      *     off balance: all nodes will be on the same side with respect to {@code t}
  129.      *   </li>
  130.      * </ul>
  131.      * <p>
  132.      * It is <em>not</em> an error to call this method with {@code t} outside of the grid,
  133.      * it simply implies that the interpolation will become an extrapolation and accuracy
  134.      * will decrease as {@code t} goes farther from the grid points. This is intended so
  135.      * interpolation does not fail near the end of the grid.
  136.      * </p>
  137.      * @param t coordinate of the point to interpolate
  138.      * @return index {@code i} such {@link #node(int) node(i)}, {@link #node(int) node(i+1)},
  139.      * ... {@link #node(int) node(i+n-1)} can be used for interpolating a value at
  140.      * coordinate {@code t}
  141.      * @since 1.4
  142.      */
  143.     public int interpolationIndex(final T t) {

  144.         final int middleOffset = (n - 1) / 2;
  145.         int iInf = middleOffset;
  146.         int iSup = grid.length - (n - 1) + middleOffset;

  147.         // first try to simply reuse the cached index,
  148.         // for faster return in a common case
  149.         final int    cached = cache.get();
  150.         final int    middle = cached + middleOffset;
  151.         final T aMid0  = grid[middle];
  152.         final T aMid1  = grid[middle + 1];
  153.         if (t.getReal() < aMid0.getReal()) {
  154.             if (middle == iInf) {
  155.                 // we are in the unbalanced low area
  156.                 return cached;
  157.             }
  158.         } else if (t.getReal() < aMid1.getReal()) {
  159.             // we are in the balanced middle area
  160.             return cached;
  161.         } else {
  162.             if (middle == iSup - 1) {
  163.                 // we are in the unbalanced high area
  164.                 return cached;
  165.             }
  166.         }

  167.         // we need to find a new index
  168.         T aInf = grid[iInf];
  169.         T aSup = grid[iSup];
  170.         while (iSup - iInf > 1) {
  171.             final int iInterp = (int) ((iInf * (aSup.getReal() - t.getReal()) + iSup * (t.getReal() - aInf.getReal())) / (aSup.getReal() - aInf.getReal()));
  172.             final int iMed    = FastMath.max(iInf + 1, FastMath.min(iInterp, iSup - 1));
  173.             if (t.getReal() < grid[iMed].getReal()) {
  174.                 // keeps looking in the lower part of the grid
  175.                 iSup = iMed;
  176.                 aSup = grid[iSup];
  177.             } else {
  178.                 // keeps looking in the upper part of the grid
  179.                 iInf = iMed;
  180.                 aInf = grid[iInf];
  181.             }
  182.         }

  183.        final int newCached = iInf - middleOffset;
  184.        cache.compareAndSet(cached, newCached);
  185.        return newCached;

  186.     }

  187. }