package ags.util;
import java.util.Arrays;
/**
* A collection of matrix operations :)
*
* @author Alex Schultz (aka Rednaxela)
*/
public class MatrixOps {
/**
* Transpose a matrix (flip it 90 degrees)
*/
public static double[][] transpose(double [][] input) {
double[][] output = new double[input[0].length][input.length];
for (int x=0; x<input.length; x++) {
for (int y=0; y<input[0].length; y++) {
output[y][x] = input[x][y];
}
}
return output;
}
/**
* Multiply one matrix by another
*/
public static double[][] multiply(double[][] i1, double[][] i2) {
if (i1[0].length != i2.length)
throw new IndexOutOfBoundsException("Incompatible!");
double[][] output = new double[i1.length][i2[0].length];
for (int x=0; x<i1.length; x++) {
for (int y=0; y<i2[0].length; y++) {
for (int z=0; z<i2.length; z++) {
output[x][y] += i1[x][z] * i2[z][y];
}
}
}
return output;
}
/*
* Output an identity matrix (all zeros except a diagonal of ones) of a given size
*/
public static double[][] identity(int size) {
double[][] output = new double[size][size];
for (int x=0; x<size; x++) {
output[x][x] = 1;
}
return output;
}
/**
* Do a deep copy of a matrix
*/
public static double[][] copy(double[][] input) {
double[][] output = new double[input.length][];
for (int x=0; x<input.length; x++) {
output[x] = Arrays.copyOf(input[x], input[x].length);
}
return output;
}
public static void swapRows(double[][] input, int a, int b) {
final double[] temprow = input[a];
input[a] = input[b];
input[b] = temprow;
}
/**
* Change a matrix to reduced row eschelon form
* Roughly based on psudocode on wikipedia
*/
public static double[][] toReducedRowEschelon(double[][] input) {
double[][] output = copy(input);
int lead = 0;
int rowCount = input.length;
int columnCount = input[0].length;
for (int r=0; r<rowCount; r++) {
if (columnCount <= lead)
break;
int i = r;
while (output[i][lead] == 0) {
i++;
if (rowCount == i) {
i = r;
lead++;
if (columnCount == lead)
break;
}
}
swapRows(output, i, r);
double d = output[r][lead];
for (int x=0; x<columnCount; x++)
output[r][x] /= d;
for (i=0; i<rowCount; i++) {
if (i != r) {
double m = output[i][lead];
for (int x=0; x<columnCount; x++) {
output[i][x] -= output[r][x] * m;
}
}
}
lead++;
}
return output;
}
/**
* Use the matrix inversion algorithm
*/
public static double[][] inverse(double[][] input) {
if (input.length != input[0].length)
throw new IndexOutOfBoundsException("The matrix is not square!");
final int size = input.length;
double[][] output = identity(size);
input = copy(input);
for (int x=0; x<size; x++) {
// Make row start with 1
double f = input[x][x];
if (f == 0)
continue;
//throw new java.lang.IllegalArgumentException("Matrix is not invertable!");
for (int y=0; y<size; y++) {
input[x][y] /= f;
output[x][y] /= f;
}
input[x][x] = 1;
// Zero other rows
for (int y=0; y<size; y++) {
if (y != x) {
double v = input[y][x];
for (int z=0; z<size; z++) {
input[y][z] -= input[x][z]*v;
output[y][z] -= output[x][z]*v;
}
input[y][x] = 0;
}
}
}
return output;
}
/**
* Print out two double arrays side-by-side.
*/
public static void print2Matrix(double[][] input, double[][] input2) {
for (int x=0; x<input.length; x++) {
double[] line = input[x];
for (double value : line) {
System.out.print(value+"\t");
}
line = input2[x];
for (double value : line) {
System.out.print(value+"\t");
}
System.out.println();
}
}
/**
* Print out double array, seperated with tabs and newlines
*/
public static void printMatrix(double[][] input) {
for (double[] line : input) {
for (double value : line) {
System.out.print(value+"\t");
}
System.out.println();
}
}
/**
* Some testing things. Just me checking that some functions work as expected
*/
public static void main(String[] args) {
System.out.println("Matrix test app:");
double[][] testmatrix = new double[][]{{1, 2}, {2, 1}};
System.out.println("Test matrix:");
printMatrix(testmatrix);
System.out.println();
System.out.println("Transpose matrix:");
printMatrix(transpose(testmatrix));
System.out.println();
double[][] t1 = new double[][]{{1, 0, 2}, {-1, 3, 1}};
double[][] t2 = new double[][]{{3, 1}, {2, 1}, {1, 0}};
System.out.println("Multiply matrix:");
printMatrix(multiply(t1, t2));
System.out.println();
System.out.println("Inverse matrix:");
printMatrix(inverse(testmatrix));
System.out.println();
System.out.println("Inverse times the test matrix:");
printMatrix(multiply(inverse(testmatrix), testmatrix));
System.out.println();
System.out.println("Special case of the Moore-Penrose pseudoinverse:");
printMatrix(partialPsudoInverse(testmatrix));
System.out.println();
System.out.println("Reduced Row Eschelon form test:");
testmatrix = new double[][]{{1,2,6},{5,4,3}};
printMatrix(testmatrix);
System.out.println("to...");
printMatrix(toReducedRowEschelon(testmatrix));
System.out.println();
}
/**
* Partial Psudo Inverse function
*/
public static double[][] partialPsudoInverse(double[][] input) {
double[][] transposed = transpose(input);
return multiply(inverse(multiply(transposed, input)),transposed);
}
/**
* The algorithm to fit a plane/hyperplane of best fit for a data set
*/
public static double[] bestFitHyperplane(double[][] variables, double[] results) {
if (variables.length != results.length)
throw new IllegalArgumentException("Length of variables and results do not match!");
//if (variables.length < variables[0].length+1)
// throw new IllegalArgumentException("Not enough data!");
final int samples = variables.length;
final double [][] M = new double[samples][];
for (int x=0; x<samples; x++) {
double[] line = new double[variables[x].length+1];
line[0] = 1;
for (int y=0; y<variables[x].length; y++) {
line[y+1] = variables[x][y];
}
M[x] = line;
}
final double [][] Y = new double[samples][1];
for (int x=0; x<samples; x++) {
Y[x][0] = results[x];
}
double[][] Z = multiply(partialPsudoInverse(M), Y);
double[] output = new double[Z.length];
for (int x=0; x<Z.length; x++) {
output[x] = Z[x][0];
}
return output;
}
}
This is by far the most fun I've had testing code since playing RoboCode. Click & drag to stretch things, right-click & drag to move the points. Works beautifully!! -- Simonton
package rednaxela;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* @author Eric Simonton
*/
public class Tester extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame("Stretch");
frame.setContentPane(new Tester());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.pack();
frame.setVisible(true);
}
private Point[] corners = new Point[8];
private Point[] r1 = new Point[4];
private Point[] r2 = new Point[4];
private Point[] points = new Point[2];
public Tester() {
setPreferredSize(new Dimension(800, 400));
r1[0] = corners[0] = new Point(20, 20);
r1[1] = corners[1] = new Point(20, 380);
r1[2] = corners[2] = new Point(380, 380);
r1[3] = corners[3] = new Point(380, 20);
points[0] = new Point(200, 200);
r2[0] = corners[4] = new Point(420, 20);
r2[1] = corners[5] = new Point(420, 380);
r2[2] = corners[6] = new Point(780, 380);
r2[3] = corners[7] = new Point(780, 20);
points[1] = new Point(600, 200);
Mouser mouser = new Mouser();
addMouseListener(mouser);
addMouseMotionListener(mouser);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
Graphics2D g2d = (Graphics2D) g;
Polygon rect = new Polygon();
for (int i = 8; --i >= 0; ) {
rect.addPoint(corners[i].x, corners[i].y);
if (i % 4 == 0) {
g2d.draw(rect);
rect.reset();
}
}
g.setColor(Color.RED);
g.fillOval(points[0].x - 1, points[0].y - 1, 4, 4);
g.fillOval(points[1].x - 1, points[1].y - 1, 4, 4);
}
private int getClosestCorner(Point p) {
return getClosestPoint(p, corners);
}
private int getClosestPoint(Point p) {
return getClosestPoint(p, points);
}
private int getClosestPoint(Point p, Point[] points) {
int closestPoint = 0;
double closestDistance = points[0].distance(p);
for (int i = points.length; --i > 0;) {
double dist = points[i].distance(p);
if (dist < closestDistance) {
closestDistance = dist;
closestPoint = i;
}
}
return closestPoint;
}
private void setCorner(int i, Point p) {
corners[i].setLocation(p);
recalculate(i > 3);
}
private void setPoint(int i, Point p) {
points[i].setLocation(p);
recalculate(i < 1);
}
private void recalculate(boolean firstIsConstant) {
if (firstIsConstant) {
points[1].setLocation(Utils.solveElasticProblem(r1, r2, points[0]));
} else {
points[0].setLocation(Utils.solveElasticProblem(r2, r1, points[1]));
}
repaint();
}
private class Mouser extends MouseAdapter implements MouseMotionListener {
private boolean corner;
private int clickPoint;
public void mousePressed(MouseEvent e) {
if (e.getButton() == 1) {
corner = true;
clickPoint = getClosestCorner(e.getPoint());
} else {
corner = false;
clickPoint = getClosestPoint(e.getPoint());
}
mouseDragged(e);
}
public void mouseDragged(MouseEvent e) {
if (corner) {
setCorner(clickPoint, e.getPoint());
} else {
setPoint(clickPoint, e.getPoint());
}
}
public void mouseMoved(MouseEvent e) {
// don't care
}
}
}