import graph.Axis; import graph.DataSet; import graph.G2Dint; import java.applet.Applet; import java.awt.Button; import java.awt.Color; import java.awt.GridLayout; import java.awt.Label; import java.awt.Panel; import java.awt.TextField; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import Jama.Matrix; import Jama.SingularValueDecomposition; /** * VisualSVD.java, a Java applet to allow a visual understanding of what * the singular value decomposition says about the linear transformation * described by a 2x2 matrix A. * *

* @author Andrew Trusty */ public class VisualSVD extends Applet { /** The object used to graph the results **/ private G2Dint graph; /** The x axis of the graph object **/ private Axis xaxis; /** The yaxis of the graph object **/ private Axis yaxis; /** The object used to graph the results **/ private G2Dint tgraph; /** The x axis of the graph object **/ private Axis txaxis; /** The yaxis of the graph object **/ private Axis tyaxis; /** The data set for the actual results **/ private DataSet unitData; /** The data set for the theoretical bound results **/ private DataSet transformData; /** The unit circle points storage array **/ private double[] unitPoints; /** The number of points to plot to form the graph **/ private final static int PLOT_POINTS = 1000; /** The increment number for plotting points to form the graph **/ private final static double increment = (Math.PI * 2) / PLOT_POINTS; /** The textfields used to display the matrix A **/ private TextField A_a; private TextField A_b; private TextField A_c; private TextField A_d; /** The textfields used to display the matrix V **/ private TextField V_a; private TextField V_b; private TextField V_c; private TextField V_d; /** The textfields used to display the matrix D **/ private TextField D_a; private TextField D_b; private TextField D_c; private TextField D_d; /** The textfields used to display the matrix U **/ private TextField U_a; private TextField U_b; private TextField U_c; private TextField U_d; /** The user inputed matrix A **/ private Matrix A; /** The diagnolized matrix found by the SVD of A **/ private Matrix D; /** The matrix V found by the SVD of A **/ private Matrix V; /** Initialize all the components **/ public void init() { setSize(525, 280); setBackground(Color.WHITE); setLayout(null); A = new Matrix(new double[][]{{1, 0}, {0, 1}}); V = new Matrix(new double[][]{{1, 0}, {0, 1}}); Label uc = new Label("Unit Circle"); uc.setBounds(110, 3, 80, 16); add(uc); Label lt = new Label("Linear Transformation by A"); lt.setBounds(325, 3, 150, 16); add(lt); Panel topPanel = new Panel(null); Panel input = initAinput(); input.setBounds(5, 0, 350, 83); topPanel.add(input); Button calc = new Button("Calculate SVD & Graph"); calc.setBounds(363, 47, 150, 25); topPanel.add(calc); topPanel.setBounds(5, 198, 515, 85); add(topPanel); // calculate the unit circle points double theta = 0; unitPoints = new double[PLOT_POINTS * 2]; for (int i = 0; i < (PLOT_POINTS * 2); i+=2) { unitPoints[i] = Math.cos(theta); unitPoints[i + 1] = Math.sin(theta); theta += increment; } unitData = new DataSet(); try { unitData.append(unitPoints, PLOT_POINTS); } catch (Exception e) { System.err.println("Unable to append unitPoints data"); } Panel graphPanel = new Panel(new GridLayout(1, 2)); // setup graphs graph = new G2Dint(); xaxis = graph.createXAxis(); xaxis.setTitleText("X"); yaxis = graph.createYAxis(); yaxis.setTitleText("Y"); xaxis.setManualRange(true); yaxis.setManualRange(true); // attach unitData to its graph graph.attachDataSet(unitData); xaxis.attachDataSet(unitData); yaxis.attachDataSet(unitData); tgraph = new G2Dint(); tgraph.disable(); // disable it so it cannot be selected etc // and wont accept input which could make strange side-effects // happen transformData = new DataSet(); txaxis = tgraph.createXAxis(); txaxis.setTitleText("X"); tyaxis = tgraph.createYAxis(); tyaxis.setTitleText("Y"); txaxis.setManualRange(true); tyaxis.setManualRange(true); // attach unitData to its graph tgraph.attachDataSet(unitData); txaxis.attachDataSet(unitData); tyaxis.attachDataSet(unitData); graph.setDataBackground(new Color(160,212,207)); graph.gridcolor = new Color(80, 170, 161); graph.zerocolor = new Color(134, 248, 237); graph.setBounds(0, 0, 260, 220); graphPanel.add(graph); tgraph.setDataBackground(new Color(160,212,207)); tgraph.gridcolor = new Color(80, 170, 161); tgraph.zerocolor = new Color(134, 248, 237); tgraph.setBounds(260, 0, 260, 220); graphPanel.add(tgraph); graphPanel.setBounds(-10, 3, 520, 220); add(graphPanel); graph.addMouseListener(new MouseAdapter(){ public void mousePressed(MouseEvent e) { graph.line = true; } public void mouseReleased(MouseEvent e) { //graph.line = false; } }); graph.addMouseMotionListener(new MouseMotionListener(){ public void mouseDragged(MouseEvent e) { double[] pt = graph.getClosestPoint(e.getX(), e.getY()); // graph the line on the unit circle graph.drawLine(pt[0], pt[1]); // transform the point and draw it on the transfomed graph double[][] tpts = A.times(new Matrix( new double[][]{{pt[0]}, {pt[1]}})).getArray(); tgraph.drawLine(tpts[0][0], tpts[1][0]); } public void mouseMoved(MouseEvent e) { // do nothing // } }); calc.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { tgraph.line = false; graph.line = false; parseA(); plotTransform(); double[] pt; // check if the transformation is the identity, if so // just show the unit vectors if (A.get(0, 0) == 1 && A.get(0,1) == 0 && A.get(1,0) == 0 && A.get(1,1) == 1) { double unit = Math.sqrt(2) / 2; pt = new double[]{unit , unit}; tgraph.drawVector(pt[0], pt[1]); tgraph.drawVector2(-pt[0], pt[1]); } else { pt = tgraph.getClosestPointCoords(0, 0); tgraph.drawVector(pt[0], pt[1]); pt = tgraph.getMostFarPointCoords(0, 0); tgraph.drawVector2(pt[0], pt[1]); } graph.repaint(); tgraph.repaint(); } }); } /** * Initialize the input fields for the matrix A. */ public Panel initAinput() { Panel Apanel = new Panel(new GridLayout(2, 5)); Label Vlbl = new Label("Matrix V"); Apanel.add(Vlbl); Label Dlbl = new Label("Matrix D"); Apanel.add(Dlbl); Label Ulbl = new Label("Matrix U^t"); Apanel.add(Ulbl); Label empty = new Label(""); Apanel.add(empty); Label Albl = new Label("Matrix A"); Apanel.add(Albl); Panel VentryPanel = new Panel(new GridLayout(2, 2)); V_a = new TextField(""); V_a.setEditable(false); VentryPanel.add(V_a); V_b = new TextField(""); V_b.setEditable(false); VentryPanel.add(V_b); V_c = new TextField(""); V_c.setEditable(false); VentryPanel.add(V_c); V_d = new TextField(""); V_d.setEditable(false); VentryPanel.add(V_d); Apanel.add(VentryPanel); Panel DentryPanel = new Panel(new GridLayout(2, 2)); D_a = new TextField(""); D_a.setEditable(false); DentryPanel.add(D_a); D_b = new TextField(""); D_b.setEditable(false); DentryPanel.add(D_b); D_c = new TextField(""); D_c.setEditable(false); DentryPanel.add(D_c); D_d = new TextField(""); D_d.setEditable(false); DentryPanel.add(D_d); Apanel.add(DentryPanel); Panel UentryPanel = new Panel(new GridLayout(2, 2)); U_a = new TextField(""); U_a.setEditable(false); UentryPanel.add(U_a); U_b = new TextField(""); U_b.setEditable(false); UentryPanel.add(U_b); U_c = new TextField(""); U_c.setEditable(false); UentryPanel.add(U_c); U_d = new TextField(""); U_d.setEditable(false); UentryPanel.add(U_d); Apanel.add(UentryPanel); Label eql = new Label(" = "); Apanel.add(eql); Panel AentryPanel = new Panel(new GridLayout(2, 2)); A_a = new TextField("1"); AentryPanel.add(A_a); A_b = new TextField("0"); AentryPanel.add(A_b); A_c = new TextField("0"); AentryPanel.add(A_c); A_d = new TextField("1"); AentryPanel.add(A_d); Apanel.add(AentryPanel); return Apanel; } /** * Parse the text fields and create the Matrix A from their values. */ public void parseA() { double a = Double.parseDouble(A_a.getText()); double b = Double.parseDouble(A_b.getText()); double c = Double.parseDouble(A_c.getText()); double d = Double.parseDouble(A_d.getText()); A = new Matrix(new double[][]{{a, b}, {c, d}}); //A.print(3, 4); } /** * Calculates the transformed points for the matrix A, and plots * them on the transform graph. */ public void plotTransform() { // calculate the transformed circle points double theta = 0; double[] transformPoints = new double[PLOT_POINTS * 2]; for (int i = 0; i < (PLOT_POINTS * 2); i+=2) { Matrix B = A.times(new Matrix(new double[][]{{unitPoints[i]}, {unitPoints[i + 1]}})); transformPoints[i] = B.get(0, 0); transformPoints[i + 1] = B.get(1, 0); } txaxis.detachAll(); tyaxis.detachAll(); tgraph.detachDataSets(); transformData.deleteData(); try { transformData.append(transformPoints, PLOT_POINTS); } catch (Exception e) { System.err.println("Unable to append unitPoints data"); } tgraph.attachDataSet(transformData); txaxis.attachDataSet(transformData); tyaxis.attachDataSet(transformData); double xmax = txaxis.getDataMax(); double ymax = tyaxis.getDataMax(); double max = (xmax > ymax) ? xmax : ymax; txaxis.maximum = max; txaxis.minimum = -max; tyaxis.maximum = max; tyaxis.minimum = -max; tgraph.repaint(); SingularValueDecomposition Asvd = A.svd(); Matrix U = Asvd.getV().transpose(); // strange V is U.. D = Asvd.getS(); V = Asvd.getU(); //(V.times(D).times(U)).print(4, 2); V_a.setText("" + V.get(0, 0)); V_b.setText("" + V.get(0, 1)); V_c.setText("" + V.get(1, 0)); V_d.setText("" + V.get(1, 1)); D_a.setText("" + D.get(0, 0)); D_b.setText("" + D.get(0, 1)); D_c.setText("" + D.get(1, 0)); D_d.setText("" + D.get(1, 1)); U_a.setText("" + U.get(0, 0)); U_b.setText("" + U.get(0, 1)); U_c.setText("" + U.get(1, 0)); U_d.setText("" + U.get(1, 1)); } }