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 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.Timer; 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.formatting.RocketDescriptor; 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.gui.util.FileHelper; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.Markers; 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.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.Named; 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 Logger log = LoggerFactory.getLogger(GeneralOptimizationDialog.class); 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 RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>(); private final Map<Object, List<SimulationModifier>> simulationModifiers = new HashMap<Object, List<SimulationModifier>>(); private final OpenRocketDocument baseDocument; private OpenRocketDocument documentCopy; 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.NOUNIT; 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) { 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.info(Markers.USER_MARKER, "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.info(Markers.USER_MARKER, "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.ttip"); 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.info(Markers.USER_MARKER, "Stopping optimization"); stopOptimization(); } else { log.info(Markers.USER_MARKER, "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.info(Markers.USER_MARKER, "Plotting optimization path, dimensionality=" + selectedModifiers.size()); OptimizationPlotDialog dialog = new OptimizationPlotDialog( Collections.unmodifiableList(optimizationPath), Collections.unmodifiableMap(evaluationHistory), Collections.unmodifiableList(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.info(Markers.USER_MARKER, "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.info(Markers.USER_MARKER, "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.info(Markers.USER_MARKER, "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.info(Markers.USER_MARKER, "Closing optimization dialog"); stopOptimization(); GeneralOptimizationDialog.this.dispose(); } }); panel.add(closeButton, "right"); this.add(panel); clearHistory(); updateComponents(); GUIUtil.setDisposableDialogOptions(this, null); } 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]); // Check for DeploymentAltitude modifier, if it's there, we want to make certain the DeploymentEvent // is ALTITUDE: for (SimulationModifier mod : modifiers) { try { mod.initialize(simulation); } catch (OptimizationException ex) { updating = true; startButton.setSelected(false); startButton.setText(START_TEXT); updating = false; throw new BugException(ex); } } // 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(); // Disable the start/stop button for a short while after ending the simulation // to prevent accidentally starting a new optimization when trying to stop it startButton.setEnabled(false); Timer timer = new Timer(750, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { startButton.setEnabled(true); } }); timer.setRepeats(false); timer.start(); updateComponents(); } @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 && pointValue.getParameterValue() != null) { bestValue = pointValue.getParameterValue().getValue(); } else { 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) { SimulationModifier updated = newModifiers.get(index); updated.setMinValue(original.getMinValue()); updated.setMaxValue(original.getMaxValue()); newSelected.add(updated); } } } 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().getFlightConfigurationID(); String name = createSimulationName(s.getName(), descriptor.format(rocket, id)); simulations.add(new Named<Simulation>(s, name)); } for (String id : rocket.getFlightConfigurationIDs()) { if (id == null) { continue; } Simulation sim = new Simulation(rocket); sim.getConfiguration().setFlightConfigurationID(id); String name = createSimulationName(trans.get("basicSimulationName"), descriptor.format(rocket, id)); simulations.add(new Named<Simulation>(sim, name)); } Simulation sim = new Simulation(rocket); sim.getConfiguration().setFlightConfigurationID(null); String name = createSimulationName(trans.get("noSimulationName"), descriptor.format(rocket, null)); simulations.add(new Named<Simulation>(sim, name)); simulationSelectionCombo.setModel(new DefaultComboBoxModel(simulations.toArray())); simulationSelectionCombo.setSelectedIndex(0); 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."); } Collections.sort(optimizationParameters, new Comparator<OptimizableParameter>() { @Override public int compare(OptimizableParameter o1, OptimizableParameter o2) { return o1.getName().compareTo(o2.getName()); } }); } private void loadSimulationModifiers() { simulationModifiers.clear(); 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()) { List<SimulationModifier> list = simulationModifiers.get(key); Collections.sort(list, new Comparator<SimulationModifier>() { @Override public int compare(SimulationModifier o1, SimulationModifier o2) { return o1.getName().compareTo(o2.getName()); } }); } } private void addModifier(SimulationModifier mod) { if (!selectedModifiers.contains(mod)) { log.info(Markers.USER_MARKER, "Adding simulation modifier " + mod); selectedModifiers.add(mod); selectedModifierTableModel.fireTableDataChanged(); availableModifierTree.repaint(); } else { log.info(Markers.USER_MARKER, "Attempting to add an already existing simulation modifier " + mod); } } private void removeModifier(SimulationModifier mod) { log.info(Markers.USER_MARKER, "Removing simulation modifier " + mod); selectedModifiers.remove(mod); selectedModifierTableModel.fireTableDataChanged(); availableModifierTree.repaint(); } /** * Update the enabled status of all components in the dialog. */ private void updateComponents() { boolean state; if (updating) { log.debug("Ignoring updateComponents"); return; } log.debug("Running updateComponents()"); updating = true; // First enable all components if optimization not running if (!running) { log.debug("Initially enabling all components"); for (JComponent c : disableComponents) { c.setEnabled(true); } } // "Add" button SimulationModifier mod = getSelectedAvailableModifier(); state = (mod != null && !selectedModifiers.contains(mod)); log.debug("addButton enabled: " + state); addButton.setEnabled(state); // "Remove" button state = (selectedModifierTable.getSelectedRow() >= 0); log.debug("removeButton enabled: " + state); removeButton.setEnabled(state); // "Remove all" button state = (!selectedModifiers.isEmpty()); log.debug("removeAllButton enabled: " + state); removeAllButton.setEnabled(state); // Optimization goal String selected = (String) optimizationGoalCombo.getSelectedItem(); state = GOAL_SEEK.equals(selected); log.debug("optimizationGoalSpinner & UnitSelector enabled: " + state); optimizationGoalSpinner.setVisible(state); optimizationGoalUnitSelector.setVisible(state); // Minimum/maximum stability options state = minimumStabilitySelected.isSelected(); log.debug("minimumStabilitySpinner & UnitSelector enabled: " + state); minimumStabilitySpinner.setEnabled(state); minimumStabilityUnitSelector.setEnabled(state); state = maximumStabilitySelected.isSelected(); log.debug("maximumStabilitySpimmer & UnitSelector enabled: " + state); maximumStabilitySpinner.setEnabled(state); maximumStabilityUnitSelector.setEnabled(state); // Plot button (enabled if path exists and dimensionality is 1 or 2) state = (!optimizationPath.isEmpty() && (selectedModifiers.size() == 1 || selectedModifiers.size() == 2)); log.debug("plotButton enabled: " + state + " optimizationPath.isEmpty=" + optimizationPath.isEmpty() + " selectedModifiers.size=" + selectedModifiers.size()); plotButton.setEnabled(state); // Save button (enabled if path exists) state = (!evaluationHistory.isEmpty()); log.debug("saveButton enabled: " + state); saveButton.setEnabled(state); // Last disable all components if optimization is running if (running) { log.debug("Disabling all components because optimization is 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(((SwingPreferences) Application.getPreferences()).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() { /* This is to debug a NPE where the returned selected item is null. */ Object item = simulationSelectionCombo.getSelectedItem(); if (item == null) { String s = "Selected simulation is null:"; s = s + " item count=" + simulationSelectionCombo.getItemCount(); for (int i = 0; i < simulationSelectionCombo.getItemCount(); i++) { s = s + " [" + i + "]=" + simulationSelectionCombo.getItemAt(i); } throw new BugException(s); } return ((Named<Simulation>) item).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) { if (row >= selectedModifiers.size()) { throw new BugException("setValueAt with invalid row: value=" + value + " row=" + row + " column=" + column + " selectedModifiers.size=" + selectedModifiers.size() + " selectedModifiers=" + selectedModifiers + " selectedModifierTable.getRowCount=" + selectedModifierTable.getRowCount()); } 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()); } } }