/* * ------------------------------------------------------------------------- * $Id: NLPanel.java,v 1.3 2004/04/07 20:08:27 estewart Exp $ * ------------------------------------------------------------------------- * Copyright (c) 1999 Visual Numerics Inc. All Rights Reserved. * * This software is confidential information which is proprietary to * and a trade secret of Visual Numerics, Inc. Use, duplication or * disclosure is subject to the terms of an appropriate license * agreement. * * VISUAL NUMERICS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE * SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. VISUAL * NUMERICS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR * ITS DERIVATIVES. *-------------------------------------------------------------------------- */ /* * NLPanel.java * * Created on August 19, 2003, 2:15 PM */ package com.imsl.demo.fitting; import com.imsl.chart.*; import com.imsl.math.*; import java.util.Vector; import java.awt.event.*; import javax.swing.JOptionPane; public class NLPanel extends javax.swing.JPanel implements ActionListener, MouseListener { private Chart chart; private AxisXY axis; private Data point, line; private Vector dataX, dataY; private double[] xRange, yRange, solution, xGuess; private NonlinLeastSquares.Function function; private ChartFunction chartFunction; private javax.swing.JFrame parentFrame; private javax.swing.JRadioButtonMenuItem[] jRadioButtons; private final int numData = 4; private final String dataName[] = {"Blank", "Sunflower Growth", "Metal Response", "Plume Trajectory"}; private int nParams; private StringBuffer infoSolution; private String[] infoParams; private String infoData, infoModel; private javax.swing.ImageIcon iconModel; private javax.swing.JPopupMenu popMenu; /** Creates new form CsPanel */ public NLPanel(javax.swing.JFrame parent) { this.parentFrame = parent; dataX = new Vector(); dataY = new Vector(); initComponents(); setPreferredSize(new java.awt.Dimension(parent.getSize().width, (int)(0.85*parent.getSize().height))); popMenu = createPopMenu(); jButtonMore.addMouseListener(new PopupListener()); jButtonGenerate.addActionListener(this); jButtonEnter.addActionListener(this); jButtonReset.addActionListener(this); radioSig.addActionListener(this); radioExp.addActionListener(this); radioPow.addActionListener(this); addDataButtons(); setChart(); jPanelChart.setPreferredSize(new java.awt.Dimension(parent.getSize().width, (int)(0.66*parent.getSize().height))); getData(); setModel(); update(); } private void setChart() { chart = jPanelChart.getChart(); chart.getLegend().setPaint(true); chart.getLegend().setViewport(0.4,0.55,0.01,0.5); axis = new AxisXY(chart); axis.setViewport(0.12,0.88,0.05,0.88); // Set axes ranges axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF); axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF); axis.getAxisX().setWindow(0.0, 50.0); axis.getAxisY().setWindow(0.0, 75.0); axis.getAxisX().setTextFormat(new java.text.DecimalFormat("###.#")); axis.getAxisY().setTextFormat(new java.text.DecimalFormat("###.#")); // Add MouseListeren to the chart. jPanelChart.addMouseListener(this); } // add the available data sets to the dataMenu private void addDataButtons() { jRadioButtons = new javax.swing.JRadioButtonMenuItem[numData]; javax.swing.ButtonGroup group = new javax.swing.ButtonGroup(); for (int i=0; i<numData; i++) { jRadioButtons[i] = new javax.swing.JRadioButtonMenuItem(); jRadioButtons[i].setText(dataName[i]); jRadioButtons[i].addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { getData(); update(); } }); group.add(jRadioButtons[i]); ((FittingMain)parentFrame).getDataMenu().add(jRadioButtons[i]); } jRadioButtons[1].setSelected(true); } // clear and load data sets determined by the menu selection public void getData() { dataX.clear(); dataY.clear(); xRange = new double[2]; yRange = new double[2]; String xTitle = "X Value"; String yTitle = "Y Value"; String xFormat = "###.#"; String yFormat = "###.#"; int selected = -1; for (int i=0; i<numData; i++) { if (jRadioButtons[i].isSelected()) { selected = i; break; } } switch (selected) { case 0: // blank xRange[0] = 0.0; xRange[1] = 50.0; yRange[0] = 0.0; yRange[1] = 50.0; this.infoData = "User data.\n"; break; case 1: // sunflower growth data double[] x1 = {7,14,21,28,35,42,49,56,63,70,77,85}; double[] y1 = {17.93,36.36,67.67,98.10,131.00,169.50,205.50,228.30,247.10,250.50,253.80,254.50}; this.infoData = "The growth rate of a sunflower as documented \n"+ "in Reed, H. S. and Holland, R. H. (1919), Growth \n"+ "of sunflower seeds; Proceedings of the National \n"+ "Academy of Sciences, volume 5, p. 140.\n\n"+ "As with much biological data, this growth data \n"+ "is well-represented by a sigmoidal model.\n"; for (int i=0; i<x1.length; i++) { dataX.add(new Double(x1[i])); dataY.add(new Double(y1[i])); } xTitle = "Time, days"; yTitle = "Height, cm"; xFormat = "##"; yFormat = "###"; xRange[0] = 0.0; xRange[1] = 100.0; yRange[0] = 0.0; yRange[1] = 300.0; break; case 2: // NIST ultrasonic responce and metal distance. double[] x2 = {80,71,64,58,52,44,37,31,26,22,19,17,15}; double[] y2 = {0.5,0.625,0.75,0.875,1.0,1.25,1.5,1.75,2.0,2.25,2.5,2.75,3.00}; this.infoData = "This data is from the calibration of a metal\n"+ "alloy block subjected to ultrasound waves. It was\n"+ "obtained from a NIST report available at:\n"+ "http://ts.nist.gov/ts/htdocs/230/233/calibrations/mechanical/PUBS/E127-98.pdf\n\n"+ "This data shows exponential decay and can be\n"+ "represented well by an exponential model.\n\n"; for (int i=0; i<x2.length; i++) { dataX.add(new Double(x2[i])); dataY.add(new Double(y2[i])); } xTitle = "Echo Amplitude, %"; yTitle = "Metal Distance, inches"; xFormat = "###"; yFormat = "#.#"; xRange[0] = 0.0; xRange[1] = 100.0; yRange[0] = 0.0; yRange[1] = 5.0; break; case 3: // Turbulent chimney plume trajectory double[] x3 = {2.90,5.7,8.69,10.63,11.58,12.76,14.48,15.19,17.37,21.27,25.52,30.38,31.90,38.28,42.54,45.58,51.05,53.17,60.77,63.81,63.81,75.96,76.57,91.15}; double[] y3 = {1.88,2.46,2.95,3.90,3.32,4.01,3.61,4.30,3.87,5.29,5.48,6.37,6.82,6.68,7.38,7.96,8.16,8.36,9.71,9.36,9.47,11.14,10.03,12.41}; this.infoData = "This data set is from an experimental study on \n"+ "the behavior of chimney plumes in a steady wind.\n"+ "The source is: Huq., P. and Stewart, E. J. (1996)\n"+ "A laboratory study of buoyant plumes in laminar\n"+ "and turbulent crossflows. Atmospheric Environment\n"+ "30 (7), 1125-1135.\n\n"+ "This trajectory data is historically known to \n"+ "follow a 2/3 power law.\n\n"; for (int i=0; i<x3.length; i++) { dataX.add(new Double(x3[i])); dataY.add(new Double(y3[i])); } xTitle = "Distance from Source, m"; yTitle = "Plume Elevation, m"; xFormat = "###"; yFormat = "##.#"; xRange[0] = 0.0; xRange[1] = 100.0; yRange[0] = 0.0; yRange[1] = 15.0; break; } axis.getAxisX().getAxisTitle().setTitle(xTitle); axis.getAxisY().getAxisTitle().setTitle(yTitle); axis.getAxisX().setWindow(xRange); axis.getAxisY().setWindow(yRange); axis.getAxisX().setTextFormat(new java.text.DecimalFormat(xFormat)); axis.getAxisY().setTextFormat(new java.text.DecimalFormat(yFormat)); } /** load the function to solve and to plot */ private void setModel() { int selected = -1; if (radioSig.isSelected()) selected = 0; if (radioExp.isSelected()) selected = 1; if (radioPow.isSelected()) selected = 2; switch (selected) { case 0: // sigmoidal model function = new NonlinLeastSquares.Function() { public void f(double theta[], double f[]) { // theta[0] = at // theta[1] = ab // theta[2] = x0 // theta[3] = w for (int i=0; i<dataX.size(); i++) { f[i] = (theta[1] + (theta[0] - theta[1])/ (1.0 + JMath.exp(- (((Double)dataX.get(i)).doubleValue() - theta[2])/theta[3]))) - ((Double)dataY.get(i)).doubleValue(); } } }; chartFunction = new ChartFunction() { public double f(double x) { return (solution[1] + (solution[0] - solution[1])/ (1.0 + JMath.exp(- (x - solution[2])/solution[3]))); } }; this.nParams = 4; this.xGuess = new double[] {300, 0, 50.0, 10.0}; this.infoParams = new String[] {"At", "Ab", "xo", "w"}; this.infoModel = "A sigmoidal model, with its easily identified\n"+ "S shape, is used to represent many biological\n"+ "processes.\n\n"+ "The parameters to solve for are At, Ab, xo, and w\n\n"; this.iconModel = new javax.swing.ImageIcon(getClass().getResource("/com/imsl/demo/fitting/eq_sig.gif")); break; case 1: // exponential over linear function = new NonlinLeastSquares.Function() { public void f(double theta[], double f[]) { // theta[0] = b1 // theta[1] = b2 // theta[2] = b3 for (int i=0; i<dataX.size(); i++) { double xx = ((Double)dataX.get(i)).doubleValue(); f[i] = JMath.exp(-theta[0]*xx)/(theta[1] + theta[2]*xx) - ((Double)dataY.get(i)).doubleValue(); } } }; chartFunction = new ChartFunction() { public double f(double x) { return JMath.exp(-solution[0]*x)/(solution[1] + solution[2]*x); } }; this.nParams = 3; this.xGuess = new double[] {0.1, 0.1, 0.1}; this.infoParams = new String[] {"b1", "b2", "b3"}; this.infoModel = "This is an exponential model with additional\n"+ "inverse dependence on x, providing a better \n"+ "fit than a normal exponential for some cases.\n\n"+ "The parameters to solve for are b1, b2, and b3\n\n"; this.iconModel = new javax.swing.ImageIcon(getClass().getResource("/com/imsl/demo/fitting/eq_exp.gif")); break; case 2: // two-parameter 2/3 power law function = new NonlinLeastSquares.Function() { public void f(double theta[], double f[]) { // theta[0] = betaT // theta[1] = beta for (int i=0; i<dataX.size(); i++) { double xx = ((Double)dataX.get(i)).doubleValue(); f[i] = JMath.pow((3/(theta[0]*theta[0]))*(0.05*xx*xx+xx)+JMath.pow((0.32/theta[1]),3),1./3.)-(0.32/theta[1])-((Double)dataY.get(i)).doubleValue(); } } }; chartFunction = new ChartFunction() { public double f(double x) { return JMath.pow((3/(solution[0]*solution[0]))*(0.05*x*x+x)+JMath.pow((0.32/solution[1]),3),1./3.)-(0.32/solution[1]); } }; this.nParams = 2; this.xGuess = new double[] {0.4, 0.6}; this.infoParams = new String[] {"betaT", "beta"}; this.infoModel = "A power law appears as a straight line on log\n"+ "axes. For this model, we know the power is 2/3\n"+ "and other adjustment variables are computed.\n\n"+ "The parameters to solve for are betaT and beta\n\n"; this.iconModel = new javax.swing.ImageIcon(getClass().getResource("/com/imsl/demo/fitting/eq_pow.gif")); break; } } private void getSolution() { NonlinLeastSquares zs = new NonlinLeastSquares(dataX.size(), nParams); zs.setGuess(xGuess); zs.setMaxIterations(500); solution = new double[nParams]; if (dataX.size() > nParams) { try { solution = zs.solve(function); } catch (NonlinLeastSquares.TooManyIterationsException tmie) { javax.swing.JOptionPane.showMessageDialog(this, /*tmie.getMessage()*/ "Too many iterations required. Select \n\n" + " More Info -> Data\n\nfor information " + "about the best model\nchoice for the chosen data set.", "Convergence Details", javax.swing.JOptionPane.ERROR_MESSAGE); } } infoSolution = new StringBuffer("Parameter Solutions\n"); infoSolution.append("-------------------\n"); java.text.DecimalFormat df = new java.text.DecimalFormat("####.####"); for (int i=0; iprivate void initComponents() {//GEN-BEGIN:initComponents buttonGroup = new javax.swing.ButtonGroup(); jPanel = new javax.swing.JPanel(); jButtonEnter = new javax.swing.JButton(); jButtonGenerate = new javax.swing.JButton(); jLabelPoints = new javax.swing.JLabel(); numField = new javax.swing.JTextField("6", 4); jButtonReset = new javax.swing.JButton(); jPanel1 = new javax.swing.JPanel(); jLabel1 = new javax.swing.JLabel(); jPanelChecks = new javax.swing.JPanel(); radioSig = new javax.swing.JRadioButton(); radioExp = new javax.swing.JRadioButton(); radioPow = new javax.swing.JRadioButton(); jButtonMore = new javax.swing.JButton(); jPanelChart = new com.imsl.chart.JPanelChart(); setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS)); setPreferredSize(new java.awt.Dimension(500, 600)); jPanel.setMaximumSize(new java.awt.Dimension(32767, 36)); jButtonEnter.setText("Enter Points"); jPanel.add(jButtonEnter); jButtonGenerate.setText("Generate"); jPanel.add(jButtonGenerate); jLabelPoints.setText("# of Points: "); jPanel.add(jLabelPoints); numField.setColumns(4); numField.setText("6"); jPanel.add(numField); jButtonReset.setText("Reset"); jPanel.add(jButtonReset); add(jPanel); jPanel1.setMaximumSize(new java.awt.Dimension(32767, 20)); jPanel1.setMinimumSize(new java.awt.Dimension(89, 20)); jPanel1.setPreferredSize(new java.awt.Dimension(89, 20)); jLabel1.setText("Select Nonlinear Model: "); jPanel1.add(jLabel1); add(jPanel1); jPanelChecks.setMaximumSize(new java.awt.Dimension(32767, 36)); jPanelChecks.setPreferredSize(new java.awt.Dimension(468, 36)); radioSig.setSelected(true); radioSig.setText("Sigmoidal"); buttonGroup.add(radioSig); jPanelChecks.add(radioSig); radioExp.setText("Exponential Over Linear"); buttonGroup.add(radioExp); jPanelChecks.add(radioExp); radioPow.setText("2/3 Power Law "); buttonGroup.add(radioPow); jPanelChecks.add(radioPow); jButtonMore.setText("More Info"); jPanelChecks.add(jButtonMore); add(jPanelChecks); jPanelChart.setPreferredSize(new java.awt.Dimension(500, 500)); add(jPanelChart); }//GEN-END:initComponents // Implement ActionListener public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("Enter Points")) { // Enter points using a text area. javax.swing.JTextArea textArea = new javax.swing.JTextArea(10, 5); javax.swing.JScrollPane scroll = new javax.swing.JScrollPane(textArea); int option = 0; try { option = JOptionPane.showConfirmDialog(this, scroll, "Enter Points", JOptionPane.OK_CANCEL_OPTION); if (option == JOptionPane.CANCEL_OPTION) return; } catch (Exception ex) { } java.util.StringTokenizer lineToken = new java.util.StringTokenizer(textArea.getText(), "\n"); while (lineToken.hasMoreElements()) { java.util.StringTokenizer token = new java.util.StringTokenizer(lineToken.nextToken(), "(){}[] ,\t\n"); // If the line does not contain 2 numbers, then ignore. if (token.countTokens() == 2) { try { double x = Double.parseDouble(token.nextToken()); double y = Double.parseDouble(token.nextToken()); // Ignore any points that are not in the range. if ((x >= xRange[0]) && (x <= xRange[1]) && (y >= yRange[0]) && (y <= yRange[1])) { dataX.add(new Double(x)); dataY.add(new Double(y)); } } catch (Exception ex) { } } } update(); } else if (e.getActionCommand().equals("Generate")) { int i = 0; try { i = Integer.parseInt(numField.getText()); } catch (Exception exception) { i = 6; numField.setText("6"); } finally { if (i <= 0) { i = 6; numField.setText("6"); } for (int j = 0; j < i; j++) { dataX.add(new Double(xRange[1] * Math.random() + xRange[0])); dataY.add(new Double(yRange[1] * Math.random() + yRange[0])); } update(); } } else if (e.getActionCommand().equals("Reset")) { numField.setText("6"); dataX = new Vector(); dataY = new Vector(); update(); getData(); update(); } else if (e.getActionCommand().equals("Data")) { javax.swing.JOptionPane.showMessageDialog(this, infoData, "Data Details", javax.swing.JOptionPane.INFORMATION_MESSAGE); } else if (e.getActionCommand().equals("Model")) { javax.swing.JOptionPane.showMessageDialog(this, infoModel, "Model Details", javax.swing.JOptionPane.INFORMATION_MESSAGE, iconModel); } else if (e.getActionCommand().equals("Solution")) { // setFont() has no affect on JOptionPane // see: http://developer.java.sun.com/developer/bugParade/bugs/4458089.html //jop.setFont(new java.awt.Font("Monospaced", java.awt.Font.PLAIN, 12)); // so we'll do it the hard way javax.swing.JLabel infoLabel = new javax.swing.JLabel(); // of course, JLabel doesn't recognize \n, so it's more work here too infoLabel.setText("<HTML>"+infoSolution.toString().replaceAll("\n","<BR>")+"</HTML>"); infoLabel.setFont(new java.awt.Font("Monospaced", java.awt.Font.PLAIN, 12)); javax.swing.JOptionPane.showMessageDialog(this, infoLabel, "Solution Details", javax.swing.JOptionPane.INFORMATION_MESSAGE, iconModel); } else { // this means a checkbox is changed setModel(); update(); } } // Implement MouseListener public void mouseClicked(MouseEvent e) { double user[] = {0,0}; axis.mapDeviceToUser(e.getX(), e.getY(), user); // Do not add the point if it is not in the range. if ((user[0] < xRange[0]) || (user[0] > xRange[1]) || (user[1] < yRange[0]) || (user[1] > yRange[1])) return; // Button is to add points. Otherwise, remove points. if (e.getModifiers() == MouseEvent.BUTTON1_MASK) { dataX.add(new Double(user[0])); dataY.add(new Double(user[1])); update(); } else { int idx = -1; double min = 0.01; final double ux = (user[0]-xRange[0])/(double)(xRange[1]-xRange[0]); final double uy = (user[1]-yRange[0])/(double)(yRange[1]-yRange[0]); for (int i = 0; i < dataX.size(); i++) { double x = (((Double)dataX.get(i)).doubleValue()-xRange[0])/ (double)(xRange[1]-xRange[0]); double y = (((Double)dataY.get(i)).doubleValue()-yRange[0])/ (double)(yRange[1]-yRange[0]); double dist = Math.sqrt((ux - x)*(ux - x) + (uy - y)*(uy - y)); if (dist < min) { min = dist; idx = i; } } if (idx != -1) { dataX.remove(idx); dataY.remove(idx); update(); } } } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabelPoints; private javax.swing.JButton jButtonMore; private javax.swing.JButton jButtonGenerate; private javax.swing.JButton jButtonReset; private javax.swing.JTextField numField; private com.imsl.chart.JPanelChart jPanelChart; private javax.swing.JRadioButton radioPow; private javax.swing.JRadioButton radioExp; private javax.swing.JRadioButton radioSig; private javax.swing.JPanel jPanel1; private javax.swing.JButton jButtonEnter; private javax.swing.JPanel jPanel; private javax.swing.JPanel jPanelChecks; // End of variables declaration//GEN-END:variables class PopupListener extends MouseAdapter { public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger() || e.getModifiers() == MouseEvent.BUTTON1_MASK) { popMenu.show(e.getComponent(), e.getX(), e.getY()); } } } }