/*
* HierarchicalPriorDialog.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
/*
* PriorDialog.java
*
* @author Marc A. Suchard
*/
package dr.app.beauti.priorsPanel;
import dr.app.beauti.components.hpm.HierarchicalModelComponentOptions;
import dr.app.beauti.components.hpm.HierarchicalPhylogeneticModel;
import dr.app.beauti.options.BeautiOptions;
import dr.app.beauti.options.Parameter;
import dr.app.beauti.types.PriorType;
import dr.app.gui.chart.Axis;
import dr.app.gui.chart.JChart;
import dr.app.gui.chart.LinearAxis;
import dr.app.gui.chart.PDFPlot;
import dr.app.gui.components.RealNumberField;
import dr.app.util.OSType;
import dr.math.distributions.Distribution;
import dr.math.distributions.GammaDistribution;
import dr.math.distributions.NormalDistribution;
import dr.math.distributions.OffsetPositiveDistribution;
import dr.util.NumberFormatter;
import jam.panels.OptionsPanel;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.HashMap;
import java.util.Map;
/**
* @author Marc A. Suchard
*/
public class HierarchicalPriorDialog {
private JFrame frame;
private Map<PriorType, PriorOptionsPanel> optionsPanels = new HashMap<PriorType, PriorOptionsPanel>();
private JComboBox priorCombo = new JComboBox();
private JCheckBox meanInRealSpaceCheck = new JCheckBox();
private RealNumberField initialField = new RealNumberField();
private RealNumberField selectedField;
// private final SpecialNumberPanel specialNumberPanel;
private JTextField nameField;
private JPanel panel;
// private final SpecialNumberPanel specialNumberPanel;
private JChart[] chart;
private JPanel[] quantilePanel;
private JTextArea[] quantileText;
private java.util.List<Parameter> parameterList;
private Parameter parameter;
final private BeautiOptions options;
private double hpmMeanMean = 0.0;
private double hpmMeanStDev = 100.0;
private double hpmMeanInitial = 0.0;
private double hpmPrecShape = 0.001;
private double hpmPrecScale = 1000.0;
private double hpmPrecInitial = 1.0;
public HierarchicalPriorDialog(JFrame frame, BeautiOptions options) {
this.frame = frame;
this.options = options;
initialField.setColumns(10);
optionsPanels.put(PriorType.NORMAL_PRIOR, new NormalOptionsPanel());
optionsPanels.put(PriorType.GAMMA_PRIOR, new GammaOptionsPanel());
chart = new JChart[2];
quantileText = new JTextArea[2];
quantilePanel = new JPanel[2];
for (int i = 0; i < 2; ++i) {
chart[i] = new JChart(new LinearAxis(Axis.AT_MINOR_TICK, Axis.AT_MINOR_TICK),
new LinearAxis(Axis.AT_ZERO, Axis.AT_DATA));
JLabel quantileLabels = new JLabel();
quantileLabels.setFont(quantileLabels.getFont().deriveFont(10.0f));
quantileLabels.setOpaque(false);
quantileLabels.setText("<html><p align=\"right\">Quantiles: 2.5%:<br>5%:<br>Median:<br>95%:<br>97.5%:</p></html>");
quantileText[i] = new JTextArea(0, 5);
quantileText[i].setFont(quantileText[i].getFont().deriveFont(10.0f));
quantileText[i].setOpaque(false);
quantileText[i].setEditable(false);
quantileLabels.setHorizontalAlignment(JLabel.LEFT);
quantilePanel[i] = new JPanel();
quantilePanel[i].add(quantileLabels);
quantilePanel[i].add(quantileText[i]);
}
// specialNumberPanel = new SpecialNumberPanel(this);
// specialNumberPanel.setEnabled(false);
}
public void addHPM(java.util.List<Parameter> parameterList) {
HierarchicalModelComponentOptions comp = (HierarchicalModelComponentOptions)
options.getComponentOptions(HierarchicalModelComponentOptions.class);
HierarchicalPhylogeneticModel hpm = comp.addHPM(nameField.getText(), parameterList, parameterList.get(0).priorType);
hpm.getConditionalParameterList().get(0).mean = hpmMeanMean;
hpm.getConditionalParameterList().get(0).stdev = hpmMeanStDev;
hpm.getConditionalParameterList().get(0).setInitial(hpmMeanInitial);
hpm.getConditionalParameterList().get(1).shape = hpmPrecShape;
hpm.getConditionalParameterList().get(1).scale = hpmPrecScale;
hpm.getConditionalParameterList().get(1).setInitial(hpmPrecInitial);
}
public boolean validateModelName() {
return validateModelName(nameField.getText());
}
private boolean validateModelName(String modelName) {
// System.err.println("Validating: " + modelName);
// check that the name is valid
if (modelName.trim().length() == 0) {
Toolkit.getDefaultToolkit().beep();
return false;
}
// check that the trait name doesn't exist
if (modelExists(modelName)) {
JOptionPane.showMessageDialog(frame,
"A model with this name already exists.",
"HPM name error",
// JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
// System.err.println("Model name exists");
return false;
// if (option == JOptionPane.NO_OPTION) {
// return false;
// }
}
return true;
}
private boolean modelExists(String modelName) {
HierarchicalModelComponentOptions comp = (HierarchicalModelComponentOptions)
options.getComponentOptions(HierarchicalModelComponentOptions.class);
return comp.modelExists(modelName);
}
public int showDialog(final java.util.List<Parameter> parameterList) {
this.parameterList = parameterList;
this.parameter = parameterList.get(0);
PriorType priorType = parameter.priorType;
// Set-up combo box depending on parameters
priorCombo.removeAllItems();
if (parameter.isNonNegative) {
priorCombo.addItem(PriorType.LOGNORMAL_HPM_PRIOR);
}
priorCombo.addItem(PriorType.NORMAL_HPM_PRIOR);
priorCombo.setSelectedItem(priorType);
double lower = Double.NEGATIVE_INFINITY;
double upper = Double.POSITIVE_INFINITY;
if (parameter.isZeroOne) {
lower = 0.0;
upper = 1.0;
} else if (parameter.isNonNegative) {
lower = 0.0;
}
initialField.setRange(lower, upper);
initialField.setValue(parameter.getInitial());
panel = new JPanel(new GridBagLayout());
setupComponents();
JScrollPane scrollPane = new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBorder(null);
scrollPane.getViewport().setOpaque(false);
JOptionPane optionPane = new JOptionPane(scrollPane,
JOptionPane.PLAIN_MESSAGE,
JOptionPane.OK_CANCEL_OPTION,
null,
null,
null);
optionPane.setBorder(new EmptyBorder(12, 12, 12, 12));
final JDialog dialog = optionPane.createDialog(frame, "Phylogenetic Hierarchical Model Setup");
priorCombo.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
setupComponents();
dialog.validate();
dialog.repaint();
dialog.pack();
}
});
for (PriorOptionsPanel optionsPanel : optionsPanels.values()) {
optionsPanel.addListener(new PriorOptionsPanel.Listener() {
public void optionsPanelChanged() {
setupChart();
dialog.validate();
dialog.repaint();
}
});
}
dialog.pack();
if (OSType.isMac()) {
dialog.setMinimumSize(new Dimension(dialog.getBounds().width, 300));
} else {
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension d = tk.getScreenSize();
if (d.height < 700 && panel.getHeight() > 450) {
dialog.setSize(new Dimension(panel.getWidth() + 100, 550));
} else {
// setSize because optionsPanel is shrunk in dialog
dialog.setSize(new Dimension(panel.getWidth() + 100, panel.getHeight() + 100));
}
// System.out.println("panel width = " + panel.getWidth());
// System.out.println("panel height = " + panel.getHeight());
}
dialog.setResizable(true);
dialog.setVisible(true);
int result = JOptionPane.CANCEL_OPTION;
Integer value = (Integer) optionPane.getValue();
if (value != null && value != -1) {
result = value;
}
// if (result == JOptionPane.OK_OPTION) {
// getArguments();
// }
return result;
}
private void setArguments(PriorType priorType) {
optionsPanels.get(PriorType.NORMAL_PRIOR).setArguments(parameter);
optionsPanels.get(PriorType.GAMMA_PRIOR).setArguments(parameter);
}
public void getArguments() {
for (Parameter parameter : parameterList) {
parameter.priorType = (PriorType) priorCombo.getSelectedItem();
}
// Get hyperpriors
optionsPanels.get(PriorType.NORMAL_PRIOR).getArguments(parameter);
optionsPanels.get(PriorType.GAMMA_PRIOR).getArguments(parameter);
}
private void setupComponents() {
panel.removeAll();
JPanel mainPanel = new JPanel(new GridBagLayout());
panel.add(mainPanel);
OptionsPanel[] optionsPanel = new OptionsPanel[4];
JPanel panel[] = new JPanel[4];
for (int i = 0; i < 4; ++i) {
optionsPanel[i] = new OptionsPanel(12, (OSType.isMac() ? 6 : 24));
panel[i] = new JPanel(new FlowLayout(FlowLayout.CENTER));
if (i > 1) {
panel[i].setLayout(new BoxLayout(panel[i], BoxLayout.PAGE_AXIS));
}
panel[i].add(optionsPanel[i]);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = i % 2;
gbc.gridy = i / 2;
mainPanel.add(panel[i], gbc);
}
optionsPanel[0].addComponent(new JLabel("Select HPM for parameters: "));
OptionsPanel list = new OptionsPanel();
list.setBackground(Color.WHITE);
list.setOpaque(true);
for (Parameter p : parameterList) {
JLabel label = new JLabel("\t" + p.getName());
label.setForeground(Color.DARK_GRAY);
list.addSpanningComponent(label);
}
optionsPanel[0].addSpanningComponent(list);
PriorType modelType;
optionsPanel[1].addComponentWithLabel("Hierarchical Distribution: ", priorCombo);
modelType = (PriorType) priorCombo.getSelectedItem();
optionsPanel[1].addSeparator();
String modelName = "untitled";
nameField = new JTextField(modelName);
nameField.setColumns(10);
optionsPanel[1].addComponentWithLabel("Unique Name: ", nameField);
optionsPanel[2].addSeparator();
optionsPanel[3].addSeparator();
optionsPanel[2].addSpanningComponent(new JLabel("Population Mean Hyperprior: Normal"));
optionsPanel[3].addSpanningComponent(new JLabel("Population Precision Hyperprior: Gamma"));
optionsPanel[2].addSpanningComponent(optionsPanels.get(PriorType.NORMAL_PRIOR));
optionsPanel[3].addSpanningComponent(optionsPanels.get(PriorType.GAMMA_PRIOR));
optionsPanel[2].addSeparator();
optionsPanel[3].addSeparator();
setupChart();
for (int i = 0; i < 2; ++i) {
chart[i].setPreferredSize(new Dimension(300, 200));
chart[i].setFontSize(8);
panel[2 + i].add(chart[i]);
panel[2 + i].add(quantilePanel[i]);
}
setArguments(modelType);
mainPanel.repaint();
}
// public void setSelectedField(RealNumberField selectedField) {
// this.selectedField = selectedField;
// }
//
// public RealNumberField getSelectedField() {
// return selectedField;
// }
NumberFormatter formatter = new NumberFormatter(4);
private void setupChart() {
for (int i = 0; i < 2; ++i) {
chart[i].removeAllPlots();
double offset = 0.0;
Distribution distribution = null;
switch (i) {
case 0:
distribution = optionsPanels.get(PriorType.NORMAL_PRIOR).getDistribution();
break;
case 1:
distribution = optionsPanels.get(PriorType.GAMMA_PRIOR).getDistribution();
break;
}
chart[i].addPlot(new PDFPlot(distribution, offset));
if (distribution != null) {
quantileText[i].setText(formatter.format(distribution.quantile(0.025)) +
"\n" + formatter.format(distribution.quantile(0.05)) +
"\n" + formatter.format(distribution.quantile(0.5)) +
"\n" + formatter.format(distribution.quantile(0.95)) +
"\n" + formatter.format(distribution.quantile(0.975)));
}
}
}
// options panels
class NormalOptionsPanel extends PriorOptionsPanel {
public NormalOptionsPanel() {
super(false);
}
public void setup() {
addField("Hyperprior Mean", 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
addField("Hyperprior Stdev", 1000.0, 0.0, Double.MAX_VALUE);
}
public Distribution getDistribution() {
return new NormalDistribution(getValue(0), getValue(1));
}
public void setArguments(Parameter parameter) {
getInitialField().setValue(hpmMeanInitial);
getField(0).setValue(hpmMeanMean);
getField(1).setValue(hpmMeanStDev);
}
public void getArguments(Parameter parameter) {
hpmMeanInitial = getInitialField().getValue();
hpmMeanMean = getValue(0);
hpmMeanStDev = getValue(1);
}
@Override
boolean isInputValid() {
return true;
}
}
class GammaOptionsPanel extends PriorOptionsPanel {
public GammaOptionsPanel() {
super(false);
}
public void setup() {
addField("Hyperprior Shape", 0.001, Double.MIN_VALUE, Double.POSITIVE_INFINITY);
addField("Hyperprior Scale", 1000.0, Double.MIN_VALUE, Double.POSITIVE_INFINITY);
// addField("Offset", 0.0, 0.0, Double.POSITIVE_INFINITY);
}
public Distribution getDistribution() {
return new OffsetPositiveDistribution(
new GammaDistribution(getValue(0), getValue(1)), 0.0);
}
public void setArguments(Parameter parameter) {
getInitialField().setValue(hpmPrecInitial);
getField(0).setValue(hpmPrecShape);
getField(1).setValue(hpmPrecScale);
}
public void getArguments(Parameter parameter) {
hpmPrecInitial = getInitialField().getValue();
parameter.shape = hpmPrecShape = getValue(0);
parameter.scale = hpmPrecScale = getValue(1);
}
@Override
boolean isInputValid() {
return true;
}
}
}