/*
* -------------------------------------------------------------------------
* $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; i
private 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());
}
}
}
}