GridAxis.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 java.io.Serializable;
  19. import java.util.concurrent.atomic.AtomicInteger;

  20. import org.hipparchus.exception.LocalizedCoreFormats;
  21. import org.hipparchus.exception.MathIllegalArgumentException;
  22. import org.hipparchus.util.FastMath;
  23. import org.hipparchus.util.MathArrays;

  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.  * @since 1.4
  46.  */
  47. public class GridAxis implements Serializable {

  48.     /** Serializable UID. */
  49.     private static final long serialVersionUID = 20180926L;

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

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

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

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

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

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

  74.     }

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

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

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

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

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

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

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

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

  187.     }

  188. }