/* * PriorOptionsPanel.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 */ package dr.app.beauti.priorsPanel; import dr.app.beauti.options.Parameter; import dr.app.beauti.types.PriorType; import dr.app.beauti.util.PanelUtils; import dr.app.gui.components.RealNumberField; import dr.app.util.OSType; import dr.math.distributions.*; import jam.panels.OptionsPanel; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.text.NumberFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author Alexei Drummond * @author Andrew Rambaut * @author Walter Xie */ abstract class PriorOptionsPanel extends OptionsPanel { // this not throw exception, exception thrown by FocusListener in RealNumberField public boolean hasInvalidInput(PriorType priorType) { // TODO move all validation here for (JComponent component : argumentFields) { if (component instanceof RealNumberField && !((RealNumberField) component).isValueValid()) { error = ((RealNumberField) component).getErrorMessage(); return true; } } if (priorType == PriorType.UNIFORM_PRIOR && !isInputValid()) { error = "Invalid uniform bound !"; return true; } if (isTruncatable && isTruncatedCheck.isSelected()) { if (!lowerField.isValueValid()) { error = lowerField.getErrorMessage(); return true; } else if (!upperField.isValueValid()) { error = upperField.getErrorMessage(); return true; } else if (lowerField.getValue() >= upperField.getValue()) { error = "Invalid truncation bound !"; return true; } else if (getValue(OFFSET) > -1 && lowerField.getValue() < getValue(OFFSET)) { error = "Offset cannot be smaller than truncation lower !"; return true; } else { error = ""; return false; } } error = ""; return false; } public String error = ""; interface Listener { void optionsPanelChanged(); } private List<JComponent> argumentFields = new ArrayList<JComponent>(); private List<String> argumentNames = new ArrayList<String>(); private boolean isCalibratedYule = false; private boolean isInitializable = true; private final boolean isTruncatable; private final RealNumberField initialField = new RealNumberField(); private final JButton negativeInfinityButton; private final JButton positiveInfinityButton; private final JCheckBox isTruncatedCheck = new JCheckBox("Truncate to:"); // only this RealNumberField constructor adds FocusListener private final RealNumberField lowerField = new RealNumberField(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, "truncate lower"); private final JLabel lowerLabel = new JLabel("Lower: "); private final RealNumberField upperField = new RealNumberField(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, "truncate upper"); private final JLabel upperLabel = new JLabel("Upper: "); protected final Set<Listener> listeners = new HashSet<Listener>(); PriorOptionsPanel(boolean isTruncatable) { super(12, (OSType.isMac() ? 6 : 24)); this.isTruncatable = isTruncatable; negativeInfinityButton = new JButton(NumberFormat.getNumberInstance().format(Double.NEGATIVE_INFINITY)); PanelUtils.setupComponent(negativeInfinityButton); negativeInfinityButton.setFocusable(false); negativeInfinityButton.setActionCommand(RealNumberField.NEGATIVE_INFINITY); negativeInfinityButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { lowerField.setText(e.getActionCommand()); } }); negativeInfinityButton.setToolTipText("Click to set 'Positive Infinity' in the numerical field."); positiveInfinityButton = new JButton(NumberFormat.getNumberInstance().format(Double.POSITIVE_INFINITY)); PanelUtils.setupComponent(positiveInfinityButton); positiveInfinityButton.setFocusable(false); positiveInfinityButton.setActionCommand(RealNumberField.POSITIVE_INFINITY); positiveInfinityButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { upperField.setText(e.getActionCommand()); } }); positiveInfinityButton.setToolTipText("Click to set 'Negative Infinity' in the numerical field."); initialField.setColumns(10); lowerField.setColumns(10); upperField.setColumns(10); setup(); isTruncatedCheck.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { lowerField.setEnabled(isTruncatedCheck.isSelected()); lowerLabel.setEnabled(isTruncatedCheck.isSelected()); negativeInfinityButton.setEnabled(isTruncatedCheck.isSelected()); upperField.setEnabled(isTruncatedCheck.isSelected()); upperLabel.setEnabled(isTruncatedCheck.isSelected()); positiveInfinityButton.setEnabled(isTruncatedCheck.isSelected()); for (Listener listener : listeners) { listener.optionsPanelChanged(); } } }); KeyListener listener = new KeyAdapter() { public void keyReleased(KeyEvent e) { if (e.getComponent() instanceof RealNumberField) { String number = ((RealNumberField) e.getComponent()).getText(); if (!(number.equals("") || number.endsWith("e") || number.endsWith("E") || number.endsWith("-"))) { // System.out.println(e.getID() + " = \"" + ((RealNumberField) e.getComponent()).getText() + "\""); // setupChart(); // dialog.repaint(); for (Listener listener : listeners) { listener.optionsPanelChanged(); } } } } }; initialField.addKeyListener(listener); for (JComponent component : argumentFields) { if (component instanceof RealNumberField) { component.addKeyListener(listener); } } lowerField.addKeyListener(listener); upperField.addKeyListener(listener); } protected RealNumberField getInitialField() { return initialField; } void addListener(Listener listener) { listeners.add(listener); } void removeAllListeners() { listeners.clear(); } protected void setFieldRange(RealNumberField field, boolean isNonNegative, boolean isZeroOne) { double lower = Double.NEGATIVE_INFINITY; double upper = Double.POSITIVE_INFINITY; if (isZeroOne) { lower = 0.0; upper = 1.0; } else if (isNonNegative) { lower = 0.0; } field.setRange(lower, upper); } protected void setFieldRange(RealNumberField field, boolean isNonNegative, boolean isZeroOne, double truncationLower, double truncationUpper) { double lower = Double.NEGATIVE_INFINITY; double upper = Double.POSITIVE_INFINITY; if (isZeroOne) { lower = 0.0; upper = 1.0; } else if (isNonNegative) { lower = 0.0; } if (lower < truncationLower) { lower = truncationLower; } if (upper > truncationUpper) { upper = truncationUpper; } field.setRange(lower, upper); } protected void addField(String name, double initialValue, double min, boolean includeMin, double max, boolean includeMax) { RealNumberField field = new RealNumberField(min, includeMin, max, includeMax, name); field.setValue(initialValue); addField(name, field); } protected void addField(String name, double initialValue, double min, double max) { RealNumberField field = new RealNumberField(min, max, name); field.setValue(initialValue); addField(name, field); } protected void addField(String name, RealNumberField field) { argumentNames.add(name); field.setColumns(10); argumentFields.add(field); setupComponents(); } protected void addCheckBox(String name, JCheckBox jCheckBox) { argumentNames.add(name); argumentFields.add(jCheckBox); setupComponents(); } protected void replaceFieldName(int i, String name) { argumentNames.set(i, name); ((RealNumberField) argumentFields.get(i)).setLabel(name); setupComponents(); } protected double getValue(int i) { return ((RealNumberField) argumentFields.get(i)).getValue(); } protected double getValue(String fieldName) { int i = argumentNames.indexOf(fieldName); if (i < 0) return -1; // has no offset field return ((RealNumberField) argumentFields.get(i)).getValue(); } protected String getArguName(int i) { return argumentNames.get(i); } private void setupComponents() { removeAll(); if (isInitializable && !isCalibratedYule) { addComponentWithLabel("Initial value: ", initialField); } for (int i = 0; i < argumentFields.size(); i++) { addComponentWithLabel(argumentNames.get(i) + ":", argumentFields.get(i)); } if (isTruncatable && !isCalibratedYule) { addSpanningComponent(isTruncatedCheck); JPanel panel = new JPanel(); panel.add(upperField); panel.add(positiveInfinityButton); addComponents(upperLabel, panel); panel = new JPanel(); panel.add(lowerField); panel.add(negativeInfinityButton); addComponents(lowerLabel, panel); positiveInfinityButton.setMinimumSize(new Dimension(negativeInfinityButton.getWidth(), negativeInfinityButton.getHeight())); lowerField.setEnabled(isTruncatedCheck.isSelected()); lowerLabel.setEnabled(isTruncatedCheck.isSelected()); negativeInfinityButton.setEnabled(isTruncatedCheck.isSelected()); upperField.setEnabled(isTruncatedCheck.isSelected()); upperLabel.setEnabled(isTruncatedCheck.isSelected()); positiveInfinityButton.setEnabled(isTruncatedCheck.isSelected()); } } RealNumberField getField(int i) { return (RealNumberField) argumentFields.get(i); } Distribution getDistribution(Parameter parameter) { Distribution dist = getDistribution(); boolean isBounded = isTruncatedCheck.isSelected(); double lower = Double.NEGATIVE_INFINITY; double upper = Double.POSITIVE_INFINITY; if (parameter.isZeroOne) { lower = 0.0; upper = 1.0; // isBounded = true; } else if (parameter.isNonNegative) { lower = 0.0; // isBounded = true; } if (dist != null && isTruncatable && isBounded) { if (isTruncatedCheck.isSelected()) { lower = lowerField.getValue(); upper = upperField.getValue(); } dist = new TruncatedDistribution(dist, lower, upper); } return dist; } void setArguments(Parameter parameter, PriorType priorType) { this.isCalibratedYule = parameter.isCalibratedYule; this.isInitializable = priorType.isInitializable && !parameter.isStatistic && !parameter.isNodeHeight; if (!parameter.isStatistic && !parameter.isNodeHeight) { setFieldRange(initialField, parameter.isNonNegative, parameter.isZeroOne); initialField.setValue(parameter.getInitial()); } isTruncatedCheck.setSelected(parameter.isTruncated); setFieldRange(lowerField, parameter.isNonNegative, parameter.isZeroOne, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); setFieldRange(upperField, parameter.isNonNegative, parameter.isZeroOne, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); lowerField.setLabel(parameter.getName() + " truncate lower"); upperField.setLabel(parameter.getName() + " truncate upper"); lowerField.setValue(parameter.getLowerBound()); upperField.setValue(parameter.getUpperBound()); setArguments(parameter); setupComponents(); } void getArguments(Parameter parameter, PriorType priorType) { if (priorType.isInitializable && !parameter.isStatistic && !parameter.isNodeHeight) { parameter.setInitial(initialField.getValue()); } parameter.isTruncated = isTruncatedCheck.isSelected(); if (parameter.isTruncated) { parameter.truncationLower = lowerField.getValue(); parameter.truncationUpper = upperField.getValue(); } getArguments(parameter); } abstract void setup(); abstract Distribution getDistribution(); abstract void setArguments(Parameter parameter); abstract void getArguments(Parameter parameter); abstract boolean isInputValid(); private static final String OFFSET = "Offset"; static final PriorOptionsPanel INFINITE_UNIFORM = new PriorOptionsPanel(false) { void setup() { } Distribution getDistribution() { return null; } void setArguments(Parameter parameter) { } void getArguments(Parameter parameter) { } @Override boolean isInputValid() { return getValue(0) > getValue(1); } }; static final PriorOptionsPanel UNIFORM = new PriorOptionsPanel(false) { void setup() { addField("Upper", 1.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); addField("Lower", 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } Distribution getDistribution() { return new UniformDistribution( getValue(1), // lower getValue(0) // upper ); } void setArguments(Parameter parameter) { super.setFieldRange(getField(0), parameter.isNonNegative, parameter.isZeroOne, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); super.setFieldRange(getField(1), parameter.isNonNegative, parameter.isZeroOne, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); getField(0).setValue(parameter.uniformUpper); getField(1).setValue(parameter.uniformLower); getField(0).setLabel(parameter.getName() + " " + getArguName(0).toLowerCase()); getField(1).setLabel(parameter.getName() + " " + getArguName(1).toLowerCase()); } void getArguments(Parameter parameter) { parameter.isTruncated = false; parameter.uniformUpper = getValue(0); parameter.uniformLower = getValue(1); } @Override boolean isInputValid() { return getValue(0) > getValue(1); } }; static final PriorOptionsPanel EXPONENTIAL = new PriorOptionsPanel(true) { void setup() { addField("Mean", 1.0, 0.0, false, Double.POSITIVE_INFINITY, true); addField(OFFSET, 0.0, 0.0, true, Double.POSITIVE_INFINITY, true); } public Distribution getDistribution() { return new OffsetPositiveDistribution( new ExponentialDistribution(1.0 / getValue(0)), getValue(1)); } public void setArguments(Parameter parameter) { setFieldRange(getField(0), true, parameter.isZeroOne); getField(0).setValue(parameter.mean != 0.0 ? parameter.mean : 1.0); getField(1).setValue(parameter.offset); getField(0).setLabel(parameter.getName() + " " + getArguName(0).toLowerCase()); getField(1).setLabel(parameter.getName() + " " + getArguName(1).toLowerCase()); } public void getArguments(Parameter parameter) { parameter.mean = getValue(0); parameter.offset = getValue(1); } @Override boolean isInputValid() { return true; } }; static final PriorOptionsPanel LAPLACE = new PriorOptionsPanel(true) { void setup() { addField("Mean", 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); addField("Scale", 1.0, 0.0, false, Double.POSITIVE_INFINITY, true); } public Distribution getDistribution() { return new LaplaceDistribution(getValue(0), getValue(1)); } public void setArguments(Parameter parameter) { getField(0).setValue(parameter.mean); setFieldRange(getField(0), true, false); getField(1).setValue(parameter.scale); getField(0).setLabel(parameter.getName() + " " + getArguName(0).toLowerCase()); getField(1).setLabel(parameter.getName() + " " + getArguName(1).toLowerCase()); } public void getArguments(Parameter parameter) { parameter.mean = getValue(0); parameter.scale = getValue(1); } @Override boolean isInputValid() { return true; } }; static final PriorOptionsPanel NORMAL = new PriorOptionsPanel(true) { void setup() { addField("Mean", 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); addField("Stdev", 1.0, 0.0, false, Double.POSITIVE_INFINITY, true); } public Distribution getDistribution() { return new NormalDistribution(getValue(0), getValue(1)); } public void setArguments(Parameter parameter) { getField(0).setValue(parameter.mean); getField(1).setValue(parameter.stdev); getField(0).setLabel(parameter.getName() + " " + getArguName(0).toLowerCase()); getField(1).setLabel(parameter.getName() + " " + getArguName(1).toLowerCase()); } public void getArguments(Parameter parameter) { parameter.mean = getValue(0); parameter.stdev = getValue(1); } @Override boolean isInputValid() { return true; } }; static final PriorOptionsPanel LOG_NORMAL = new PriorOptionsPanel(true) { private JCheckBox meanInRealSpaceCheck; void setup() { meanInRealSpaceCheck = new JCheckBox(); if (meanInRealSpaceCheck.isSelected()) { addField("Mean", 0.01, 0.0, false, Double.POSITIVE_INFINITY, true); } else { addField("Log(Mean)", 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } addField("Log(Stdev)", 1.0, 0.0, Double.POSITIVE_INFINITY); addField(OFFSET, 0.0, 0.0, Double.POSITIVE_INFINITY); addCheckBox("Mean In Real Space", meanInRealSpaceCheck); meanInRealSpaceCheck.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent ev) { if (meanInRealSpaceCheck.isSelected()) { replaceFieldName(0, "Mean"); if (getValue(0) <= 0) { getField(0).setValue(0.01); } getField(0).setRange(0.0, Double.POSITIVE_INFINITY); } else { replaceFieldName(0, "Log(Mean)"); getField(0).setRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } for (Listener listener : listeners) { listener.optionsPanelChanged(); } } }); } public Distribution getDistribution() { double mean = getValue(0); if (meanInRealSpaceCheck.isSelected()) { if (mean <= 0) { throw new IllegalArgumentException("meanInRealSpace works only for a positive mean"); } mean = Math.log(getValue(0)) - 0.5 * getValue(1) * getValue(1); } return new OffsetPositiveDistribution( new LogNormalDistribution(mean, getValue(1)), getValue(2)); } public void setArguments(Parameter parameter) { getField(0).setValue(parameter.mean); getField(1).setValue(parameter.stdev); getField(2).setValue(parameter.offset); meanInRealSpaceCheck.setSelected(parameter.isMeanInRealSpace()); getField(0).setLabel(parameter.getName() + " " + getArguName(0).toLowerCase()); getField(1).setLabel(parameter.getName() + " " + getArguName(1).toLowerCase()); getField(2).setLabel(parameter.getName() + " " + getArguName(2).toLowerCase()); } public void getArguments(Parameter parameter) { parameter.mean = getValue(0); parameter.stdev = getValue(1); parameter.offset = getValue(2); parameter.setMeanInRealSpace(meanInRealSpaceCheck.isSelected()); } @Override boolean isInputValid() { return true; } }; static final PriorOptionsPanel GAMMA = new PriorOptionsPanel(true) { void setup() { addField("Shape", 1.0, 0.0, false, Double.POSITIVE_INFINITY, true); addField("Scale", 1.0, 0.0, false, Double.POSITIVE_INFINITY, true); addField(OFFSET, 0.0, 0.0, Double.POSITIVE_INFINITY); } public Distribution getDistribution() { return new OffsetPositiveDistribution( new GammaDistribution(getValue(0), getValue(1)), getValue(2)); } public void setArguments(Parameter parameter) { getField(0).setValue(parameter.shape); getField(1).setValue(parameter.scale); getField(2).setValue(parameter.offset); getField(0).setLabel(parameter.getName() + " " + getArguName(0).toLowerCase()); getField(1).setLabel(parameter.getName() + " " + getArguName(1).toLowerCase()); getField(2).setLabel(parameter.getName() + " " + getArguName(2).toLowerCase()); } public void getArguments(Parameter parameter) { parameter.shape = getValue(0); parameter.scale = getValue(1); parameter.offset = getValue(2); } @Override boolean isInputValid() { return true; } }; static final PriorOptionsPanel INVERSE_GAMMA = new PriorOptionsPanel(true) { void setup() { addField("Shape", 1.0, 0.0, false, Double.POSITIVE_INFINITY, true); addField("Scale", 1.0, 0.0, false, Double.POSITIVE_INFINITY, true); addField(OFFSET, 0.0, 0.0, Double.POSITIVE_INFINITY); } public Distribution getDistribution() { return new OffsetPositiveDistribution( new InverseGammaDistribution(getValue(0), getValue(1)), getValue(2)); } public void setArguments(Parameter parameter) { getField(0).setValue(parameter.shape); getField(1).setValue(parameter.scale); getField(2).setValue(parameter.offset); getField(0).setLabel(parameter.getName() + " " + getArguName(0).toLowerCase()); getField(1).setLabel(parameter.getName() + " " + getArguName(1).toLowerCase()); getField(2).setLabel(parameter.getName() + " " + getArguName(2).toLowerCase()); } public void getArguments(Parameter parameter) { parameter.shape = getValue(0); parameter.scale = getValue(1); parameter.offset = getValue(2); } @Override boolean isInputValid() { return true; } }; // class TruncatedNormalOptionsPanel extends PriorOptionsPanel { // // public TruncatedNormalOptionsPanel() { // // addField("Mean", 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); // addField("Stdev", 1.0, 0.0, Parameter.UNIFORM_MAX_BOUND); // addField("Lower", 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); // addField("Upper", 1.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); // } // // public Distribution getDistribution() { // return new TruncatedNormalDistribution(getValue(0), getValue(1), getValue(2), getValue(3)); // } // // public void setParameterPrior(Parameter parameter) { // parameter.mean = getValue(0); // parameter.stdev = getValue(1); // parameter.isTruncated = true; // parameter.truncationLower = getValue(2); // parameter.truncationUpper = getValue(3); // } // } static final PriorOptionsPanel BETA = new PriorOptionsPanel(true) { void setup() { addField("Shape", 1.0, 0.0, false, Double.POSITIVE_INFINITY, true); addField("ShapeB", 1.0, 0.0, false, Double.POSITIVE_INFINITY, true); addField(OFFSET, 0.0, 0.0, Double.POSITIVE_INFINITY); } public Distribution getDistribution() { return new OffsetPositiveDistribution( new BetaDistribution(getValue(0), getValue(1)), getValue(2)); } public void setArguments(Parameter parameter) { getField(0).setValue(parameter.shape); getField(1).setValue(parameter.shapeB); getField(2).setValue(parameter.offset); getField(0).setLabel(parameter.getName() + " " + getArguName(0).toLowerCase()); getField(1).setLabel(parameter.getName() + " " + getArguName(1).toLowerCase()); getField(2).setLabel(parameter.getName() + " " + getArguName(2).toLowerCase()); } public void getArguments(Parameter parameter) { parameter.shape = getValue(0); parameter.shapeB = getValue(1); parameter.offset = getValue(2); } @Override boolean isInputValid() { return true; } }; static final PriorOptionsPanel CTMC_RATE_REFERENCE = new PriorOptionsPanel(false) { void setup() { } public Distribution getDistribution() { return null; } public void setArguments(Parameter parameter) { } public void getArguments(Parameter parameter) { } @Override boolean isInputValid() { return true; } }; static final PriorOptionsPanel ONE_OVER_X = new PriorOptionsPanel(false) { void setup() { } public Distribution getDistribution() { return null; } public void setArguments(Parameter parameter) { } public void getArguments(Parameter parameter) { } @Override boolean isInputValid() { return true; } }; }