Skip to content

Commit

Permalink
Added bilinear interpolation for 2D grids.
Browse files Browse the repository at this point in the history
Github: fixes #48.
  • Loading branch information
maisonobe committed Sep 26, 2018
1 parent c9163fb commit fe27bcb
Show file tree
Hide file tree
Showing 7 changed files with 763 additions and 0 deletions.
4 changes: 4 additions & 0 deletions hipparchus-core/src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ If the output is not quite correct, check for invisible trailing spaces!
</properties>
<body>
<release version="1.4" date="TBC" description="TBC">
<action dev="luc" type="add" issue="issues/48" >
Added bilinear interpolation for 2D grids.
Github: fixes #48.
</action>
<action dev="luc" type="add" issue="issues/47" >
Added field version of sinCos.
Github: fixes #47.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Licensed to the Hipparchus project under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The Hipparchus project 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.hipparchus.analysis.interpolation;

import java.io.Serializable;

import org.hipparchus.analysis.BivariateFunction;
import org.hipparchus.exception.MathIllegalArgumentException;

/**
* Interpolate grid data using bi-linear interpolation.
* <p>
* This interpolator is thread-safe.
* </p>
* @since 1.4
*/
public class BilinearInterpolatingFunction implements BivariateFunction, Serializable {

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

/** Grid along the x axis. */
private final GridAxis xGrid;

/** Grid size along the x axis. */
private final int xSize;

/** Grid along the y axis. */
private final GridAxis yGrid;

/** Grid size along the y axis. */
private final int ySize;

/** Values of the interpolation points on all the grid knots (in a flatten array). */
private final double[] fVal;

/** Simple constructor.
* @param xVal All the x-coordinates of the interpolation points, sorted
* in increasing order.
* @param yVal All the y-coordinates of the interpolation points, sorted
* in increasing order.
* @param fVal The values of the interpolation points on all the grid knots:
* {@code fVal[i][j] = f(xVal[i], yVal[j])}.
* @exception MathIllegalArgumentException if grid size is smaller than 2
* or if the grid is not sorted in strict increasing order
*/
public BilinearInterpolatingFunction(final double[] xVal, final double[] yVal,
final double[][] fVal)
throws MathIllegalArgumentException {
this.xGrid = new GridAxis(xVal, 2);
this.xSize = xVal.length;
this.yGrid = new GridAxis(yVal, 2);
this.ySize = yVal.length;
this.fVal = new double[xSize * ySize];
int k = 0;
for (int i = 0; i < xSize; ++i) {
final double[] fi = fVal[i];
for (int j = 0; j < ySize; ++j) {
this.fVal[k++] = fi[j];
}
}
}

/** Get the lowest grid x coordinate.
* @return lowest grid x coordinate
*/
public double getXInf() {
return xGrid.node(0);
}

/** Get the highest grid x coordinate.
* @return highest grid x coordinate
*/
public double getXSup() {
return xGrid.node(xGrid.size() - 1);
}

/** Get the lowest grid y coordinate.
* @return lowest grid y coordinate
*/
public double getYInf() {
return yGrid.node(0);
}

/** Get the highest grid y coordinate.
* @return highest grid y coordinate
*/
public double getYSup() {
return yGrid.node(yGrid.size() - 1);
}

/** {@inheritDoc} */
@Override
public double value(final double x, final double y) {

// get the interpolation nodes
final int i = xGrid.interpolationIndex(x);
final int j = yGrid.interpolationIndex(y);
final double x0 = xGrid.node(i);
final double x1 = xGrid.node(i + 1);
final double y0 = yGrid.node(j);
final double y1 = yGrid.node(j + 1);

// get the function values at interpolation nodes
final int k0 = i * ySize + j;
final int k1 = k0 + ySize;
final double z00 = fVal[k0];
final double z01 = fVal[k0 + 1];
final double z10 = fVal[k1];
final double z11 = fVal[k1 + 1];

// interpolate
final double dx0 = x - x0;
final double dx1 = x1 - x;
final double dx10 = x1 - x0;
final double dy0 = y - y0;
final double dy1 = y1 - y;
final double dy10 = y1 - y0;
return (dx0 * (dy0 * z11 + dy1 * z10) + dx1 * (dy0 * z01 + dy1 * z00)) /
(dx10 * dy10);

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Licensed to the Hipparchus project under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The Hipparchus project 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.hipparchus.analysis.interpolation;

import org.hipparchus.exception.MathIllegalArgumentException;

/**
* Interpolate grid data using bi-linear interpolation.
* @since 1.4
*/
public class BilinearInterpolator implements BivariateGridInterpolator {

/** {@inheritDoc} */
@Override
public BilinearInterpolatingFunction interpolate(final double[] xval, final double[] yval,
final double[][] fval)
throws MathIllegalArgumentException {
return new BilinearInterpolatingFunction(xval, yval, fval);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Licensed to the Hipparchus project under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The Hipparchus project 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.hipparchus.analysis.interpolation;

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;

import org.hipparchus.exception.LocalizedCoreFormats;
import org.hipparchus.exception.MathIllegalArgumentException;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.MathArrays;

/**
* Helper for finding interpolation nodes along one axis of grid data.
* <p>
* This class is intended to be used for interpolating inside grids.
* It works on any sorted data without duplication and size at least
* {@code n} where {@code n} is the number of points required for
* interpolation (i.e. 2 for linear interpolation, 3 for quadratic...)
* <p>
* </p>
* The method uses linear interpolation to select the nodes indices.
* It should be O(1) for sufficiently regular data, therefore much faster
* than bisection. It also features caching, which improves speed when
* interpolating several points in raw in the close locations, i.e. when
* successive calls have a high probability to return the same interpolation
* nodes. This occurs for example when scanning with small steps a loose
* grid. The method also works on non-regular grids, but may be slower in
* this case.
* </p>
* <p>
* This class is thread-safe.
* </p>
* @since 1.4
*/
public class GridAxis implements Serializable {

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

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

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

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

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

// safety checks
if (grid.length < n) {
throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DIMENSION,
grid.length, n);
}
MathArrays.checkOrder(grid);

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

}

/** Get the number of points of the grid.
* @return number of points of the grid
*/
public int size() {
return grid.length;
}

/** Get the number of points required for interpolation.
* @return number of points required for interpolation
*/
public int getN() {
return n;
}

/** Get the interpolation node at specified index.
* @param index node index
* @return coordinate of the node at specified index
*/
public double node(final int index) {
return grid[index];
}

/** Get the index of the first interpolation node for some coordinate along the grid.
* <p>
* The index return is the one for the lowest interpolation node suitabme for
* {@code t}. This means that if {@code i} is returned the nodes to use for
* interpolation at coordinate {@code t} are at indices {@code i}, {@code i+1},
* ..., {@code i+n-1}, where {@code n} is the number of points required for
* interpolation passed at construction.
* </p>
* <p>
* The index is selected in order to have the array as balanced as possible
* around {@code t}. This means that if {@code n} if even and {@code t} is
* sufficiently far from the grid endpoints, the nodes at indices {@code i} to
* {code i+n/2} will be smaller than {@code t} and nodes at indices {@code i+n/2+1}
* to {@code i+n-1} will be larger than {@code t}. If {@code t} is close to endpoints
* but still within the grid, the returned node will be unbalanced. If {@code t}
* is outside of the grid, all nodes will be on the same side with respect to
* {@code t}. It is <em>not</em> an error to call this method with {@code t} outside
* of the grid, it simply implies that the interpolation will become an extrapolation
* and accuracy will decrease as {@code t} goes farther from the grid points. This
* is intended so interpolation does not fail near the end of the grid.
* </p>
* @param t coordinate of the point to interpolate
* @return index {@code i} such {@link #node(int) node(i)}, {@link #node(int) node(i+1)},
* ... {@link #node(int) node(i+n-1)} can be used for interpolating a value at
* coordinate {@code t}
* @since 1.4
*/
public int interpolationIndex(final double t) {

final int middleOffset = (n - 1) / 2;

// first try to simply reuse the last used index,
// for faster return in a common case
final int cached = cache.get();
final int middle = cached + middleOffset;
if (grid[middle] <= t && t < grid[middle + 1]) {
return cached;
}

// we need to find a new index
int iInf = middleOffset;
double aInf = grid[iInf];
int iSup = grid.length - (n - 1) + middleOffset;
double aSup = grid[iSup];
while (iSup - iInf > 1) {
final int iInterp = (int) ((iInf * (aSup - t) + iSup * (t - aInf)) / (aSup - aInf));
final int iMed = FastMath.max(iInf + 1, FastMath.min(iInterp, iSup - 1));
if (t < grid[iMed]) {
// keeps looking in the lower part of the grid
iSup = iMed;
aSup = grid[iSup];
} else {
// keeps looking in the upper part of the grid
iInf = iMed;
aInf = grid[iInf];
}
}

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

}

}
Loading

0 comments on commit fe27bcb

Please sign in to comment.