package net.sf.openrocket.gui.dialogs.optimization;
+import java.awt.Component;
import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.ServiceLoader;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSpinner;
+import javax.swing.JTable;
+import javax.swing.JToggleButton;
+import javax.swing.ListSelectionModel;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableColumnModel;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.SpinnerEditor;
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+import net.sf.openrocket.gui.components.CsvOptionPanel;
+import net.sf.openrocket.gui.components.DescriptionArea;
+import net.sf.openrocket.gui.components.DoubleCellEditor;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
+import net.sf.openrocket.gui.components.UnitCellEditor;
+import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.scalefigure.RocketFigure;
+import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.general.Point;
import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
-import net.sf.openrocket.optimization.services.OptimizableParameterService;
-import net.sf.openrocket.optimization.services.SimulationModifierService;
+import net.sf.openrocket.optimization.rocketoptimization.domains.IdentitySimulationDomain;
+import net.sf.openrocket.optimization.rocketoptimization.domains.StabilityDomain;
+import net.sf.openrocket.optimization.rocketoptimization.goals.MaximizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.goals.MinimizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.goals.ValueSeekGoal;
+import net.sf.openrocket.optimization.services.OptimizationServiceHelper;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.CaliberUnit;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.unit.Value;
import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.Chars;
+import net.sf.openrocket.util.FileHelper;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Named;
+import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.TextUtil;
+import com.itextpdf.text.Font;
+
+/**
+ * General rocket optimization dialog.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
public class GeneralOptimizationDialog extends JDialog {
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+ private static final Collator collator = Collator.getInstance();
+
+
+ private static final String GOAL_MAXIMIZE = trans.get("goal.maximize");
+ private static final String GOAL_MINIMIZE = trans.get("goal.minimize");
+ private static final String GOAL_SEEK = trans.get("goal.seek");
+
+ private static final String START_TEXT = trans.get("btn.start");
+ private static final String STOP_TEXT = trans.get("btn.stop");
+
+
+
private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>();
private final Map<Object, List<SimulationModifier>> simulationModifiers =
new HashMap<Object, List<SimulationModifier>>();
- private static final Translator trans = Application.getTranslator();
+
+ private final OpenRocketDocument baseDocument;
+ private OpenRocketDocument documentCopy;
+
- private final OpenRocketDocument document;
+ private final JButton addButton;
+ private final JButton removeButton;
+ private final JButton removeAllButton;
+
+ private final ParameterSelectionTableModel selectedModifierTableModel;
+ private final JTable selectedModifierTable;
+ private final DescriptionArea selectedModifierDescription;
+ private final SimulationModifierTree availableModifierTree;
+
+ private final JComboBox simulationSelectionCombo;
+ private final JComboBox optimizationParameterCombo;
+
+ private final JComboBox optimizationGoalCombo;
+ private final JSpinner optimizationGoalSpinner;
+ private final UnitSelector optimizationGoalUnitSelector;
+ private final DoubleModel optimizationSeekValue;
+
+ private DoubleModel minimumStability;
+ private DoubleModel maximumStability;
+ private final JCheckBox minimumStabilitySelected;
+ private final JSpinner minimumStabilitySpinner;
+ private final UnitSelector minimumStabilityUnitSelector;
+ private final JCheckBox maximumStabilitySelected;
+ private final JSpinner maximumStabilitySpinner;
+ private final UnitSelector maximumStabilityUnitSelector;
+
+ private final JLabel bestValueLabel;
+ private final JLabel stepCountLabel;
+ private final JLabel evaluationCountLabel;
+ private final JLabel stepSizeLabel;
+
+ private final RocketFigure figure;
+ private final JToggleButton startButton;
+ private final JButton plotButton;
+ private final JButton saveButton;
+
+ private final JButton applyButton;
+ private final JButton resetButton;
+ private final JButton closeButton;
+
+ private final List<SimulationModifier> selectedModifiers = new ArrayList<SimulationModifier>();
+
+ /** List of components to disable while optimization is running */
+ private final List<JComponent> disableComponents = new ArrayList<JComponent>();
+
+ /** Whether optimization is currently running or not */
+ private boolean running = false;
+ /** The optimization worker that is running */
+ private OptimizationWorker worker = null;
+
+
+ private double bestValue = Double.NaN;
+ private Unit bestValueUnit = Unit.NOUNIT2;
+ private int stepCount = 0;
+ private int evaluationCount = 0;
+ private double stepSize = 0;
+
+ private final Map<Point, FunctionEvaluationData> evaluationHistory = new LinkedHashMap<Point, FunctionEvaluationData>();
+ private final List<Point> optimizationPath = new LinkedList<Point>();
+
+
+ private boolean updating = false;
+
+ /**
+ * Sole constructor.
+ *
+ * @param document the document
+ * @param parent the parent window
+ */
public GeneralOptimizationDialog(OpenRocketDocument document, Window parent) {
- this.document = document;
+ super(parent, trans.get("title"));
+
+ this.baseDocument = document;
+ this.documentCopy = document.copy();
loadOptimizationParameters();
loadSimulationModifiers();
+
+ JPanel sub;
+ JLabel label;
+ JScrollPane scroll;
+ String tip;
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+
+ ChangeListener clearHistoryChangeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ clearHistory();
+ }
+ };
+ ActionListener clearHistoryActionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ clearHistory();
+ }
+ };
+
+
+
+ //// Selected modifiers table
+
+ selectedModifierTableModel = new ParameterSelectionTableModel();
+ selectedModifierTable = new JTable(selectedModifierTableModel);
+ selectedModifierTable.setDefaultRenderer(Double.class, new DoubleCellRenderer());
+ selectedModifierTable.setRowSelectionAllowed(true);
+ selectedModifierTable.setColumnSelectionAllowed(false);
+ selectedModifierTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ // Make sure spinner editor fits into the cell height
+ selectedModifierTable.setRowHeight(new JSpinner().getPreferredSize().height - 4);
+
+ selectedModifierTable.setDefaultEditor(Double.class, new DoubleCellEditor());
+ selectedModifierTable.setDefaultEditor(Unit.class, new UnitCellEditor() {
+ @Override
+ protected UnitGroup getUnitGroup(Unit value, int row, int column) {
+ return selectedModifiers.get(row).getUnitGroup();
+ }
+ });
+
+ disableComponents.add(selectedModifierTable);
+
+ selectedModifierTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ updateComponents();
+ }
+ });
+
+ // Set column widths
+ TableColumnModel columnModel = selectedModifierTable.getColumnModel();
+ columnModel.getColumn(0).setPreferredWidth(150);
+ columnModel.getColumn(1).setPreferredWidth(40);
+ columnModel.getColumn(2).setPreferredWidth(40);
+ columnModel.getColumn(3).setPreferredWidth(40);
+
+ scroll = new JScrollPane(selectedModifierTable);
+
+ label = new StyledLabel(trans.get("lbl.paramsToOptimize"), Style.BOLD);
+ disableComponents.add(label);
+ panel.add(label, "split 3, flowy");
+ panel.add(scroll, "wmin 300lp, height 200lp, grow");
+ selectedModifierDescription = new DescriptionArea(2, -3);
+ disableComponents.add(selectedModifierDescription);
+ panel.add(selectedModifierDescription, "growx");
+
+
+
+ //// Add/remove buttons
+ sub = new JPanel(new MigLayout("fill"));
+
+ addButton = new JButton(Chars.LEFT_ARROW + " " + trans.get("btn.add") + " ");
+ addButton.setToolTipText(trans.get("btn.add.ttip"));
+ addButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SimulationModifier mod = getSelectedAvailableModifier();
+ if (mod != null) {
+ addModifier(mod);
+ clearHistory();
+ } else {
+ log.error("Attempting to add simulation modifier when none is selected");
+ }
+ }
+ });
+ disableComponents.add(addButton);
+ sub.add(addButton, "wrap para, sg button");
+
+ removeButton = new JButton(" " + trans.get("btn.remove") + " " + Chars.RIGHT_ARROW);
+ removeButton.setToolTipText(trans.get("btn.remove.ttip"));
+ removeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SimulationModifier mod = getSelectedModifier();
+ if (mod == null) {
+ log.error("Attempting to remove simulation modifier when none is selected");
+ return;
+ }
+ removeModifier(mod);
+ clearHistory();
+ }
+ });
+ disableComponents.add(removeButton);
+ sub.add(removeButton, "wrap para*2, sg button");
+
+ removeAllButton = new JButton(trans.get("btn.removeAll"));
+ removeAllButton.setToolTipText(trans.get("btn.removeAll.ttip"));
+ removeAllButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Removing all selected modifiers");
+ selectedModifiers.clear();
+ selectedModifierTableModel.fireTableDataChanged();
+ availableModifierTree.repaint();
+ clearHistory();
+ }
+ });
+ disableComponents.add(removeAllButton);
+ sub.add(removeAllButton, "wrap para, sg button");
+
+ panel.add(sub);
+
+
+
+ //// Available modifier tree
+ availableModifierTree = new SimulationModifierTree(documentCopy.getRocket(), simulationModifiers, selectedModifiers);
+ availableModifierTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ updateComponents();
+ }
+ });
+
+ // Handle double-click
+ availableModifierTree.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ SimulationModifier mod = getSelectedAvailableModifier();
+ if (mod != null) {
+ addModifier(mod);
+ clearHistory();
+ } else {
+ log.user("Double-clicked non-available option");
+ }
+ }
+ }
+ });
+
+ disableComponents.add(availableModifierTree);
+ scroll = new JScrollPane(availableModifierTree);
+ label = new StyledLabel(trans.get("lbl.availableParams"), Style.BOLD);
+ disableComponents.add(label);
+ panel.add(label, "split 2, flowy");
+ panel.add(scroll, "width 300lp, height 200lp, grow, wrap para*2");
+
+
+
+
+ //// Optimization options sub-panel
+
+ sub = new JPanel(new MigLayout("fill"));
+ TitledBorder border = BorderFactory.createTitledBorder(trans.get("lbl.optimizationOpts"));
+ GUIUtil.changeFontStyle(border, Font.BOLD);
+ sub.setBorder(border);
+ disableComponents.add(sub);
+
+
+ //// Simulation to optimize
+
+ label = new JLabel(trans.get("lbl.optimizeSim"));
+ tip = trans.get("lbl.optimizeSim.ttip");
+ label.setToolTipText(tip);
+ disableComponents.add(label);
+ sub.add(label, "");
+
+ simulationSelectionCombo = new JComboBox();
+ simulationSelectionCombo.setToolTipText(tip);
+ populateSimulations();
+ simulationSelectionCombo.addActionListener(clearHistoryActionListener);
+ disableComponents.add(simulationSelectionCombo);
+ sub.add(simulationSelectionCombo, "growx, wrap unrel");
+
+
+
+ //// Value to optimize
+ label = new JLabel(trans.get("lbl.optimizeValue"));
+ tip = trans.get("lbl.optimizeValue.ttip");
+ label.setToolTipText(tip);
+ disableComponents.add(label);
+ sub.add(label, "");
+
+ optimizationParameterCombo = new JComboBox();
+ optimizationParameterCombo.setToolTipText(tip);
+ populateParameters();
+ optimizationParameterCombo.addActionListener(clearHistoryActionListener);
+ disableComponents.add(optimizationParameterCombo);
+ sub.add(optimizationParameterCombo, "growx, wrap unrel");
+
+
+
+ //// Optimization goal
+ label = new JLabel(trans.get("lbl.optimizeGoal"));
+ tip = trans.get("lbl.optimizeGoal");
+ label.setToolTipText(tip);
+ disableComponents.add(label);
+ sub.add(label, "");
+
+ optimizationGoalCombo = new JComboBox(new String[] { GOAL_MAXIMIZE, GOAL_MINIMIZE, GOAL_SEEK });
+ optimizationGoalCombo.setToolTipText(tip);
+ optimizationGoalCombo.setEditable(false);
+ optimizationGoalCombo.addActionListener(clearHistoryActionListener);
+ disableComponents.add(optimizationGoalCombo);
+ sub.add(optimizationGoalCombo, "growx");
+
+
+ //// Optimization custom value
+ optimizationSeekValue = new DoubleModel(0, UnitGroup.UNITS_NONE);
+ optimizationSeekValue.addChangeListener(clearHistoryChangeListener);
+
+ optimizationGoalSpinner = new JSpinner(optimizationSeekValue.getSpinnerModel());
+ tip = trans.get("lbl.optimizeGoalValue.ttip");
+ optimizationGoalSpinner.setToolTipText(tip);
+ optimizationGoalSpinner.setEditor(new SpinnerEditor(optimizationGoalSpinner));
+ disableComponents.add(optimizationGoalSpinner);
+ sub.add(optimizationGoalSpinner, "width 30lp");
+
+ optimizationGoalUnitSelector = new UnitSelector(optimizationSeekValue);
+ optimizationGoalUnitSelector.setToolTipText(tip);
+ disableComponents.add(optimizationGoalUnitSelector);
+ sub.add(optimizationGoalUnitSelector, "width 20lp, wrap unrel");
+
+
+ panel.add(sub, "grow");
+
+
+
+ //// Required stability sub-panel
+
+ sub = new JPanel(new MigLayout("fill"));
+ border = BorderFactory.createTitledBorder(trans.get("lbl.requireStability"));
+ GUIUtil.changeFontStyle(border, Font.BOLD);
+ sub.setBorder(border);
+ disableComponents.add(sub);
+
+
+
+ double ref = CaliberUnit.calculateCaliber(baseDocument.getRocket());
+ minimumStability = new DoubleModel(ref, UnitGroup.stabilityUnits(ref));
+ maximumStability = new DoubleModel(5 * ref, UnitGroup.stabilityUnits(ref));
+ minimumStability.addChangeListener(clearHistoryChangeListener);
+ maximumStability.addChangeListener(clearHistoryChangeListener);
+
+
+ //// Minimum stability
+ tip = trans.get("lbl.requireMinStability.ttip");
+ minimumStabilitySelected = new JCheckBox(trans.get("lbl.requireMinStability"));
+ minimumStabilitySelected.setSelected(true);
+ minimumStabilitySelected.setToolTipText(tip);
+ minimumStabilitySelected.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateComponents();
+ }
+ });
+ disableComponents.add(minimumStabilitySelected);
+ sub.add(minimumStabilitySelected);
+
+ minimumStabilitySpinner = new JSpinner(minimumStability.getSpinnerModel());
+ minimumStabilitySpinner.setToolTipText(tip);
+ minimumStabilitySpinner.setEditor(new SpinnerEditor(minimumStabilitySpinner));
+ disableComponents.add(minimumStabilitySpinner);
+ sub.add(minimumStabilitySpinner, "growx");
+
+ minimumStabilityUnitSelector = new UnitSelector(minimumStability);
+ minimumStabilityUnitSelector.setToolTipText(tip);
+ disableComponents.add(minimumStabilityUnitSelector);
+ sub.add(minimumStabilityUnitSelector, "growx, wrap unrel");
+
+
+ //// Maximum stability
+ tip = trans.get("lbl.requireMaxStability.ttip");
+ maximumStabilitySelected = new JCheckBox(trans.get("lbl.requireMaxStability"));
+ maximumStabilitySelected.setToolTipText(tip);
+ maximumStabilitySelected.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateComponents();
+ }
+ });
+ disableComponents.add(maximumStabilitySelected);
+ sub.add(maximumStabilitySelected);
+
+ maximumStabilitySpinner = new JSpinner(maximumStability.getSpinnerModel());
+ maximumStabilitySpinner.setToolTipText(tip);
+ maximumStabilitySpinner.setEditor(new SpinnerEditor(maximumStabilitySpinner));
+ disableComponents.add(maximumStabilitySpinner);
+ sub.add(maximumStabilitySpinner, "growx");
+
+ maximumStabilityUnitSelector = new UnitSelector(maximumStability);
+ maximumStabilityUnitSelector.setToolTipText(tip);
+ disableComponents.add(maximumStabilityUnitSelector);
+ sub.add(maximumStabilityUnitSelector, "growx, wrap para");
+
+
+
+ // DescriptionArea desc = new DescriptionArea("Stability requirements are verified during each time step of the simulation.",
+ // 2, -2, false);
+ // desc.setViewportBorder(null);
+ // disableComponents.add(desc);
+ // sub.add(desc, "span, growx");
+
+
+ panel.add(sub, "span 2, grow, wrap para*2");
+
+
+
+
+ //// Rocket figure
+ figure = new RocketFigure(getSelectedSimulation().getConfiguration());
+ figure.setBorderPixels(1, 1);
+ ScaleScrollPane figureScrollPane = new ScaleScrollPane(figure);
+ figureScrollPane.setFitting(true);
+ panel.add(figureScrollPane, "span, split, height 200lp, grow");
+
+
+ sub = new JPanel(new MigLayout("fill"));
+
+
+ label = new JLabel(trans.get("status.bestValue"));
+ tip = trans.get("status.bestValue.ttip");
+ label.setToolTipText(tip);
+ sub.add(label, "gapright unrel");
+
+ bestValueLabel = new JLabel();
+ bestValueLabel.setToolTipText(tip);
+ sub.add(bestValueLabel, "wmin 60lp, wrap rel");
+
+
+ label = new JLabel(trans.get("status.stepCount"));
+ tip = trans.get("status.stepCount.ttip");
+ label.setToolTipText(tip);
+ sub.add(label, "gapright unrel");
+
+ stepCountLabel = new JLabel();
+ stepCountLabel.setToolTipText(tip);
+ sub.add(stepCountLabel, "wrap rel");
+
+
+ label = new JLabel(trans.get("status.evalCount"));
+ tip = trans.get("status.evalCount");
+ label.setToolTipText(tip);
+ sub.add(label, "gapright unrel");
+
+ evaluationCountLabel = new JLabel();
+ evaluationCountLabel.setToolTipText(tip);
+ sub.add(evaluationCountLabel, "wrap rel");
+
+
+ label = new JLabel(trans.get("status.stepSize"));
+ tip = trans.get("status.stepSize.ttip");
+ label.setToolTipText(tip);
+ sub.add(label, "gapright unrel");
+
+ stepSizeLabel = new JLabel();
+ stepSizeLabel.setToolTipText(tip);
+ sub.add(stepSizeLabel, "wrap para");
+
+
+ //// Start/Stop button
+
+ startButton = new JToggleButton(START_TEXT);
+ startButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (updating) {
+ log.debug("Updating, ignoring event");
+ return;
+ }
+ if (running) {
+ log.user("Stopping optimization");
+ stopOptimization();
+ } else {
+ log.user("Starting optimization");
+ startOptimization();
+ }
+ }
+ });
+ sub.add(startButton, "span, growx, wrap para*2");
+
+
+ plotButton = new JButton(trans.get("btn.plotPath"));
+ plotButton.setToolTipText(trans.get("btn.plotPath.ttip"));
+ plotButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Plotting optimization path, dimensionality=" + selectedModifiers.size());
+ OptimizationPlotDialog dialog = new OptimizationPlotDialog(optimizationPath, evaluationHistory,
+ selectedModifiers, getSelectedParameter(),
+ UnitGroup.stabilityUnits(getSelectedSimulation().getRocket()),
+ GeneralOptimizationDialog.this);
+ dialog.setVisible(true);
+ }
+ });
+ disableComponents.add(plotButton);
+ sub.add(plotButton, "span, growx, wrap");
+
+
+ saveButton = new JButton(trans.get("btn.save"));
+ saveButton.setToolTipText(trans.get("btn.save.ttip"));
+ saveButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("User selected save path");
+ savePath();
+ }
+ });
+ disableComponents.add(saveButton);
+ sub.add(saveButton, "span, growx");
+
+
+
+ panel.add(sub, "wrap para*2");
+
+
+
+
+ //// Bottom buttons
+
+ applyButton = new JButton(trans.get("btn.apply"));
+ applyButton.setToolTipText(trans.get("btn.apply.ttip"));
+ applyButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Applying optimization changes");
+ applyDesign();
+ }
+ });
+ disableComponents.add(applyButton);
+ panel.add(applyButton, "span, split, gapright para, right");
+
+ resetButton = new JButton(trans.get("btn.reset"));
+ resetButton.setToolTipText(trans.get("btn.reset.ttip"));
+ resetButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Resetting optimization design");
+ resetDesign();
+ }
+ });
+ disableComponents.add(resetButton);
+ panel.add(resetButton, "gapright para, right");
+
+ closeButton = new JButton(trans.get("btn.close"));
+ closeButton.setToolTipText(trans.get("btn.close.ttip"));
+ closeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Closing optimization dialog");
+ stopOptimization();
+ GeneralOptimizationDialog.this.dispose();
+ }
+ });
+ panel.add(closeButton, "right");
+
+
+ this.add(panel);
+ clearHistory();
+ updateComponents();
+ GUIUtil.setDisposableDialogOptions(this, null);
}
- private void loadOptimizationParameters() {
- ServiceLoader<OptimizableParameterService> loader =
- ServiceLoader.load(OptimizableParameterService.class);
+ private void startOptimization() {
+ if (running) {
+ log.info("Optimization already running");
+ return;
+ }
+
+
+ if (selectedModifiers.isEmpty()) {
+ JOptionPane.showMessageDialog(this, trans.get("error.selectParams.text"),
+ trans.get("error.selectParams.title"), JOptionPane.ERROR_MESSAGE);
+ updating = true;
+ startButton.setSelected(false);
+ startButton.setText(START_TEXT);
+ updating = false;
+ return;
+ }
+
+
+ running = true;
+
+ // Update the button status
+ updating = true;
+ startButton.setSelected(true);
+ startButton.setText(STOP_TEXT);
+ updating = false;
+
+
+ // Create a copy of the simulation (we're going to modify the original in the current thread)
+ Simulation simulation = getSelectedSimulation();
+ Rocket rocketCopy = simulation.getRocket().copyWithOriginalID();
+ simulation = simulation.duplicateSimulation(rocketCopy);
+
+ OptimizableParameter parameter = getSelectedParameter();
+
+ OptimizationGoal goal;
+ String value = (String) optimizationGoalCombo.getSelectedItem();
+ if (GOAL_MAXIMIZE.equals(value)) {
+ goal = new MaximizationGoal();
+ } else if (GOAL_MINIMIZE.equals(value)) {
+ goal = new MinimizationGoal();
+ } else if (GOAL_SEEK.equals(value)) {
+ goal = new ValueSeekGoal(optimizationSeekValue.getValue());
+ } else {
+ throw new BugException("optimizationGoalCombo had invalid value: " + value);
+ }
+
+ SimulationDomain domain;
+ if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
+ double min, max;
+ boolean minAbsolute, maxAbsolute;
+
+ /*
+ * Make minAbsolute/maxAbsolute consistent with each other to produce reasonable
+ * result in plot tool tips. Yes, this is a bit ugly.
+ */
+
+ // Min stability
+ Unit unit = minimumStability.getCurrentUnit();
+ if (unit instanceof CaliberUnit) {
+ min = unit.toUnit(minimumStability.getValue());
+ minAbsolute = false;
+ } else {
+ min = minimumStability.getValue();
+ minAbsolute = true;
+ }
+
+ // Max stability
+ unit = maximumStability.getCurrentUnit();
+ if (unit instanceof CaliberUnit) {
+ max = unit.toUnit(maximumStability.getValue());
+ maxAbsolute = false;
+ } else {
+ max = maximumStability.getValue();
+ maxAbsolute = true;
+ }
+
+
+ if (!minimumStabilitySelected.isSelected()) {
+ min = Double.NaN;
+ minAbsolute = maxAbsolute;
+ }
+ if (!maximumStabilitySelected.isSelected()) {
+ max = Double.NaN;
+ maxAbsolute = minAbsolute;
+ }
+
+ domain = new StabilityDomain(min, minAbsolute, max, maxAbsolute);
+ } else {
+ domain = new IdentitySimulationDomain();
+ }
+
+ SimulationModifier[] modifiers = selectedModifiers.toArray(new SimulationModifier[0]);
- for (OptimizableParameterService g : loader) {
- optimizationParameters.addAll(g.getParameters(document));
+ // Create and start the background worker
+ worker = new OptimizationWorker(simulation, parameter, goal, domain, modifiers) {
+ @Override
+ protected void done(OptimizationException exception) {
+ log.info("Optimization finished, exception=" + exception, exception);
+
+ if (exception != null) {
+ JOptionPane.showMessageDialog(GeneralOptimizationDialog.this,
+ new Object[] {
+ trans.get("error.optimizationFailure.text"),
+ exception.getLocalizedMessage()
+ }, trans.get("error.optimizationFailure.title"), JOptionPane.ERROR_MESSAGE);
+ }
+
+ worker = null;
+ stopOptimization();
+ }
+
+ @Override
+ protected void functionEvaluated(List<FunctionEvaluationData> data) {
+ for (FunctionEvaluationData d : data) {
+ evaluationHistory.put(d.getPoint(), d);
+ evaluationCount++;
+ }
+ updateCounters();
+ }
+
+ @Override
+ protected void optimizationStepTaken(List<OptimizationStepData> data) {
+
+ // Add starting point to the path
+ if (optimizationPath.isEmpty()) {
+ optimizationPath.add(data.get(0).getOldPoint());
+ }
+
+ // Add other points to the path
+ for (OptimizationStepData d : data) {
+ optimizationPath.add(d.getNewPoint());
+ }
+
+ // Get function value from the latest point
+ OptimizationStepData latest = data.get(data.size() - 1);
+ Point newPoint = latest.getNewPoint();
+
+ FunctionEvaluationData pointValue = evaluationHistory.get(newPoint);
+ if (pointValue != null) {
+ bestValue = pointValue.getParameterValue().getValue();
+ } else {
+ log.error("History does not contain point " + newPoint);
+ bestValue = Double.NaN;
+ }
+
+ // Update the simulation
+ Simulation sim = getSelectedSimulation();
+ for (int i = 0; i < newPoint.dim(); i++) {
+ try {
+ selectedModifiers.get(i).modify(sim, newPoint.get(i));
+ } catch (OptimizationException e) {
+ throw new BugException("Simulation modifier failed to modify the base simulation " +
+ "modifier=" + selectedModifiers.get(i), e);
+ }
+ }
+ figure.updateFigure();
+
+ // Update other counter data
+ stepCount += data.size();
+ stepSize = latest.getStepSize();
+ updateCounters();
+ }
+ };
+ worker.start();
+
+
+ clearHistory();
+
+ updateComponents();
+ }
+
+ private void stopOptimization() {
+ if (!running) {
+ log.info("Optimization not running");
+ return;
+ }
+
+ if (worker != null && worker.isAlive()) {
+ log.info("Worker still running, interrupting it and setting to null");
+ worker.interrupt();
+ worker = null;
+ return;
+ }
+
+ running = false;
+
+ // Update the button status
+ updating = true;
+ startButton.setSelected(false);
+ startButton.setText(START_TEXT);
+ updating = false;
+
+
+ updateComponents();
+ }
+
+
+
+
+ /**
+ * Reset the current optimization history and values. This does not reset the design.
+ */
+ private void clearHistory() {
+ evaluationHistory.clear();
+ optimizationPath.clear();
+ bestValue = Double.NaN;
+ bestValueUnit = getSelectedParameter().getUnitGroup().getDefaultUnit();
+ stepCount = 0;
+ evaluationCount = 0;
+ stepSize = 0.5;
+ updateCounters();
+ updateComponents();
+ }
+
+
+ private void applyDesign() {
+ // TODO: MEDIUM: Apply also potential changes to simulations
+ Rocket src = getSelectedSimulation().getRocket().copyWithOriginalID();
+ Rocket dest = baseDocument.getRocket();
+ try {
+ baseDocument.startUndo(trans.get("undoText"));
+ dest.freeze();
+
+ // Remove all children
+ while (dest.getChildCount() > 0) {
+ dest.removeChild(0);
+ }
+
+ // Move all children to the destination rocket
+ while (src.getChildCount() > 0) {
+ RocketComponent c = src.getChild(0);
+ src.removeChild(0);
+ dest.addChild(c);
+ }
+
+ } finally {
+ dest.thaw();
+ baseDocument.stopUndo();
+ }
+ }
+
+
+ private void resetDesign() {
+ clearHistory();
+
+ documentCopy = baseDocument.copy();
+
+ loadOptimizationParameters();
+ loadSimulationModifiers();
+
+ // Replace selected modifiers with corresponding new modifiers
+ List<SimulationModifier> newSelected = new ArrayList<SimulationModifier>();
+ for (SimulationModifier original : selectedModifiers) {
+ List<SimulationModifier> newModifiers = simulationModifiers.get(original.getRelatedObject());
+ if (newModifiers != null) {
+ int index = newModifiers.indexOf(original);
+ if (index >= 0) {
+ newSelected.add(newModifiers.get(index));
+ }
+ }
+ }
+ selectedModifiers.clear();
+ selectedModifiers.addAll(newSelected);
+ selectedModifierTableModel.fireTableDataChanged();
+
+ // Update the available modifier tree
+ availableModifierTree.populateTree(documentCopy.getRocket(), simulationModifiers);
+ availableModifierTree.expandComponents();
+
+
+ // Update selectable simulations
+ populateSimulations();
+
+ // Update selectable parameters
+ populateParameters();
+
+ }
+
+
+ private void populateSimulations() {
+ String current = null;
+ Object selection = simulationSelectionCombo.getSelectedItem();
+ if (selection != null) {
+ current = selection.toString();
+ }
+
+
+ List<Named<Simulation>> simulations = new ArrayList<Named<Simulation>>();
+ Rocket rocket = documentCopy.getRocket();
+
+ for (Simulation s : documentCopy.getSimulations()) {
+ String id = s.getConfiguration().getMotorConfigurationID();
+ String name = createSimulationName(s.getName(), rocket.getMotorConfigurationNameOrDescription(id));
+ simulations.add(new Named<Simulation>(s, name));
+ }
+
+ for (String id : rocket.getMotorConfigurationIDs()) {
+ if (id == null) {
+ continue;
+ }
+ Simulation sim = new Simulation(rocket);
+ sim.getConfiguration().setMotorConfigurationID(id);
+ String name = createSimulationName(trans.get("basicSimulationName"), rocket.getMotorConfigurationNameOrDescription(id));
+ simulations.add(new Named<Simulation>(sim, name));
}
+
+ Simulation sim = new Simulation(rocket);
+ sim.getConfiguration().setMotorConfigurationID(null);
+ String name = createSimulationName(trans.get("noSimulationName"), rocket.getMotorConfigurationNameOrDescription(null));
+ simulations.add(new Named<Simulation>(sim, name));
+
+
+ simulationSelectionCombo.setModel(new DefaultComboBoxModel(simulations.toArray()));
+
+ if (current != null) {
+ for (int i = 0; i < simulations.size(); i++) {
+ if (simulations.get(i).toString().equals(current)) {
+ simulationSelectionCombo.setSelectedIndex(i);
+ break;
+ }
+ }
+ }
+ }
+
+
+ private void populateParameters() {
+ String current = null;
+ Object selection = optimizationParameterCombo.getSelectedItem();
+ if (selection != null) {
+ current = selection.toString();
+ } else {
+ // Default to apogee altitude event if it is not the first one in the list
+ current = trans.get("MaximumAltitudeParameter.name");
+ }
+
+ List<Named<OptimizableParameter>> parameters = new ArrayList<Named<OptimizableParameter>>();
+ for (OptimizableParameter p : optimizationParameters) {
+ parameters.add(new Named<OptimizableParameter>(p, p.getName()));
+ }
+
+ optimizationParameterCombo.setModel(new DefaultComboBoxModel(parameters.toArray()));
+
+ for (int i = 0; i < parameters.size(); i++) {
+ if (parameters.get(i).toString().equals(current)) {
+ optimizationParameterCombo.setSelectedIndex(i);
+ break;
+ }
+ }
+ }
+
+ private void updateCounters() {
+ bestValueLabel.setText(bestValueUnit.toStringUnit(bestValue));
+ stepCountLabel.setText("" + stepCount);
+ evaluationCountLabel.setText("" + evaluationCount);
+ stepSizeLabel.setText(UnitGroup.UNITS_RELATIVE.toStringUnit(stepSize));
+ }
+
+
+ private void loadOptimizationParameters() {
+ optimizationParameters.clear();
+ optimizationParameters.addAll(OptimizationServiceHelper.getOptimizableParameters(documentCopy));
+
if (optimizationParameters.isEmpty()) {
throw new BugException("No rocket optimization parameters found, distribution built wrong.");
}
private void loadSimulationModifiers() {
- ServiceLoader<SimulationModifierService> loader = ServiceLoader.load(SimulationModifierService.class);
+ simulationModifiers.clear();
- for (SimulationModifierService g : loader) {
- for (SimulationModifier m : g.getModifiers(document)) {
- Object key = m.getRelatedObject();
- List<SimulationModifier> list = simulationModifiers.get(key);
- if (list == null) {
- list = new ArrayList<SimulationModifier>();
- simulationModifiers.put(key, list);
- }
- list.add(m);
+ for (SimulationModifier m : OptimizationServiceHelper.getSimulationModifiers(documentCopy)) {
+ Object key = m.getRelatedObject();
+ List<SimulationModifier> list = simulationModifiers.get(key);
+ if (list == null) {
+ list = new ArrayList<SimulationModifier>();
+ simulationModifiers.put(key, list);
}
+ list.add(m);
}
for (Object key : simulationModifiers.keySet()) {
}
+
+
+ private void addModifier(SimulationModifier mod) {
+ if (!selectedModifiers.contains(mod)) {
+ log.user(1, "Adding simulation modifier " + mod);
+ selectedModifiers.add(mod);
+ Collections.sort(selectedModifiers, new SimulationModifierComparator());
+ selectedModifierTableModel.fireTableDataChanged();
+ availableModifierTree.repaint();
+ } else {
+ log.user(1, "Attempting to add an already existing simulation modifier " + mod);
+ }
+ }
+
+
+ private void removeModifier(SimulationModifier mod) {
+ log.user(1, "Removing simulation modifier " + mod);
+ selectedModifiers.remove(mod);
+ selectedModifierTableModel.fireTableDataChanged();
+ availableModifierTree.repaint();
+ }
+
+
+
+ /**
+ * Update the enabled status of all components in the dialog.
+ */
+ private void updateComponents() {
+
+ if (updating) {
+ return;
+ }
+
+ updating = true;
+
+
+ // First enable all components if optimization not running
+ if (!running) {
+ for (JComponent c : disableComponents) {
+ c.setEnabled(true);
+ }
+ }
+
+
+ // "Add" button
+ SimulationModifier mod = getSelectedAvailableModifier();
+ if (mod != null && !selectedModifiers.contains(mod)) {
+ addButton.setEnabled(true);
+ } else {
+ addButton.setEnabled(false);
+ }
+
+ // "Remove" button
+ removeButton.setEnabled(selectedModifierTable.getSelectedRow() >= 0);
+
+ // "Remove all" button
+ removeAllButton.setEnabled(!selectedModifiers.isEmpty());
+
+
+ // Optimization goal
+ String selected = (String) optimizationGoalCombo.getSelectedItem();
+ if (GOAL_SEEK.equals(selected)) {
+ optimizationGoalSpinner.setVisible(true);
+ optimizationGoalUnitSelector.setVisible(true);
+ } else {
+ optimizationGoalSpinner.setVisible(false);
+ optimizationGoalUnitSelector.setVisible(false);
+ }
+
+
+ // Minimum/maximum stability options
+ minimumStabilitySpinner.setEnabled(minimumStabilitySelected.isSelected());
+ minimumStabilityUnitSelector.setEnabled(minimumStabilitySelected.isSelected());
+ maximumStabilitySpinner.setEnabled(maximumStabilitySelected.isSelected());
+ maximumStabilityUnitSelector.setEnabled(maximumStabilitySelected.isSelected());
+
+
+ // Plot button (enabled if path exists and dimensionality is 1 or 2)
+ plotButton.setEnabled(!optimizationPath.isEmpty() && (selectedModifiers.size() == 1 || selectedModifiers.size() == 2));
+
+ // Save button (enabled if path exists)
+ saveButton.setEnabled(!optimizationPath.isEmpty());
+
+
+ // Last disable all components if optimization is running
+ if (running) {
+ for (JComponent c : disableComponents) {
+ c.setEnabled(false);
+ }
+ }
+
+
+ // Update description text
+ mod = getSelectedModifier();
+ if (mod != null) {
+ selectedModifierDescription.setText(mod.getDescription());
+ } else {
+ selectedModifierDescription.setText("");
+ }
+
+
+ // Update the figure
+ figure.setConfiguration(getSelectedSimulation().getConfiguration());
+
+ updating = false;
+ }
+
+
+ private void savePath() {
+
+ if (evaluationHistory.isEmpty()) {
+ throw new BugException("evaluation history is empty");
+ }
+
+ CsvOptionPanel csvOptions = new CsvOptionPanel(GeneralOptimizationDialog.class,
+ trans.get("export.header"), trans.get("export.header.ttip"));
+
+
+ JFileChooser chooser = new JFileChooser();
+ chooser.setFileFilter(FileHelper.CSV_FILE_FILTER);
+ chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
+ chooser.setAccessory(csvOptions);
+
+ if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
+ return;
+
+ File file = chooser.getSelectedFile();
+ if (file == null)
+ return;
+
+ file = FileHelper.ensureExtension(file, "csv");
+ if (!FileHelper.confirmWrite(file, this)) {
+ return;
+ }
+
+ String fieldSeparator = csvOptions.getFieldSeparator();
+ String commentCharacter = csvOptions.getCommentCharacter();
+ boolean includeHeader = csvOptions.getSelectionOption(0);
+ csvOptions.storePreferences();
+
+ log.info("Saving optimization path to " + file + ", fieldSeparator=" + fieldSeparator +
+ ", commentCharacter=" + commentCharacter + ", includeHeader=" + includeHeader);
+
+ try {
+ Writer writer = new BufferedWriter(new FileWriter(file));
+
+ // Write header
+ if (includeHeader) {
+ FunctionEvaluationData data = evaluationHistory.values().iterator().next();
+
+ writer.write(commentCharacter);
+ for (SimulationModifier mod : selectedModifiers) {
+ writer.write(mod.getRelatedObject().toString() + ": " + mod.getName() + " / " +
+ mod.getUnitGroup().getDefaultUnit().getUnit());
+ writer.write(fieldSeparator);
+ }
+ if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
+ writer.write(trans.get("export.stability") + " / " + data.getDomainReference().getUnit().getUnit());
+ writer.write(fieldSeparator);
+ }
+ writer.write(getSelectedParameter().getName() + " / " +
+ getSelectedParameter().getUnitGroup().getDefaultUnit().getUnit());
+
+ writer.write("\n");
+ }
+
+
+ for (FunctionEvaluationData data : evaluationHistory.values()) {
+ Value[] state = data.getState();
+
+ for (int i = 0; i < state.length; i++) {
+ writer.write(TextUtil.doubleToString(state[i].getUnitValue()));
+ writer.write(fieldSeparator);
+ }
+
+ if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
+ writer.write(TextUtil.doubleToString(data.getDomainReference().getUnitValue()));
+ writer.write(fieldSeparator);
+ }
+
+ if (data.getParameterValue() != null) {
+ writer.write(TextUtil.doubleToString(data.getParameterValue().getUnitValue()));
+ } else {
+ writer.write("N/A");
+ }
+ writer.write("\n");
+ }
+
+ writer.close();
+ log.info("File successfully saved");
+
+ } catch (IOException e) {
+ FileHelper.errorWriting(e, this);
+ }
+
+ }
+
+ /**
+ * Return the currently selected available simulation modifier from the modifier tree,
+ * or <code>null</code> if none selected.
+ */
+ private SimulationModifier getSelectedAvailableModifier() {
+ TreePath treepath = availableModifierTree.getSelectionPath();
+ if (treepath != null) {
+ Object o = ((DefaultMutableTreeNode) treepath.getLastPathComponent()).getUserObject();
+ if (o instanceof SimulationModifier) {
+ return (SimulationModifier) o;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the currently selected simulation.
+ * @return the selected simulation.
+ */
+ @SuppressWarnings("unchecked")
+ private Simulation getSelectedSimulation() {
+ return ((Named<Simulation>) simulationSelectionCombo.getSelectedItem()).get();
+ }
+
+
+ /**
+ * Return the currently selected simulation modifier from the table,
+ * or <code>null</code> if none selected.
+ * @return the selected modifier or <code>null</code>.
+ */
+ private SimulationModifier getSelectedModifier() {
+ int row = selectedModifierTable.getSelectedRow();
+ if (row < 0) {
+ return null;
+ }
+ row = selectedModifierTable.convertRowIndexToModel(row);
+ return selectedModifiers.get(row);
+ }
+
+
+ /**
+ * Return the currently selected optimization parameter.
+ * @return the selected optimization parameter.
+ */
+ @SuppressWarnings("unchecked")
+ private OptimizableParameter getSelectedParameter() {
+ return ((Named<OptimizableParameter>) optimizationParameterCombo.getSelectedItem()).get();
+ }
+
+
+ private Unit getModifierUnit(int index) {
+ return selectedModifiers.get(index).getUnitGroup().getDefaultUnit();
+ }
+
+ private String createSimulationName(String simulationName, String motorConfiguration) {
+ String name;
+ boolean hasParenthesis = motorConfiguration.matches("^[\\[\\(].*[\\]\\)]$");
+ name = simulationName + " ";
+ if (!hasParenthesis) {
+ name += "(";
+ }
+ name += motorConfiguration;
+ if (!hasParenthesis) {
+ name += ")";
+ }
+ return name;
+ }
+
+ /**
+ * The table model for the parameter selection.
+ *
+ * [Body tube: Length] [min] [max] [unit]
+ */
+ private class ParameterSelectionTableModel extends AbstractTableModel {
+
+ private static final int PARAMETER = 0;
+ private static final int CURRENT = 1;
+ private static final int MIN = 2;
+ private static final int MAX = 3;
+ private static final int COUNT = 4;
+
+ @Override
+ public int getColumnCount() {
+ return COUNT;
+ }
+
+ @Override
+ public int getRowCount() {
+ return selectedModifiers.size();
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ switch (column) {
+ case PARAMETER:
+ return trans.get("table.col.parameter");
+ case CURRENT:
+ return trans.get("table.col.current");
+ case MIN:
+ return trans.get("table.col.min");
+ case MAX:
+ return trans.get("table.col.max");
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+
+ }
+
+ @Override
+ public Class<?> getColumnClass(int column) {
+ switch (column) {
+ case PARAMETER:
+ return String.class;
+ case CURRENT:
+ return Double.class;
+ case MIN:
+ return Double.class;
+ case MAX:
+ return Double.class;
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+ }
+
+ @Override
+ public Object getValueAt(int row, int column) {
+
+ SimulationModifier modifier = selectedModifiers.get(row);
+
+ switch (column) {
+ case PARAMETER:
+ return modifier.getRelatedObject().toString() + ": " + modifier.getName();
+ case CURRENT:
+ try {
+ return getModifierUnit(row).toUnit(modifier.getCurrentSIValue(getSelectedSimulation()));
+ } catch (OptimizationException e) {
+ throw new BugException("Could not read current SI value from modifier " + modifier, e);
+ }
+ case MIN:
+ return getModifierUnit(row).toUnit(modifier.getMinValue());
+ case MAX:
+ return getModifierUnit(row).toUnit(modifier.getMaxValue());
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+
+ }
+
+ @Override
+ public void setValueAt(Object value, int row, int column) {
+
+ switch (column) {
+ case PARAMETER:
+ break;
+
+ case MIN:
+ double min = (Double) value;
+ min = getModifierUnit(row).fromUnit(min);
+ selectedModifiers.get(row).setMinValue(min);
+ break;
+
+ case CURRENT:
+ break;
+
+ case MAX:
+ double max = (Double) value;
+ max = getModifierUnit(row).fromUnit(max);
+ selectedModifiers.get(row).setMaxValue(max);
+ break;
+
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+ this.fireTableRowsUpdated(row, row);
+
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int column) {
+ switch (column) {
+ case PARAMETER:
+ return false;
+ case CURRENT:
+ return false;
+ case MIN:
+ return true;
+ case MAX:
+ return true;
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+ }
+
+ }
+
+
+ private class DoubleCellRenderer extends DefaultTableCellRenderer {
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+
+ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+
+ double val = (Double) value;
+ Unit unit = getModifierUnit(row);
+
+ val = unit.fromUnit(val);
+ this.setText(unit.toStringUnit(val));
+
+ return this;
+ }
+ }
+
+
+ private static class SimulationModifierComparator implements Comparator<SimulationModifier> {
+
+ @Override
+ public int compare(SimulationModifier mod1, SimulationModifier mod2) {
+ Object rel1 = mod1.getRelatedObject();
+ Object rel2 = mod2.getRelatedObject();
+
+ /*
+ * Primarily order by related object:
+ *
+ * - RocketComponents first
+ * - Two RocketComponents are ordered based on their position in the rocket
+ */
+ if (!rel1.equals(rel2)) {
+
+ if (rel1 instanceof RocketComponent) {
+ if (rel2 instanceof RocketComponent) {
+
+ RocketComponent root = ((RocketComponent) rel1).getRoot();
+ for (RocketComponent c : root) {
+ if (c.equals(rel1)) {
+ return -1;
+ }
+ if (c.equals(rel2)) {
+ return 1;
+ }
+ }
+
+ throw new BugException("Error sorting modifiers, mod1=" + mod1 + " rel1=" + rel1 +
+ " mod2=" + mod2 + " rel2=" + rel2);
+
+ } else {
+ return -1;
+ }
+ } else {
+ if (rel2 instanceof RocketComponent) {
+ return 1;
+ }
+ }
+
+ }
+
+ // Secondarily sort by name
+ return collator.compare(mod1.getName(), mod2.getName());
+ }
+ }
+
+
+
}