/*
* Open-Source tuning tools
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.vgi.mafscaling;
import java.awt.Color;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.regex.Pattern;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import org.apache.commons.math3.analysis.BivariateFunction;
import org.apache.commons.math3.analysis.UnivariateFunction;
import org.apache.commons.math3.analysis.interpolation.AkimaSplineInterpolator;
import org.apache.commons.math3.analysis.interpolation.BicubicInterpolator;
import org.apache.commons.math3.analysis.interpolation.BivariateGridInterpolator;
import org.apache.commons.math3.analysis.interpolation.LinearInterpolator;
import org.apache.commons.math3.analysis.interpolation.LoessInterpolator;
import org.apache.commons.math3.analysis.interpolation.PiecewiseBicubicSplineInterpolator;
import org.apache.commons.math3.analysis.interpolation.SplineInterpolator;
import org.apache.commons.math3.analysis.interpolation.UnivariateInterpolator;
import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
import org.apache.commons.math3.exception.DimensionMismatchException;
import org.apache.commons.math3.exception.NoDataException;
import org.apache.commons.math3.exception.NonMonotonicSequenceException;
import org.apache.commons.math3.exception.NumberIsTooSmallException;
import org.apache.commons.math3.exception.OutOfRangeException;
import org.apache.commons.math3.util.MathArrays;
public final class Utils {
/**
* Shortened numeric validation regex
*/
public final static String fileFieldSplitter = "\\s*,\\s*";
public final static String fpRegex = "[\\x00-\\x20]*[+-]?(NaN|Infinity|((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*";
// ("[\\x00-\\x20]*[+-]?(((\\p{Digit}+)(\\.)?((\\p{Digit}+)?))|(\\.((\\p{Digit}+))))[\\x00-\\x20]*");
public final static String tmRegex = ".*\\d{1,2}:\\d{2}:\\d{2}\\.\\d{3}.*";
public final static String onOffRegex = "(?i)^\\s*(ON|OFF|OPENED|OPEN|CLOSED|CLOSE)\\s*$";
public final static Pattern offPattern = Pattern.compile("^\\s*(OFF|CLOSED|CLOSE)\\s*$", Pattern.CASE_INSENSITIVE);
public final static Pattern onPattern = Pattern.compile("^\\s*(ON|OPENED|OPEN)\\s*$", Pattern.CASE_INSENSITIVE);
public final static Color ZeroColor = new Color(255, 255, 255, 0);
private static long baseTime = 0;
//////////////////////////////////////////////////////////////////////////////
// COLORING METHODS
//////////////////////////////////////////////////////////////////////////////
/**
* Method returns unique gradient colors between RED(0xFF0000) and BLUE(0x0000FF)
* @param numColors, number of unique gradient colors between RED and BLUE
* @return array of unique gradient colors
*/
public static Color[] getColorArray(int numColors) {
return getColorArray(new Color(0xFF0000), new Color(0x0000FF), numColors);
}
/**
* Method returns unique gradient colors between specified colors
* @param begin, start color
* @param end, end color
* @param numColors, number of gradient unique colors between start and end colors
* @return array of unique gradient colors
*/
public static Color[] getColorArray(Color begin, Color end, int numColors) {
Color[] gradient;
if (numColors == 1)
gradient = new Color[3];
else
gradient = new Color[numColors];
float[] hsv1 = Color.RGBtoHSB(begin.getRed(), begin.getGreen(), begin.getBlue(), null);
float[] hsv2 = Color.RGBtoHSB(end.getRed(), end.getGreen(), end.getBlue(), null);
int a1 = begin.getAlpha();
float h1 = hsv1[0] ;
float s1 = hsv1[1];
float v1 = hsv1[2];
float da = end.getAlpha()- a1;
float dh = hsv2[0] - h1;
float ds = hsv2[1]- s1;
float dv = hsv2[2] - v1;
for (int i = 0; i < gradient.length; ++i) {
float rel = i / (float)(gradient.length - 1);
int rgb = Color.HSBtoRGB(h1 + dh * rel, s1 + ds * rel, v1 + dv * rel);
rgb +=(((int)(a1 + da * rel)) << 24);
gradient[i] = new Color(rgb);
}
if (numColors == 1)
gradient = new Color[] { gradient[1] };
return gradient;
}
//////////////////////////////////////////////////////////////////////////////
// TABLE METHODS
//////////////////////////////////////////////////////////////////////////////
/**
* Methods return table gradient colors matrix
* @param table, table to get matrix for
* @param startRow, row to begin coloring from
* @param startColumn, column to begin coloring from
*/
public static Color[][] generateTableColorMatrix(JTable table, int startRow, int startColumn) {
return generateTableColorMatrix(table, startRow, startColumn, table.getRowCount(), table.getColumnCount());
}
/**
* Methods return table gradient colors matrix
* @param table, table to get matrix for
* @param startRow, row to begin coloring from
* @param startColumn, column to begin coloring from
* @param endRow, row to finish coloring on
* @param endColumn, column to finish coloring on
* @return
*/
public static Color[][] generateTableColorMatrix(JTable table, int startRow, int startColumn, int endRow, int endColumn) {
Color[][] colorMatrix = null;
TreeSet<Double> uniqueValues = new TreeSet<Double>();
int i, j;
String value;
for (i = startRow; i < endRow; ++i) {
for (j = startColumn; j < endColumn; ++j) {
value = table.getValueAt(i, j).toString();
if (!value.isEmpty() && Pattern.matches(Utils.fpRegex, value))
uniqueValues.add(Double.valueOf(value));
}
}
if (uniqueValues.size() > 0) {
List<Color> colors = Arrays.asList(Utils.getColorArray(uniqueValues.size()));
Collections.reverse(colors);
colorMatrix = new Color[endRow][endColumn];
for (i = startRow; i < endRow; ++i) {
for (j = startColumn; j < endColumn; ++j) {
value = table.getValueAt(i, j).toString();
if (!value.isEmpty() && Pattern.matches(Utils.fpRegex, value))
colorMatrix[i][j] = colors.get(uniqueValues.headSet(Double.valueOf(value)).size());
else
colorMatrix[i][j] = ZeroColor;
}
}
}
return colorMatrix;
}
/**
* Method sets gradient background color for unique values assuming the first row and first column are headers.
* The table must have default renderer set as BgColorFormatRenderer
* @param table
*/
public static void colorTable(JTable table) {
int row = 1;
int col = 1;
if (table.getColumnCount() == 2 && table.getRowCount() >= 2 && !table.getValueAt(0, 0).toString().equals(""))
row = 0;
else if (table.getColumnCount() >= 2 && table.getRowCount() == 2 && !table.getValueAt(0, 0).toString().equals(""))
col = 0;
Color[][] colorMatrix = Utils.generateTableColorMatrix(table, row, col);
if (colorMatrix == null)
colorMatrix = new Color[table.getRowCount()][table.getColumnCount()];
if (col == 1) {
for (int i = 0; i < colorMatrix.length; ++i)
colorMatrix[i][0] = Color.LIGHT_GRAY;
}
if (row == 1) {
for (int i = 0; i < colorMatrix[0].length; ++i)
colorMatrix[0][i] = Color.LIGHT_GRAY;
}
BgColorFormatRenderer renderer = (BgColorFormatRenderer)table.getDefaultRenderer(Object.class);
if (renderer != null)
renderer.setColors(colorMatrix);
((DefaultTableModel)table.getModel()).fireTableDataChanged();
}
/**
* Method clears the table cells color
* @param table
*/
public static void clearTableColors(JTable table) {
TableCellRenderer renderer = table.getDefaultRenderer(Object.class);
if (renderer != null && renderer instanceof BgColorFormatRenderer)
((BgColorFormatRenderer)renderer).setColors(null);
((DefaultTableModel)table.getModel()).fireTableDataChanged();
}
/**
* Method sets gradient background color for first row and first column assuming those are headers.
* The table must have default renderer set as BgColorFormatRenderer
* @param table
*/
public static void colorTableHeaders(JTable table) {
Color[][] colorMatrix = new Color[table.getRowCount()][table.getColumnCount()];
if (colorMatrix != null) {
for (int i = 0; i < colorMatrix.length; ++i)
colorMatrix[i][0] = Color.LIGHT_GRAY;
for (int i = 0; i < colorMatrix[0].length; ++i)
colorMatrix[0][i] = Color.LIGHT_GRAY;
BgColorFormatRenderer renderer = (BgColorFormatRenderer)table.getDefaultRenderer(Object.class);
if (renderer != null)
renderer.setColors(colorMatrix);
}
((DefaultTableModel)table.getModel()).fireTableDataChanged();
}
/**
* Method sets columns width to the widest value
* @param table
* @param column
* @param margin
*/
public static void adjustColumnSizes(JTable table, int column, int margin) {
DefaultTableColumnModel colModel = (DefaultTableColumnModel) table.getColumnModel();
TableColumn col = colModel.getColumn(column);
int width;
TableCellRenderer renderer = col.getHeaderRenderer();
if (renderer == null)
renderer = table.getTableHeader().getDefaultRenderer();
Component comp = renderer.getTableCellRendererComponent(table, col.getHeaderValue(), false, false, 0, 0);
width = comp.getPreferredSize().width;
for (int r = 0; r < table.getRowCount(); ++r) {
renderer = table.getCellRenderer(r, column);
comp = renderer.getTableCellRendererComponent(table, table.getValueAt(r, column), false, false, r, column);
int currentWidth = comp.getPreferredSize().width;
width = Math.max(width, currentWidth);
}
width += 2 * margin;
col.setPreferredWidth(width);
}
/**
* Method clears the table cells and sets default value - an empty string
* @param table
*/
public static void clearTable(JTable table) {
if (table.isEditing() && table.getCellEditor() != null)
table.getCellEditor().stopCellEditing();
for (int i = 0; i < table.getColumnCount(); ++i) {
for (int j = 0; j < table.getRowCount(); ++j)
table.setValueAt("", j, i);
}
clearTableColors(table);
}
/**
* Method initializes the table cells with default value - an empty string, and sets cell width to specified width
* @param table
*/
public static void initializeTable(JTable table, int columnWidth) {
table.setDefaultRenderer(Object.class, new NumberFormatRenderer());
for (int i = 0; i < table.getColumnCount(); ++i) {
table.getColumnModel().getColumn(i).setMinWidth(columnWidth);
table.getColumnModel().getColumn(i).setMaxWidth(columnWidth);
for (int j = 0; j < table.getRowCount(); ++j)
table.setValueAt("", j, i);
}
}
/**
* Method checks if the column count is greater or equal to specified count, if not - it add a column
* @param count, number of columns to check for
* @param table
*/
public static void ensureColumnCount(int count, JTable table) {
if (count <= table.getColumnCount())
return;
int[] minwidth = new int[count];
int[] maxwidth = new int[count];
int[] prefwidth = new int[count];
int i, j;
for (i = 0; i < table.getColumnCount(); ++i) {
minwidth[i] = table.getColumnModel().getColumn(i).getMinWidth();
maxwidth[i] = table.getColumnModel().getColumn(i).getMaxWidth();
prefwidth[i] = table.getColumnModel().getColumn(i).getPreferredWidth();
}
DefaultTableModel model = (DefaultTableModel)table.getModel();
for (i = table.getColumnCount(); i < count; ++i) {
model.addColumn("");
minwidth[i] = minwidth[i - 1];
maxwidth[i] = maxwidth[i - 1];
prefwidth[i] = prefwidth[i - 1];
for (j = 0; j < table.getRowCount(); ++j)
table.setValueAt("", j, i);
}
for (i = 0; i < count; ++i) {
table.getColumnModel().getColumn(i).setMinWidth(minwidth[i]);
table.getColumnModel().getColumn(i).setMaxWidth(maxwidth[i]);
table.getColumnModel().getColumn(i).setPreferredWidth(prefwidth[i]);
}
}
/**
* Method checks if the row count is greater or equal to specified count, if not - it add a row
* @param count, number of rows to check for
* @param table
*/
public static void ensureRowCount(int count, JTable table) {
DefaultTableModel model = (DefaultTableModel)table.getModel();
int i, j;
for (i = table.getRowCount(); i < count; ++i) {
model.addRow(new Object[table.getColumnCount()]);
for (j = 0; j < table.getColumnCount(); ++j)
table.setValueAt("", i, j);
}
}
/**
* Method removes row from table
* @param index of the row to be removed
* @param table, table from which to remove the row
*/
public static void removeRow(int index, JTable table) {
if (index < table.getRowCount())
((DefaultTableModel)table.getModel()).removeRow(index);
}
/**
* Method removes column from table
* @param index of the column to be removed
* @param table, table from which to remove the column
*/
public static void removeColumn(int index, JTable table) {
if (index < table.getColumnCount()) {
int j;
Object[][] data = new Object[table.getRowCount()][table.getColumnCount() - 1];
int[] minwidth = new int[table.getColumnCount() - 1];
int[] maxwidth = new int[table.getColumnCount() - 1];
int[] prefwidth = new int[table.getColumnCount() - 1];
for (int i = 0; i < table.getRowCount(); ++i) {
for (j = 0; j < table.getColumnCount(); ++j) {
if (j == index)
continue;
if (j < index) {
data[i][j] = table.getValueAt(i, j);
if (i == 0) {
minwidth[j] = table.getColumnModel().getColumn(j).getMinWidth();
maxwidth[j] = table.getColumnModel().getColumn(j).getMaxWidth();
prefwidth[j] = table.getColumnModel().getColumn(j).getPreferredWidth();
}
}
else {
data[i][j - 1] = table.getValueAt(i, j);
if (i == 0) {
minwidth[j - 1] = table.getColumnModel().getColumn(j).getMinWidth();
maxwidth[j - 1] = table.getColumnModel().getColumn(j).getMaxWidth();
prefwidth[j - 1] = table.getColumnModel().getColumn(j).getPreferredWidth();
}
}
}
}
((DefaultTableModel)table.getModel()).setDataVector(data, new String[table.getColumnCount() - 1]);
for (j = 0; j < table.getColumnCount(); ++j) {
table.getColumnModel().getColumn(j).setMinWidth(minwidth[j]);
table.getColumnModel().getColumn(j).setMaxWidth(maxwidth[j]);
table.getColumnModel().getColumn(j).setPreferredWidth(prefwidth[j]);
}
}
}
/**
* Method checks that value in a table cell is numeric
* @param value to check
* @param row index
* @param col index
* @param tableName
* @return
*/
public static boolean validateDouble(String value, int row, int col, String tableName) {
if (!Pattern.matches(Utils.fpRegex, value)) {
JOptionPane.showMessageDialog(null, "Invalid data in table " + tableName + " - value is not a number in cell (" + row + ", " + col + "): " + value, "Invalid Data", JOptionPane.ERROR_MESSAGE);
return false;
}
return true;
}
/**
* Method check is the table is empty
* @param table
* @return
*/
public static boolean isTableEmpty(JTable table) {
boolean isEmpty = true;
for (int i = 0; i < table.getRowCount() && isEmpty; ++i) {
for (int j = 0; j < table.getColumnCount() && isEmpty; ++j) {
if (!table.getValueAt(i, j).toString().isEmpty())
isEmpty = false;
}
}
return isEmpty;
}
/**
* Method checks validates data pasted into the table and realigns the table according to pasted data
* @param table
* @return
*/
public static boolean validateTablePaste(JTable table) {
if (table == null)
return false;
// check if table is empty
if (Utils.isTableEmpty(table))
return true;
// quick check of pasted data
if (table.getColumnCount() < 2 || table.getRowCount() < 2 ||
!Pattern.matches(Utils.fpRegex, table.getValueAt(0, 1).toString()) ||
!Pattern.matches(Utils.fpRegex, table.getValueAt(1, 0).toString()) ||
!Pattern.matches(Utils.fpRegex, table.getValueAt(1, 1).toString())) {
JOptionPane.showMessageDialog(null, "Pasted data doesn't seem to be a valid table with row/column headers.\n\nPlease paste " + table.getName() + " table into first cell", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
// remove extra rows
for (int i = table.getRowCount() - 1; i >= 0 && table.getValueAt(i, 0).toString().equals(""); --i)
Utils.removeRow(i, table);
// remove extra columns
for (int i = table.getColumnCount() - 1; i >= 0 && table.getValueAt(0, i).toString().equals(""); --i)
Utils.removeColumn(i, table);
if (table.getColumnCount() > 2 && table.getRowCount() > 2 && !table.getValueAt(0, 0).toString().equals("")) {
JOptionPane.showMessageDialog(null, "Pasted data doesn't seem to be a valid table with row/column headers.\n\nPlease paste " + table.getName() + " table into first cell", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
return true;
}
/**
* Method checks if table header values are valid
* @param table
* @return
*/
public static boolean validateTableHeader(JTable table) {
if (!validateTablePaste(table))
return false;
// validate row headers cells are numeric if table has multiple columns
if (table.getColumnCount() > 1) {
for (int i = (table.getColumnCount() == 2 ? 0 : 1); i < table.getRowCount(); ++i) {
if (!Pattern.matches(Utils.fpRegex, table.getValueAt(i, 0).toString())) {
JOptionPane.showMessageDialog(null, "Invalid value in Y-Axis header, row " + i, "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
if (i > 1 && Double.valueOf(table.getValueAt(i, 0).toString()) <= Double.valueOf(table.getValueAt(i - 1, 0).toString())) {
JOptionPane.showMessageDialog(null, "Invalid value in Y-Axis header, row " + i + ": subsequent value must be greater than previous", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
}
}
// validate column headers cells are numeric if table has multiple rows
if (table.getRowCount() > 1) {
for (int i = (table.getRowCount() == 2 ? 0 : 1); i < table.getColumnCount(); ++i) {
if (!Pattern.matches(Utils.fpRegex, table.getValueAt(0, i).toString())) {
JOptionPane.showMessageDialog(null, "Invalid value in X-Axis header, column " + i, "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
if (i > 1 && Double.valueOf(table.getValueAt(0, i).toString()) <= Double.valueOf(table.getValueAt(0, i - 1).toString())) {
JOptionPane.showMessageDialog(null, "Invalid value in X-Axis header, column " + i + ": subsequent value must be greater than previous", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
}
}
return true;
}
/**
* Method checks is the table is valid
* @param table
* @return
*/
public static boolean validateTable(JTable table) {
if (!validateTableHeader(table))
return false;
// validate all data cells are numeric
String val;
for (int i = 1; i < table.getRowCount(); ++i) {
for (int j = 1; j < table.getColumnCount(); ++j) {
if (i == 0 && j == 0)
continue;
val = table.getValueAt(i, j).toString();
if (val.equals(""))
table.setValueAt("0", i, j);
else if (!Pattern.matches(Utils.fpRegex, val)) {
JOptionPane.showMessageDialog(null, "Invalid value at row " + (i + 1) + " column " + (j + 1), "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
}
}
Utils.colorTable(table);
return true;
}
/**
* Methods copies table to another table
* @param fromTable
* @param toTable
*/
public static void copyTable(JTable fromTable, JTable toTable) {
ensureColumnCount(fromTable.getColumnCount(), toTable);
ensureRowCount(fromTable.getRowCount(), toTable);
for (int i = 0; i < fromTable.getRowCount(); ++i) {
for (int j = 0; j < fromTable.getColumnCount(); ++j)
toTable.setValueAt(fromTable.getValueAt(i, j), i, j);
}
}
/**
* Method calculates plotting z[][] based on provided x and y arrays and related table
* @param x is a column table header
* @param y is a row table header
* @return double array
*/
public static double[][] doubleZArray(JTable dataTable, double[] x, double[] y) {
double[][] z = new double[y.length][x.length];
for (int i = 0; i < x.length; ++i) {
for (int j = 0; j < y.length; ++j) {
if (!dataTable.getValueAt(j + 1, i + 1).toString().isEmpty())
z[j][i] = Double.valueOf(dataTable.getValueAt(j + 1, i + 1).toString());
}
}
return z;
}
/**
* Method copies Color[][] array from input data table for z-axis based on provided x and y arrays
* @param x
* @param y
* @return
*/
public static Color[][] doubleColorArray(JTable dataTable, double[] x, double[] y) {
BgColorFormatRenderer renderer = (BgColorFormatRenderer)dataTable.getDefaultRenderer(Object.class);
Color[][] z = new Color[y.length][x.length];
for (int i = 0; i < x.length; ++i) {
for (int j = 0; j < y.length; ++j) {
if (!dataTable.getValueAt(j + 1, i + 1).toString().isEmpty())
z[j][i] = renderer.getColorAt(j + 1, i + 1);
else
z[j][i] = ZeroColor;
}
}
return z;
}
//////////////////////////////////////////////////////////////////////////////
// MATH METHODS
//////////////////////////////////////////////////////////////////////////////
/**
* Method returns index of value closest to the specified in the given array of values
* @param val, target value
* @param list, array of values
* @return closet value to the target value.
* Returns lower index if distance is the same between lower and higher indexed values.
*/
public static int closestValueIndex(double val, ArrayList<Double> list) {
int index = Collections.binarySearch(list, val);
if (index < 0) {
int idxPrev = Math.max(0, -index - 2);
int idxNext = Math.min(list.size() - 1, -index - 1);
return val - list.get(idxPrev) <= list.get(idxNext) - val ? idxPrev : idxNext;
}
return index;
}
/**
* Method returns index of value closest to the specified in the given array of values
* @param val, target value
* @param array, array of values
* @return closet value to the target value.
* Returns lower index if distance is the same between lower and higher indexed values.
*/
public static int closestValueIndex(double val, double[] arr) {
int index = Arrays.binarySearch(arr, val);
if (index < 0) {
int idxPrev = Math.max(0, -index - 2);
int idxNext = Math.min(arr.length - 1, -index - 1);
return val - arr[idxPrev] <= arr[idxNext] - val ? idxPrev : idxNext;
}
return index;
}
/**
* Method returns a linearly interpolated value
* @param x is X value you want to interpolate at
* @param x1 is X value for previous point
* @param x2 is X value for following point
* @param y1 is Y value for previous point
* @param y2 is Y value for following point
* @return Y interpolated value
*/
public static double linearInterpolation(double x, double x1, double x2, double y1, double y2) {
return (x1 == x2) ? 0.0 : (y1 + (x - x1) * (y2 - y1) / (x2 - x1));
}
/**
* Method returns a value interpolated from 2D grid
* (3D RomRaider tables) using bilinear interpolation
* @param x is X value you want to interpolate at
* @param y is Y value you want to interpolate at
* @param x0 is nearest x-axis value less than X
* @param x1 is nearest x-axis value greater than X
* @param y0 is nearest y-axis value less than Y
* @param y1 is nearest y-axis value greater than Y
* @param x0y0 is the table value at x0 and y0
* @param x0y1 is the table value at x0 and y1
* @param x1y0 is the table value at x1 and y0
* @param x1y1 is the table value at x1 and y1
* @return
*/
public static double bilinearInterpolation(double x, double y, double x0, double x1, double y0, double y1, double x0y0, double x0y1, double x1y0, double x1y1) {
double t1, t2;
if (y1 == y0) {
t1 = x0y0;
t2 = x1y0;
}
else {
t1 = (y - y0) * (x0y1 - x0y0) / (y1 - y0) + x0y0;
t2 = (y - y0) * (x1y1 - x1y0) / (y1 - y0) + x1y0;
}
if (x1 == x0)
return t1;
return (x - x0) * (t2 - t1) / (x1 - x0) + t1;
}
/**
* Interpolation method types
*
*/
enum InterpolatorType {
// 2D interpolators
AkimaCubicSpline,
CubicSpline,
Linear,
Regression,
// 3D interpolators
Bicubic,
BicubicSpline,
Bilinear
}
/**
* Method returns an interpolated/extrapolated value, based on
* @param x, array of x values
* @param y, array of y values
* @param xi, is x value you want to interpolate at
* @param type, interpolation method type
* @return interpolated value
* @throws Exception
*/
public static double interpolate(double[] x, double[] y, double xi, InterpolatorType type) throws Exception {
UnivariateInterpolator interpolator = null;
switch (type) {
case AkimaCubicSpline:
interpolator = new AkimaSplineInterpolator();
break;
case Linear:
interpolator = new LinearInterpolator();
break;
case Regression:
interpolator = new LoessInterpolator();
break;
case CubicSpline:
interpolator = new SplineInterpolator();
break;
default:
throw new Exception("Invalid interpolator type for this function");
}
UnivariateFunction function = interpolator.interpolate(x, y);
PolynomialFunction[] polynomials = ((PolynomialSplineFunction) function).getPolynomials();
if (xi > x[x.length - 1])
return polynomials[polynomials.length - 1].value(xi - x[x.length - 2]);
if (xi < x[0])
return polynomials[0].value(xi - x[0]);
return function.value(xi);
}
/**
*
* @param x, array of x values
* @param y, array of y values
* @param z, double array of values at [x,y] point
* @param xi, is x value you want to interpolate at
* @param yi, is y value you want to interpolate at
* @param type, interpolation method type
* @return interpolated value
* @throws Exception
*/
public static double interpolate3d(double[] x, double[] y, double[][]z, double xi, double yi, InterpolatorType type) throws Exception {
BivariateGridInterpolator interpolator = null;
switch (type) {
case Bilinear:
interpolator = new BilinearInterpolator();
break;
case Bicubic:
interpolator = new BicubicInterpolator();
break;
case BicubicSpline:
interpolator = new PiecewiseBicubicSplineInterpolator();
break;
default:
throw new Exception("Invalid interpolator type for this function");
}
return interpolator.interpolate(x, y, z).value(xi, yi);
}
/**
* Method rounds input number by a specific step
* @param input
* @param step
* @return
*/
public static double round(double input, double step) {
return ((Math.round(input / step)) * step);
}
/**
* Calculate mean of the array
* @param data
* @return
*/
public static double mean(List<Double> data) {
double val = 0;
for (int i = 0; i < data.size(); ++i)
val += data.get(i);
return val / data.size();
}
/**
* Calculate median of the array
* @param data
* @return
*/
public static double median(List<Double> data) {
double val = 0;
Collections.sort(data);
int mid = data.size() / 2;
if (data.size() % 2 == 1)
val = data.get(mid);
else
val = (data.get(mid - 1) + data.get(mid)) / 2;
return val;
}
/**
* Calculate mode of the array
* @param data
* @return
*/
public static double mode(List<Double> data) {
ArrayList<Double> modes = new ArrayList<Double>();
HashMap<Double, Integer> countMap = new HashMap<Double, Integer>();
int max = -1;
Integer count;
for (Double n : data) {
count = countMap.get(n);
if (count == null)
count = 0;
count += 1;
countMap.put(n, count);
if (count > max)
max = count;
}
for (Map.Entry<Double, Integer> entry : countMap.entrySet()) {
if (entry.getValue() == max)
modes.add(entry.getKey());
}
Collections.sort(modes);
return modes.get(modes.size() / 2);
}
/**
* Calculate range of the array
* @param data
* @return
*/
public static double range(List<Double> data) {
return Collections.max(data) - Collections.min(data);
}
/**
* Calculate variance of the array
* @param data
* @return
*/
public static double variance(List<Double> data) {
double mean = mean(data);
double sum = 0;
double diff = 0;
for (Double d : data) {
diff = d - mean;
sum += (diff * diff);
}
return sum / data.size();
}
/**
* Calculate standard deviation of the array
* @param data
* @return
*/
public static double standardDeviation(List<Double> data) {
return Math.sqrt(variance(data));
}
/**
* Method returns random number within min/max range, inclusive
* @param min
* @param max
* @return
*/
public static int getRandomInRange(int min, int max) {
return (min + (int)(Math.random() * ((max - min) + 1)));
}
/**
* Methods compare two double values with 5 decimal points precision
* @param x
* @param y
* @return true if x is equal to y with up to 5 decimal points, false otherwise
*/
public static boolean equals(double x, double y) {
return (Math.abs(x - y) < 0.00001);
}
/**
* Method calculates commanded afr from POL table
* @param rpm
* @param col
* @param minWotEnrichment
* @param polfTable
* @return commanded afr
*/
public static double calculateCommandedAfr(double rpm, double col, double minWotEnrichment, PrimaryOpenLoopFuelingTable polfTable) {
double rpmLow = 0;
double rpmHigh = 0;
double colLow = 0;
double colHigh = 0;
double timingLowLow = 0;
double timingLowHigh = 0;
double timingHighLow = 0;
double timingHighHigh = 0;
int rpmRowLow = 1;
int rpmRowHigh = 1;
int colLowIdx = 1;
int colHighIdx = 1;
int index = 1;
double value;
// Get values for RPM
for (index = 1; index < polfTable.getRowCount(); ++index) {
value = Double.valueOf(polfTable.getValueAt(index, 0).toString());
if (rpm < value) {
rpmHigh = value;
rpmRowHigh = index;
break;
}
else if (rpm == value) {
rpmLow = rpmHigh = value;
rpmRowLow = rpmRowHigh = index;
break;
}
else {
rpmLow = value;
rpmRowLow = index;
}
}
if (index == polfTable.getRowCount()) {
rpmHigh = 10000;
rpmRowHigh = index - 1;
}
// Get values for y
for (index = 1; index < polfTable.getColumnCount(); ++index) {
value = Double.valueOf(polfTable.getValueAt(0, index).toString());
if (col < value) {
colHigh = value;
colHighIdx = index;
break;
}
else if (rpm == value) {
colLow = colHigh = value;
colLowIdx = colHighIdx = index;
break;
}
else {
colLow = value;
colLowIdx = index;
}
}
if (index == polfTable.getColumnCount()) {
colHigh = 10000;
colHighIdx = index - 1;
}
timingLowLow = Double.valueOf(polfTable.getValueAt(rpmRowLow, colLowIdx).toString());
if (timingLowLow > minWotEnrichment)
timingLowLow = minWotEnrichment;
timingLowHigh = Double.valueOf(polfTable.getValueAt(rpmRowLow, colHighIdx).toString());
if (timingLowHigh > minWotEnrichment)
timingLowHigh = minWotEnrichment;
timingHighLow = Double.valueOf(polfTable.getValueAt(rpmRowHigh, colLowIdx).toString());
if (timingHighLow > minWotEnrichment)
timingHighLow = minWotEnrichment;
timingHighHigh = Double.valueOf(polfTable.getValueAt(rpmRowHigh, colHighIdx).toString());
if (timingHighHigh > minWotEnrichment)
timingHighHigh = minWotEnrichment;
return Utils.bilinearInterpolation(col, rpm, colLow, colHigh, rpmLow, rpmHigh, timingLowLow, timingHighLow, timingLowHigh, timingHighHigh);
}
/**
* Method resets baseTime variable used for conversion from absolute time in hh:mm:ss.sss string format to relative time in msec
* @param s
*/
public static void resetBaseTime(String s) {
if (s.indexOf(':') > 0 && s.indexOf('.') > 0) {
int tmZero = '0' * 11;
baseTime = ((s.charAt(0) * 10 + s.charAt(1) - tmZero) * 3600 + (s.charAt(3) * 10 + s.charAt(4) - tmZero) * 60 + s.charAt(6) * 10 + s.charAt(7) - tmZero) * 1000;
}
else
baseTime = 0;
}
/**
* Method used for parsing various time column formats and returns time in msec as long
* @param s time as string from log file
* @return time in msec as long
*/
public static long parseTime(String s) {
if (s.indexOf(':') > 0 && s.indexOf('.') > 0) {
int tmZero = '0' * 11;
int msZero = '0' * 111;
return ((s.charAt(0) * 10 + s.charAt(1) - tmZero) * 3600 +
(s.charAt(3) * 10 + s.charAt(4) - tmZero) * 60 +
s.charAt(6) * 10 + s.charAt(7) - tmZero) * 1000 +
(s.charAt(9) * 100 + s.charAt(10) * 10 + s.charAt(11) - msZero) - baseTime;
}
if (s.indexOf('.') > 0)
return (long)(Double.valueOf(s) * 1000);
return Long.valueOf(s);
}
/**
* Method used for parsing log values besides time
* This method should only be used where all columns of the log file(s) being loaded, eg LogStats, LogView
* @param s value as string from log file
* @return value as double
*/
public static double parseValue(String s) {
if (Utils.onPattern.matcher(s).find())
return 1.0;
if (Utils.offPattern.matcher(s).find())
return 0.0;
return Double.valueOf(s);
}
}
/**
* Class that implements Apache common-math BivariateGridInterpolator to perform
* bilinear interpolation
*/
class BilinearInterpolator implements BivariateGridInterpolator {
public class BilinearInterpolatingFunction implements BivariateFunction {
double[] xval = null;
double[] yval = null;
double[][] fval = null;
BilinearInterpolatingFunction(double[] xval, double[] yval, double[][] fval) {
this.xval = xval;
this.yval = yval;
this.fval = fval;
}
@Override
public double value(double x, double y) {
int xIdx = Utils.closestValueIndex(x, xval);
int yIdx = Utils.closestValueIndex(y, yval);
if ((xIdx == 0 && x < xval[xIdx]) || (xIdx == xval.length - 1 && x > xval[xval.length - 1]))
throw new OutOfRangeException(x, xval[0], xval[xval.length - 1]);
if ((yIdx == 0 && y < yval[yIdx]) || (yIdx == yval.length - 1 && y > yval[yval.length - 1]))
throw new OutOfRangeException(y, yval[0], yval[yval.length - 1]);
double x0, x1, y0, y1, x0y0, x0y1, x1y0, x1y1;
int x0Idx, x1Idx, y0Idx, y1Idx;
x0 = x1 = xval[xIdx];
y0 = y1 = yval[yIdx];
x0Idx = x1Idx = xIdx;
y0Idx = y1Idx = yIdx;
if (x > x0) {
x1Idx = xIdx + 1;
x1 = xval[x1Idx];
}
else if (x < x0) {
x0Idx = xIdx - 1;
x0 = xval[x0Idx];
}
if (y > y0) {
y1Idx = yIdx + 1;
y1 = yval[y1Idx];
}
else if (y < y0) {
y0Idx = yIdx - 1;
y0 = yval[y0Idx];
}
x0y0 = fval[x0Idx][y0Idx];
x1y0 = fval[x1Idx][y0Idx];
x0y1 = fval[x0Idx][y1Idx];
x1y1 = fval[x1Idx][y1Idx];
return Utils.bilinearInterpolation(x, y, x0, x1, y0, y1, x0y0, x0y1, x1y0, x1y1);
}
}
@Override
public BivariateFunction interpolate(double[] xval, double[] yval, double[][] fval) throws NoDataException, DimensionMismatchException, NonMonotonicSequenceException, NumberIsTooSmallException {
if (xval.length == 0 || yval.length == 0 || fval.length == 0)
throw new NoDataException();
if (xval.length != fval.length)
throw new DimensionMismatchException(xval.length, fval.length);
if (yval.length != fval[0].length)
throw new DimensionMismatchException(yval.length, fval[0].length);
MathArrays.checkOrder(xval);
MathArrays.checkOrder(yval);
return new BilinearInterpolatingFunction(xval, yval, fval);
}
}