1 package net.sf.openrocket.gui.dialogs.optimization;
3 import java.awt.Component;
4 import java.awt.Window;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.awt.event.MouseAdapter;
8 import java.awt.event.MouseEvent;
9 import java.io.BufferedWriter;
11 import java.io.FileWriter;
12 import java.io.IOException;
13 import java.io.Writer;
14 import java.text.Collator;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.Comparator;
18 import java.util.HashMap;
19 import java.util.LinkedHashMap;
20 import java.util.LinkedList;
21 import java.util.List;
24 import javax.swing.BorderFactory;
25 import javax.swing.DefaultComboBoxModel;
26 import javax.swing.JButton;
27 import javax.swing.JCheckBox;
28 import javax.swing.JComboBox;
29 import javax.swing.JComponent;
30 import javax.swing.JDialog;
31 import javax.swing.JFileChooser;
32 import javax.swing.JLabel;
33 import javax.swing.JOptionPane;
34 import javax.swing.JPanel;
35 import javax.swing.JScrollPane;
36 import javax.swing.JSpinner;
37 import javax.swing.JTable;
38 import javax.swing.JToggleButton;
39 import javax.swing.ListSelectionModel;
40 import javax.swing.Timer;
41 import javax.swing.border.TitledBorder;
42 import javax.swing.event.ChangeEvent;
43 import javax.swing.event.ChangeListener;
44 import javax.swing.event.ListSelectionEvent;
45 import javax.swing.event.ListSelectionListener;
46 import javax.swing.event.TreeSelectionEvent;
47 import javax.swing.event.TreeSelectionListener;
48 import javax.swing.table.AbstractTableModel;
49 import javax.swing.table.DefaultTableCellRenderer;
50 import javax.swing.table.TableColumnModel;
51 import javax.swing.tree.DefaultMutableTreeNode;
52 import javax.swing.tree.TreePath;
54 import net.miginfocom.swing.MigLayout;
55 import net.sf.openrocket.document.OpenRocketDocument;
56 import net.sf.openrocket.document.Simulation;
57 import net.sf.openrocket.gui.SpinnerEditor;
58 import net.sf.openrocket.gui.adaptors.DoubleModel;
59 import net.sf.openrocket.gui.components.CsvOptionPanel;
60 import net.sf.openrocket.gui.components.DescriptionArea;
61 import net.sf.openrocket.gui.components.DoubleCellEditor;
62 import net.sf.openrocket.gui.components.StyledLabel;
63 import net.sf.openrocket.gui.components.StyledLabel.Style;
64 import net.sf.openrocket.gui.components.UnitCellEditor;
65 import net.sf.openrocket.gui.components.UnitSelector;
66 import net.sf.openrocket.gui.scalefigure.RocketFigure;
67 import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
68 import net.sf.openrocket.gui.util.FileHelper;
69 import net.sf.openrocket.gui.util.GUIUtil;
70 import net.sf.openrocket.gui.util.SwingPreferences;
71 import net.sf.openrocket.l10n.Translator;
72 import net.sf.openrocket.logging.LogHelper;
73 import net.sf.openrocket.optimization.general.OptimizationException;
74 import net.sf.openrocket.optimization.general.Point;
75 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
76 import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
77 import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
78 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
79 import net.sf.openrocket.optimization.rocketoptimization.domains.IdentitySimulationDomain;
80 import net.sf.openrocket.optimization.rocketoptimization.domains.StabilityDomain;
81 import net.sf.openrocket.optimization.rocketoptimization.goals.MaximizationGoal;
82 import net.sf.openrocket.optimization.rocketoptimization.goals.MinimizationGoal;
83 import net.sf.openrocket.optimization.rocketoptimization.goals.ValueSeekGoal;
84 import net.sf.openrocket.optimization.services.OptimizationServiceHelper;
85 import net.sf.openrocket.rocketcomponent.Rocket;
86 import net.sf.openrocket.rocketcomponent.RocketComponent;
87 import net.sf.openrocket.startup.Application;
88 import net.sf.openrocket.unit.CaliberUnit;
89 import net.sf.openrocket.unit.Unit;
90 import net.sf.openrocket.unit.UnitGroup;
91 import net.sf.openrocket.unit.Value;
92 import net.sf.openrocket.util.BugException;
93 import net.sf.openrocket.util.Chars;
94 import net.sf.openrocket.util.Named;
95 import net.sf.openrocket.util.TextUtil;
97 import com.itextpdf.text.Font;
101 * General rocket optimization dialog.
103 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
105 public class GeneralOptimizationDialog extends JDialog {
106 private static final LogHelper log = Application.getLogger();
107 private static final Translator trans = Application.getTranslator();
109 private static final Collator collator = Collator.getInstance();
112 private static final String GOAL_MAXIMIZE = trans.get("goal.maximize");
113 private static final String GOAL_MINIMIZE = trans.get("goal.minimize");
114 private static final String GOAL_SEEK = trans.get("goal.seek");
116 private static final String START_TEXT = trans.get("btn.start");
117 private static final String STOP_TEXT = trans.get("btn.stop");
121 private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>();
122 private final Map<Object, List<SimulationModifier>> simulationModifiers =
123 new HashMap<Object, List<SimulationModifier>>();
126 private final OpenRocketDocument baseDocument;
127 private OpenRocketDocument documentCopy;
130 private final JButton addButton;
131 private final JButton removeButton;
132 private final JButton removeAllButton;
134 private final ParameterSelectionTableModel selectedModifierTableModel;
135 private final JTable selectedModifierTable;
136 private final DescriptionArea selectedModifierDescription;
137 private final SimulationModifierTree availableModifierTree;
139 private final JComboBox simulationSelectionCombo;
140 private final JComboBox optimizationParameterCombo;
142 private final JComboBox optimizationGoalCombo;
143 private final JSpinner optimizationGoalSpinner;
144 private final UnitSelector optimizationGoalUnitSelector;
145 private final DoubleModel optimizationSeekValue;
147 private DoubleModel minimumStability;
148 private DoubleModel maximumStability;
149 private final JCheckBox minimumStabilitySelected;
150 private final JSpinner minimumStabilitySpinner;
151 private final UnitSelector minimumStabilityUnitSelector;
152 private final JCheckBox maximumStabilitySelected;
153 private final JSpinner maximumStabilitySpinner;
154 private final UnitSelector maximumStabilityUnitSelector;
156 private final JLabel bestValueLabel;
157 private final JLabel stepCountLabel;
158 private final JLabel evaluationCountLabel;
159 private final JLabel stepSizeLabel;
161 private final RocketFigure figure;
162 private final JToggleButton startButton;
163 private final JButton plotButton;
164 private final JButton saveButton;
166 private final JButton applyButton;
167 private final JButton resetButton;
168 private final JButton closeButton;
170 private final List<SimulationModifier> selectedModifiers = new ArrayList<SimulationModifier>();
172 /** List of components to disable while optimization is running */
173 private final List<JComponent> disableComponents = new ArrayList<JComponent>();
175 /** Whether optimization is currently running or not */
176 private boolean running = false;
177 /** The optimization worker that is running */
178 private OptimizationWorker worker = null;
181 private double bestValue = Double.NaN;
182 private Unit bestValueUnit = Unit.NOUNIT2;
183 private int stepCount = 0;
184 private int evaluationCount = 0;
185 private double stepSize = 0;
187 private final Map<Point, FunctionEvaluationData> evaluationHistory = new LinkedHashMap<Point, FunctionEvaluationData>();
188 private final List<Point> optimizationPath = new LinkedList<Point>();
191 private boolean updating = false;
197 * @param document the document
198 * @param parent the parent window
200 public GeneralOptimizationDialog(OpenRocketDocument document, Window parent) {
201 super(parent, trans.get("title"));
203 this.baseDocument = document;
204 this.documentCopy = document.copy();
206 loadOptimizationParameters();
207 loadSimulationModifiers();
214 JPanel panel = new JPanel(new MigLayout("fill"));
217 ChangeListener clearHistoryChangeListener = new ChangeListener() {
219 public void stateChanged(ChangeEvent e) {
223 ActionListener clearHistoryActionListener = new ActionListener() {
225 public void actionPerformed(ActionEvent e) {
232 //// Selected modifiers table
234 selectedModifierTableModel = new ParameterSelectionTableModel();
235 selectedModifierTable = new JTable(selectedModifierTableModel);
236 selectedModifierTable.setDefaultRenderer(Double.class, new DoubleCellRenderer());
237 selectedModifierTable.setRowSelectionAllowed(true);
238 selectedModifierTable.setColumnSelectionAllowed(false);
239 selectedModifierTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
241 // Make sure spinner editor fits into the cell height
242 selectedModifierTable.setRowHeight(new JSpinner().getPreferredSize().height - 4);
244 selectedModifierTable.setDefaultEditor(Double.class, new DoubleCellEditor());
245 selectedModifierTable.setDefaultEditor(Unit.class, new UnitCellEditor() {
247 protected UnitGroup getUnitGroup(Unit value, int row, int column) {
248 return selectedModifiers.get(row).getUnitGroup();
252 disableComponents.add(selectedModifierTable);
254 selectedModifierTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
256 public void valueChanged(ListSelectionEvent e) {
262 TableColumnModel columnModel = selectedModifierTable.getColumnModel();
263 columnModel.getColumn(0).setPreferredWidth(150);
264 columnModel.getColumn(1).setPreferredWidth(40);
265 columnModel.getColumn(2).setPreferredWidth(40);
266 columnModel.getColumn(3).setPreferredWidth(40);
268 scroll = new JScrollPane(selectedModifierTable);
270 label = new StyledLabel(trans.get("lbl.paramsToOptimize"), Style.BOLD);
271 disableComponents.add(label);
272 panel.add(label, "split 3, flowy");
273 panel.add(scroll, "wmin 300lp, height 200lp, grow");
274 selectedModifierDescription = new DescriptionArea(2, -3);
275 disableComponents.add(selectedModifierDescription);
276 panel.add(selectedModifierDescription, "growx");
280 //// Add/remove buttons
281 sub = new JPanel(new MigLayout("fill"));
283 addButton = new JButton(Chars.LEFT_ARROW + " " + trans.get("btn.add") + " ");
284 addButton.setToolTipText(trans.get("btn.add.ttip"));
285 addButton.addActionListener(new ActionListener() {
287 public void actionPerformed(ActionEvent e) {
288 SimulationModifier mod = getSelectedAvailableModifier();
293 log.error("Attempting to add simulation modifier when none is selected");
297 disableComponents.add(addButton);
298 sub.add(addButton, "wrap para, sg button");
300 removeButton = new JButton(" " + trans.get("btn.remove") + " " + Chars.RIGHT_ARROW);
301 removeButton.setToolTipText(trans.get("btn.remove.ttip"));
302 removeButton.addActionListener(new ActionListener() {
304 public void actionPerformed(ActionEvent e) {
305 SimulationModifier mod = getSelectedModifier();
307 log.error("Attempting to remove simulation modifier when none is selected");
314 disableComponents.add(removeButton);
315 sub.add(removeButton, "wrap para*2, sg button");
317 removeAllButton = new JButton(trans.get("btn.removeAll"));
318 removeAllButton.setToolTipText(trans.get("btn.removeAll.ttip"));
319 removeAllButton.addActionListener(new ActionListener() {
321 public void actionPerformed(ActionEvent e) {
322 log.user("Removing all selected modifiers");
323 selectedModifiers.clear();
324 selectedModifierTableModel.fireTableDataChanged();
325 availableModifierTree.repaint();
329 disableComponents.add(removeAllButton);
330 sub.add(removeAllButton, "wrap para, sg button");
336 //// Available modifier tree
337 availableModifierTree = new SimulationModifierTree(documentCopy.getRocket(), simulationModifiers, selectedModifiers);
338 availableModifierTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
340 public void valueChanged(TreeSelectionEvent e) {
345 // Handle double-click
346 availableModifierTree.addMouseListener(new MouseAdapter() {
348 public void mousePressed(MouseEvent e) {
349 if (e.getClickCount() == 2) {
350 SimulationModifier mod = getSelectedAvailableModifier();
355 log.user("Double-clicked non-available option");
361 disableComponents.add(availableModifierTree);
362 scroll = new JScrollPane(availableModifierTree);
363 label = new StyledLabel(trans.get("lbl.availableParams"), Style.BOLD);
364 disableComponents.add(label);
365 panel.add(label, "split 2, flowy");
366 panel.add(scroll, "width 300lp, height 200lp, grow, wrap para*2");
371 //// Optimization options sub-panel
373 sub = new JPanel(new MigLayout("fill"));
374 TitledBorder border = BorderFactory.createTitledBorder(trans.get("lbl.optimizationOpts"));
375 GUIUtil.changeFontStyle(border, Font.BOLD);
376 sub.setBorder(border);
377 disableComponents.add(sub);
380 //// Simulation to optimize
382 label = new JLabel(trans.get("lbl.optimizeSim"));
383 tip = trans.get("lbl.optimizeSim.ttip");
384 label.setToolTipText(tip);
385 disableComponents.add(label);
388 simulationSelectionCombo = new JComboBox();
389 simulationSelectionCombo.setToolTipText(tip);
390 populateSimulations();
391 simulationSelectionCombo.addActionListener(clearHistoryActionListener);
392 disableComponents.add(simulationSelectionCombo);
393 sub.add(simulationSelectionCombo, "growx, wrap unrel");
397 //// Value to optimize
398 label = new JLabel(trans.get("lbl.optimizeValue"));
399 tip = trans.get("lbl.optimizeValue.ttip");
400 label.setToolTipText(tip);
401 disableComponents.add(label);
404 optimizationParameterCombo = new JComboBox();
405 optimizationParameterCombo.setToolTipText(tip);
406 populateParameters();
407 optimizationParameterCombo.addActionListener(clearHistoryActionListener);
408 disableComponents.add(optimizationParameterCombo);
409 sub.add(optimizationParameterCombo, "growx, wrap unrel");
413 //// Optimization goal
414 label = new JLabel(trans.get("lbl.optimizeGoal"));
415 tip = trans.get("lbl.optimizeGoal");
416 label.setToolTipText(tip);
417 disableComponents.add(label);
420 optimizationGoalCombo = new JComboBox(new String[] { GOAL_MAXIMIZE, GOAL_MINIMIZE, GOAL_SEEK });
421 optimizationGoalCombo.setToolTipText(tip);
422 optimizationGoalCombo.setEditable(false);
423 optimizationGoalCombo.addActionListener(clearHistoryActionListener);
424 disableComponents.add(optimizationGoalCombo);
425 sub.add(optimizationGoalCombo, "growx");
428 //// Optimization custom value
429 optimizationSeekValue = new DoubleModel(0, UnitGroup.UNITS_NONE);
430 optimizationSeekValue.addChangeListener(clearHistoryChangeListener);
432 optimizationGoalSpinner = new JSpinner(optimizationSeekValue.getSpinnerModel());
433 tip = trans.get("lbl.optimizeGoalValue.ttip");
434 optimizationGoalSpinner.setToolTipText(tip);
435 optimizationGoalSpinner.setEditor(new SpinnerEditor(optimizationGoalSpinner));
436 disableComponents.add(optimizationGoalSpinner);
437 sub.add(optimizationGoalSpinner, "width 30lp");
439 optimizationGoalUnitSelector = new UnitSelector(optimizationSeekValue);
440 optimizationGoalUnitSelector.setToolTipText(tip);
441 disableComponents.add(optimizationGoalUnitSelector);
442 sub.add(optimizationGoalUnitSelector, "width 20lp, wrap unrel");
445 panel.add(sub, "grow");
449 //// Required stability sub-panel
451 sub = new JPanel(new MigLayout("fill"));
452 border = BorderFactory.createTitledBorder(trans.get("lbl.requireStability"));
453 GUIUtil.changeFontStyle(border, Font.BOLD);
454 sub.setBorder(border);
455 disableComponents.add(sub);
459 double ref = CaliberUnit.calculateCaliber(baseDocument.getRocket());
460 minimumStability = new DoubleModel(ref, UnitGroup.stabilityUnits(ref));
461 maximumStability = new DoubleModel(5 * ref, UnitGroup.stabilityUnits(ref));
462 minimumStability.addChangeListener(clearHistoryChangeListener);
463 maximumStability.addChangeListener(clearHistoryChangeListener);
466 //// Minimum stability
467 tip = trans.get("lbl.requireMinStability.ttip");
468 minimumStabilitySelected = new JCheckBox(trans.get("lbl.requireMinStability"));
469 minimumStabilitySelected.setSelected(true);
470 minimumStabilitySelected.setToolTipText(tip);
471 minimumStabilitySelected.addActionListener(new ActionListener() {
473 public void actionPerformed(ActionEvent e) {
477 disableComponents.add(minimumStabilitySelected);
478 sub.add(minimumStabilitySelected);
480 minimumStabilitySpinner = new JSpinner(minimumStability.getSpinnerModel());
481 minimumStabilitySpinner.setToolTipText(tip);
482 minimumStabilitySpinner.setEditor(new SpinnerEditor(minimumStabilitySpinner));
483 disableComponents.add(minimumStabilitySpinner);
484 sub.add(minimumStabilitySpinner, "growx");
486 minimumStabilityUnitSelector = new UnitSelector(minimumStability);
487 minimumStabilityUnitSelector.setToolTipText(tip);
488 disableComponents.add(minimumStabilityUnitSelector);
489 sub.add(minimumStabilityUnitSelector, "growx, wrap unrel");
492 //// Maximum stability
493 tip = trans.get("lbl.requireMaxStability.ttip");
494 maximumStabilitySelected = new JCheckBox(trans.get("lbl.requireMaxStability"));
495 maximumStabilitySelected.setToolTipText(tip);
496 maximumStabilitySelected.addActionListener(new ActionListener() {
498 public void actionPerformed(ActionEvent e) {
502 disableComponents.add(maximumStabilitySelected);
503 sub.add(maximumStabilitySelected);
505 maximumStabilitySpinner = new JSpinner(maximumStability.getSpinnerModel());
506 maximumStabilitySpinner.setToolTipText(tip);
507 maximumStabilitySpinner.setEditor(new SpinnerEditor(maximumStabilitySpinner));
508 disableComponents.add(maximumStabilitySpinner);
509 sub.add(maximumStabilitySpinner, "growx");
511 maximumStabilityUnitSelector = new UnitSelector(maximumStability);
512 maximumStabilityUnitSelector.setToolTipText(tip);
513 disableComponents.add(maximumStabilityUnitSelector);
514 sub.add(maximumStabilityUnitSelector, "growx, wrap para");
518 // DescriptionArea desc = new DescriptionArea("Stability requirements are verified during each time step of the simulation.",
520 // desc.setViewportBorder(null);
521 // disableComponents.add(desc);
522 // sub.add(desc, "span, growx");
525 panel.add(sub, "span 2, grow, wrap para*2");
531 figure = new RocketFigure(getSelectedSimulation().getConfiguration());
532 figure.setBorderPixels(1, 1);
533 ScaleScrollPane figureScrollPane = new ScaleScrollPane(figure);
534 figureScrollPane.setFitting(true);
535 panel.add(figureScrollPane, "span, split, height 200lp, grow");
538 sub = new JPanel(new MigLayout("fill"));
541 label = new JLabel(trans.get("status.bestValue"));
542 tip = trans.get("status.bestValue.ttip");
543 label.setToolTipText(tip);
544 sub.add(label, "gapright unrel");
546 bestValueLabel = new JLabel();
547 bestValueLabel.setToolTipText(tip);
548 sub.add(bestValueLabel, "wmin 60lp, wrap rel");
551 label = new JLabel(trans.get("status.stepCount"));
552 tip = trans.get("status.stepCount.ttip");
553 label.setToolTipText(tip);
554 sub.add(label, "gapright unrel");
556 stepCountLabel = new JLabel();
557 stepCountLabel.setToolTipText(tip);
558 sub.add(stepCountLabel, "wrap rel");
561 label = new JLabel(trans.get("status.evalCount"));
562 tip = trans.get("status.evalCount.ttip");
563 label.setToolTipText(tip);
564 sub.add(label, "gapright unrel");
566 evaluationCountLabel = new JLabel();
567 evaluationCountLabel.setToolTipText(tip);
568 sub.add(evaluationCountLabel, "wrap rel");
571 label = new JLabel(trans.get("status.stepSize"));
572 tip = trans.get("status.stepSize.ttip");
573 label.setToolTipText(tip);
574 sub.add(label, "gapright unrel");
576 stepSizeLabel = new JLabel();
577 stepSizeLabel.setToolTipText(tip);
578 sub.add(stepSizeLabel, "wrap para");
581 //// Start/Stop button
583 startButton = new JToggleButton(START_TEXT);
584 startButton.addActionListener(new ActionListener() {
586 public void actionPerformed(ActionEvent e) {
588 log.debug("Updating, ignoring event");
592 log.user("Stopping optimization");
595 log.user("Starting optimization");
600 sub.add(startButton, "span, growx, wrap para*2");
603 plotButton = new JButton(trans.get("btn.plotPath"));
604 plotButton.setToolTipText(trans.get("btn.plotPath.ttip"));
605 plotButton.addActionListener(new ActionListener() {
607 public void actionPerformed(ActionEvent e) {
608 log.user("Plotting optimization path, dimensionality=" + selectedModifiers.size());
609 OptimizationPlotDialog dialog = new OptimizationPlotDialog(
610 Collections.unmodifiableList(optimizationPath),
611 Collections.unmodifiableMap(evaluationHistory),
612 Collections.unmodifiableList(selectedModifiers),
613 getSelectedParameter(),
614 UnitGroup.stabilityUnits(getSelectedSimulation().getRocket()),
615 GeneralOptimizationDialog.this);
616 dialog.setVisible(true);
619 disableComponents.add(plotButton);
620 sub.add(plotButton, "span, growx, wrap");
623 saveButton = new JButton(trans.get("btn.save"));
624 saveButton.setToolTipText(trans.get("btn.save.ttip"));
625 saveButton.addActionListener(new ActionListener() {
627 public void actionPerformed(ActionEvent e) {
628 log.user("User selected save path");
632 disableComponents.add(saveButton);
633 sub.add(saveButton, "span, growx");
637 panel.add(sub, "wrap para*2");
644 applyButton = new JButton(trans.get("btn.apply"));
645 applyButton.setToolTipText(trans.get("btn.apply.ttip"));
646 applyButton.addActionListener(new ActionListener() {
648 public void actionPerformed(ActionEvent e) {
649 log.user("Applying optimization changes");
653 disableComponents.add(applyButton);
654 panel.add(applyButton, "span, split, gapright para, right");
656 resetButton = new JButton(trans.get("btn.reset"));
657 resetButton.setToolTipText(trans.get("btn.reset.ttip"));
658 resetButton.addActionListener(new ActionListener() {
660 public void actionPerformed(ActionEvent e) {
661 log.user("Resetting optimization design");
665 disableComponents.add(resetButton);
666 panel.add(resetButton, "gapright para, right");
668 closeButton = new JButton(trans.get("btn.close"));
669 closeButton.setToolTipText(trans.get("btn.close.ttip"));
670 closeButton.addActionListener(new ActionListener() {
672 public void actionPerformed(ActionEvent e) {
673 log.user("Closing optimization dialog");
675 GeneralOptimizationDialog.this.dispose();
678 panel.add(closeButton, "right");
684 GUIUtil.setDisposableDialogOptions(this, null);
688 private void startOptimization() {
690 log.info("Optimization already running");
695 if (selectedModifiers.isEmpty()) {
696 JOptionPane.showMessageDialog(this, trans.get("error.selectParams.text"),
697 trans.get("error.selectParams.title"), JOptionPane.ERROR_MESSAGE);
699 startButton.setSelected(false);
700 startButton.setText(START_TEXT);
708 // Update the button status
710 startButton.setSelected(true);
711 startButton.setText(STOP_TEXT);
715 // Create a copy of the simulation (we're going to modify the original in the current thread)
716 Simulation simulation = getSelectedSimulation();
717 Rocket rocketCopy = simulation.getRocket().copyWithOriginalID();
718 simulation = simulation.duplicateSimulation(rocketCopy);
720 OptimizableParameter parameter = getSelectedParameter();
722 OptimizationGoal goal;
723 String value = (String) optimizationGoalCombo.getSelectedItem();
724 if (GOAL_MAXIMIZE.equals(value)) {
725 goal = new MaximizationGoal();
726 } else if (GOAL_MINIMIZE.equals(value)) {
727 goal = new MinimizationGoal();
728 } else if (GOAL_SEEK.equals(value)) {
729 goal = new ValueSeekGoal(optimizationSeekValue.getValue());
731 throw new BugException("optimizationGoalCombo had invalid value: " + value);
734 SimulationDomain domain;
735 if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
737 boolean minAbsolute, maxAbsolute;
740 * Make minAbsolute/maxAbsolute consistent with each other to produce reasonable
741 * result in plot tool tips. Yes, this is a bit ugly.
745 Unit unit = minimumStability.getCurrentUnit();
746 if (unit instanceof CaliberUnit) {
747 min = unit.toUnit(minimumStability.getValue());
750 min = minimumStability.getValue();
755 unit = maximumStability.getCurrentUnit();
756 if (unit instanceof CaliberUnit) {
757 max = unit.toUnit(maximumStability.getValue());
760 max = maximumStability.getValue();
765 if (!minimumStabilitySelected.isSelected()) {
767 minAbsolute = maxAbsolute;
769 if (!maximumStabilitySelected.isSelected()) {
771 maxAbsolute = minAbsolute;
774 domain = new StabilityDomain(min, minAbsolute, max, maxAbsolute);
776 domain = new IdentitySimulationDomain();
779 SimulationModifier[] modifiers = selectedModifiers.toArray(new SimulationModifier[0]);
781 // Create and start the background worker
782 worker = new OptimizationWorker(simulation, parameter, goal, domain, modifiers) {
784 protected void done(OptimizationException exception) {
785 log.info("Optimization finished, exception=" + exception, exception);
787 if (exception != null) {
788 JOptionPane.showMessageDialog(GeneralOptimizationDialog.this,
790 trans.get("error.optimizationFailure.text"),
791 exception.getLocalizedMessage()
792 }, trans.get("error.optimizationFailure.title"), JOptionPane.ERROR_MESSAGE);
798 // Disable the start/stop button for a short while after ending the simulation
799 // to prevent accidentally starting a new optimization when trying to stop it
800 startButton.setEnabled(false);
801 Timer timer = new Timer(750, new ActionListener() {
803 public void actionPerformed(ActionEvent e) {
804 startButton.setEnabled(true);
807 timer.setRepeats(false);
813 protected void functionEvaluated(List<FunctionEvaluationData> data) {
814 for (FunctionEvaluationData d : data) {
815 evaluationHistory.put(d.getPoint(), d);
822 protected void optimizationStepTaken(List<OptimizationStepData> data) {
824 // Add starting point to the path
825 if (optimizationPath.isEmpty()) {
826 optimizationPath.add(data.get(0).getOldPoint());
829 // Add other points to the path
830 for (OptimizationStepData d : data) {
831 optimizationPath.add(d.getNewPoint());
834 // Get function value from the latest point
835 OptimizationStepData latest = data.get(data.size() - 1);
836 Point newPoint = latest.getNewPoint();
838 FunctionEvaluationData pointValue = evaluationHistory.get(newPoint);
839 if (pointValue != null && pointValue.getParameterValue() != null) {
840 bestValue = pointValue.getParameterValue().getValue();
842 bestValue = Double.NaN;
845 // Update the simulation
846 Simulation sim = getSelectedSimulation();
847 for (int i = 0; i < newPoint.dim(); i++) {
849 selectedModifiers.get(i).modify(sim, newPoint.get(i));
850 } catch (OptimizationException e) {
851 throw new BugException("Simulation modifier failed to modify the base simulation " +
852 "modifier=" + selectedModifiers.get(i), e);
855 figure.updateFigure();
857 // Update other counter data
858 stepCount += data.size();
859 stepSize = latest.getStepSize();
871 private void stopOptimization() {
873 log.info("Optimization not running");
877 if (worker != null && worker.isAlive()) {
878 log.info("Worker still running, interrupting it and setting to null");
886 // Update the button status
888 startButton.setSelected(false);
889 startButton.setText(START_TEXT);
900 * Reset the current optimization history and values. This does not reset the design.
902 private void clearHistory() {
903 evaluationHistory.clear();
904 optimizationPath.clear();
905 bestValue = Double.NaN;
906 bestValueUnit = getSelectedParameter().getUnitGroup().getDefaultUnit();
915 private void applyDesign() {
916 // TODO: MEDIUM: Apply also potential changes to simulations
917 Rocket src = getSelectedSimulation().getRocket().copyWithOriginalID();
918 Rocket dest = baseDocument.getRocket();
920 baseDocument.startUndo(trans.get("undoText"));
923 // Remove all children
924 while (dest.getChildCount() > 0) {
928 // Move all children to the destination rocket
929 while (src.getChildCount() > 0) {
930 RocketComponent c = src.getChild(0);
937 baseDocument.stopUndo();
942 private void resetDesign() {
945 documentCopy = baseDocument.copy();
947 loadOptimizationParameters();
948 loadSimulationModifiers();
950 // Replace selected modifiers with corresponding new modifiers
951 List<SimulationModifier> newSelected = new ArrayList<SimulationModifier>();
952 for (SimulationModifier original : selectedModifiers) {
953 List<SimulationModifier> newModifiers = simulationModifiers.get(original.getRelatedObject());
954 if (newModifiers != null) {
955 int index = newModifiers.indexOf(original);
957 SimulationModifier updated = newModifiers.get(index);
958 updated.setMinValue(original.getMinValue());
959 updated.setMaxValue(original.getMaxValue());
960 newSelected.add(updated);
964 selectedModifiers.clear();
965 selectedModifiers.addAll(newSelected);
966 selectedModifierTableModel.fireTableDataChanged();
968 // Update the available modifier tree
969 availableModifierTree.populateTree(documentCopy.getRocket(), simulationModifiers);
970 availableModifierTree.expandComponents();
973 // Update selectable simulations
974 populateSimulations();
976 // Update selectable parameters
977 populateParameters();
982 private void populateSimulations() {
983 String current = null;
984 Object selection = simulationSelectionCombo.getSelectedItem();
985 if (selection != null) {
986 current = selection.toString();
990 List<Named<Simulation>> simulations = new ArrayList<Named<Simulation>>();
991 Rocket rocket = documentCopy.getRocket();
993 for (Simulation s : documentCopy.getSimulations()) {
994 String id = s.getConfiguration().getMotorConfigurationID();
995 String name = createSimulationName(s.getName(), rocket.getMotorConfigurationNameOrDescription(id));
996 simulations.add(new Named<Simulation>(s, name));
999 for (String id : rocket.getMotorConfigurationIDs()) {
1003 Simulation sim = new Simulation(rocket);
1004 sim.getConfiguration().setMotorConfigurationID(id);
1005 String name = createSimulationName(trans.get("basicSimulationName"), rocket.getMotorConfigurationNameOrDescription(id));
1006 simulations.add(new Named<Simulation>(sim, name));
1010 Simulation sim = new Simulation(rocket);
1011 sim.getConfiguration().setMotorConfigurationID(null);
1012 String name = createSimulationName(trans.get("noSimulationName"), rocket.getMotorConfigurationNameOrDescription(null));
1013 simulations.add(new Named<Simulation>(sim, name));
1016 simulationSelectionCombo.setModel(new DefaultComboBoxModel(simulations.toArray()));
1017 simulationSelectionCombo.setSelectedIndex(0);
1018 if (current != null) {
1019 for (int i = 0; i < simulations.size(); i++) {
1020 if (simulations.get(i).toString().equals(current)) {
1021 simulationSelectionCombo.setSelectedIndex(i);
1029 private void populateParameters() {
1030 String current = null;
1031 Object selection = optimizationParameterCombo.getSelectedItem();
1032 if (selection != null) {
1033 current = selection.toString();
1035 // Default to apogee altitude event if it is not the first one in the list
1036 current = trans.get("MaximumAltitudeParameter.name");
1039 List<Named<OptimizableParameter>> parameters = new ArrayList<Named<OptimizableParameter>>();
1040 for (OptimizableParameter p : optimizationParameters) {
1041 parameters.add(new Named<OptimizableParameter>(p, p.getName()));
1044 optimizationParameterCombo.setModel(new DefaultComboBoxModel(parameters.toArray()));
1046 for (int i = 0; i < parameters.size(); i++) {
1047 if (parameters.get(i).toString().equals(current)) {
1048 optimizationParameterCombo.setSelectedIndex(i);
1054 private void updateCounters() {
1055 bestValueLabel.setText(bestValueUnit.toStringUnit(bestValue));
1056 stepCountLabel.setText("" + stepCount);
1057 evaluationCountLabel.setText("" + evaluationCount);
1058 stepSizeLabel.setText(UnitGroup.UNITS_RELATIVE.toStringUnit(stepSize));
1062 private void loadOptimizationParameters() {
1063 optimizationParameters.clear();
1064 optimizationParameters.addAll(OptimizationServiceHelper.getOptimizableParameters(documentCopy));
1066 if (optimizationParameters.isEmpty()) {
1067 throw new BugException("No rocket optimization parameters found, distribution built wrong.");
1070 Collections.sort(optimizationParameters, new Comparator<OptimizableParameter>() {
1072 public int compare(OptimizableParameter o1, OptimizableParameter o2) {
1073 return o1.getName().compareTo(o2.getName());
1079 private void loadSimulationModifiers() {
1080 simulationModifiers.clear();
1082 for (SimulationModifier m : OptimizationServiceHelper.getSimulationModifiers(documentCopy)) {
1083 Object key = m.getRelatedObject();
1084 List<SimulationModifier> list = simulationModifiers.get(key);
1086 list = new ArrayList<SimulationModifier>();
1087 simulationModifiers.put(key, list);
1092 for (Object key : simulationModifiers.keySet()) {
1093 List<SimulationModifier> list = simulationModifiers.get(key);
1094 Collections.sort(list, new Comparator<SimulationModifier>() {
1096 public int compare(SimulationModifier o1, SimulationModifier o2) {
1097 return o1.getName().compareTo(o2.getName());
1106 private void addModifier(SimulationModifier mod) {
1107 if (!selectedModifiers.contains(mod)) {
1108 log.user(1, "Adding simulation modifier " + mod);
1109 selectedModifiers.add(mod);
1110 Collections.sort(selectedModifiers, new SimulationModifierComparator());
1111 selectedModifierTableModel.fireTableDataChanged();
1112 availableModifierTree.repaint();
1114 log.user(1, "Attempting to add an already existing simulation modifier " + mod);
1119 private void removeModifier(SimulationModifier mod) {
1120 log.user(1, "Removing simulation modifier " + mod);
1121 selectedModifiers.remove(mod);
1122 selectedModifierTableModel.fireTableDataChanged();
1123 availableModifierTree.repaint();
1129 * Update the enabled status of all components in the dialog.
1131 private void updateComponents() {
1135 log.debug("Ignoring updateComponents");
1139 log.debug("Running updateComponents()");
1144 // First enable all components if optimization not running
1146 log.debug("Initially enabling all components");
1147 for (JComponent c : disableComponents) {
1154 SimulationModifier mod = getSelectedAvailableModifier();
1155 state = (mod != null && !selectedModifiers.contains(mod));
1156 log.debug("addButton enabled: " + state);
1157 addButton.setEnabled(state);
1160 state = (selectedModifierTable.getSelectedRow() >= 0);
1161 log.debug("removeButton enabled: " + state);
1162 removeButton.setEnabled(state);
1164 // "Remove all" button
1165 state = (!selectedModifiers.isEmpty());
1166 log.debug("removeAllButton enabled: " + state);
1167 removeAllButton.setEnabled(state);
1170 // Optimization goal
1171 String selected = (String) optimizationGoalCombo.getSelectedItem();
1172 state = GOAL_SEEK.equals(selected);
1173 log.debug("optimizationGoalSpinner & UnitSelector enabled: " + state);
1174 optimizationGoalSpinner.setVisible(state);
1175 optimizationGoalUnitSelector.setVisible(state);
1178 // Minimum/maximum stability options
1179 state = minimumStabilitySelected.isSelected();
1180 log.debug("minimumStabilitySpinner & UnitSelector enabled: " + state);
1181 minimumStabilitySpinner.setEnabled(state);
1182 minimumStabilityUnitSelector.setEnabled(state);
1184 state = maximumStabilitySelected.isSelected();
1185 log.debug("maximumStabilitySpimmer & UnitSelector enabled: " + state);
1186 maximumStabilitySpinner.setEnabled(state);
1187 maximumStabilityUnitSelector.setEnabled(state);
1190 // Plot button (enabled if path exists and dimensionality is 1 or 2)
1191 state = (!optimizationPath.isEmpty() && (selectedModifiers.size() == 1 || selectedModifiers.size() == 2));
1192 log.debug("plotButton enabled: " + state + " optimizationPath.isEmpty=" + optimizationPath.isEmpty() +
1193 " selectedModifiers.size=" + selectedModifiers.size());
1194 plotButton.setEnabled(state);
1196 // Save button (enabled if path exists)
1197 state = (!evaluationHistory.isEmpty());
1198 log.debug("saveButton enabled: " + state);
1199 saveButton.setEnabled(state);
1202 // Last disable all components if optimization is running
1204 log.debug("Disabling all components because optimization is running");
1205 for (JComponent c : disableComponents) {
1206 c.setEnabled(false);
1211 // Update description text
1212 mod = getSelectedModifier();
1214 selectedModifierDescription.setText(mod.getDescription());
1216 selectedModifierDescription.setText("");
1220 // Update the figure
1221 figure.setConfiguration(getSelectedSimulation().getConfiguration());
1227 private void savePath() {
1229 if (evaluationHistory.isEmpty()) {
1230 throw new BugException("evaluation history is empty");
1233 CsvOptionPanel csvOptions = new CsvOptionPanel(GeneralOptimizationDialog.class,
1234 trans.get("export.header"), trans.get("export.header.ttip"));
1237 JFileChooser chooser = new JFileChooser();
1238 chooser.setFileFilter(FileHelper.CSV_FILE_FILTER);
1239 chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
1240 chooser.setAccessory(csvOptions);
1242 if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
1245 File file = chooser.getSelectedFile();
1249 file = FileHelper.ensureExtension(file, "csv");
1250 if (!FileHelper.confirmWrite(file, this)) {
1254 String fieldSeparator = csvOptions.getFieldSeparator();
1255 String commentCharacter = csvOptions.getCommentCharacter();
1256 boolean includeHeader = csvOptions.getSelectionOption(0);
1257 csvOptions.storePreferences();
1259 log.info("Saving optimization path to " + file + ", fieldSeparator=" + fieldSeparator +
1260 ", commentCharacter=" + commentCharacter + ", includeHeader=" + includeHeader);
1263 Writer writer = new BufferedWriter(new FileWriter(file));
1266 if (includeHeader) {
1267 FunctionEvaluationData data = evaluationHistory.values().iterator().next();
1269 writer.write(commentCharacter);
1270 for (SimulationModifier mod : selectedModifiers) {
1271 writer.write(mod.getRelatedObject().toString() + ": " + mod.getName() + " / " +
1272 mod.getUnitGroup().getDefaultUnit().getUnit());
1273 writer.write(fieldSeparator);
1275 if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
1276 writer.write(trans.get("export.stability") + " / " + data.getDomainReference().getUnit().getUnit());
1277 writer.write(fieldSeparator);
1279 writer.write(getSelectedParameter().getName() + " / " +
1280 getSelectedParameter().getUnitGroup().getDefaultUnit().getUnit());
1286 for (FunctionEvaluationData data : evaluationHistory.values()) {
1287 Value[] state = data.getState();
1289 for (int i = 0; i < state.length; i++) {
1290 writer.write(TextUtil.doubleToString(state[i].getUnitValue()));
1291 writer.write(fieldSeparator);
1294 if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
1295 writer.write(TextUtil.doubleToString(data.getDomainReference().getUnitValue()));
1296 writer.write(fieldSeparator);
1299 if (data.getParameterValue() != null) {
1300 writer.write(TextUtil.doubleToString(data.getParameterValue().getUnitValue()));
1302 writer.write("N/A");
1308 log.info("File successfully saved");
1310 } catch (IOException e) {
1311 FileHelper.errorWriting(e, this);
1317 * Return the currently selected available simulation modifier from the modifier tree,
1318 * or <code>null</code> if none selected.
1320 private SimulationModifier getSelectedAvailableModifier() {
1321 TreePath treepath = availableModifierTree.getSelectionPath();
1322 if (treepath != null) {
1323 Object o = ((DefaultMutableTreeNode) treepath.getLastPathComponent()).getUserObject();
1324 if (o instanceof SimulationModifier) {
1325 return (SimulationModifier) o;
1332 * Return the currently selected simulation.
1333 * @return the selected simulation.
1335 @SuppressWarnings("unchecked")
1336 private Simulation getSelectedSimulation() {
1337 /* This is to debug a NPE where the returned selected item is null. */
1338 Object item = simulationSelectionCombo.getSelectedItem();
1340 String s = "Selected simulation is null:";
1341 s = s + " item count=" + simulationSelectionCombo.getItemCount();
1342 for (int i = 0; i < simulationSelectionCombo.getItemCount(); i++) {
1343 s = s + " [" + i + "]=" + simulationSelectionCombo.getItemAt(i);
1345 throw new BugException(s);
1347 return ((Named<Simulation>) item).get();
1352 * Return the currently selected simulation modifier from the table,
1353 * or <code>null</code> if none selected.
1354 * @return the selected modifier or <code>null</code>.
1356 private SimulationModifier getSelectedModifier() {
1357 int row = selectedModifierTable.getSelectedRow();
1361 row = selectedModifierTable.convertRowIndexToModel(row);
1362 return selectedModifiers.get(row);
1367 * Return the currently selected optimization parameter.
1368 * @return the selected optimization parameter.
1370 @SuppressWarnings("unchecked")
1371 private OptimizableParameter getSelectedParameter() {
1372 return ((Named<OptimizableParameter>) optimizationParameterCombo.getSelectedItem()).get();
1376 private Unit getModifierUnit(int index) {
1377 return selectedModifiers.get(index).getUnitGroup().getDefaultUnit();
1380 private String createSimulationName(String simulationName, String motorConfiguration) {
1382 boolean hasParenthesis = motorConfiguration.matches("^[\\[\\(].*[\\]\\)]$");
1383 name = simulationName + " ";
1384 if (!hasParenthesis) {
1387 name += motorConfiguration;
1388 if (!hasParenthesis) {
1395 * The table model for the parameter selection.
1397 * [Body tube: Length] [min] [max] [unit]
1399 private class ParameterSelectionTableModel extends AbstractTableModel {
1401 private static final int PARAMETER = 0;
1402 private static final int CURRENT = 1;
1403 private static final int MIN = 2;
1404 private static final int MAX = 3;
1405 private static final int COUNT = 4;
1408 public int getColumnCount() {
1413 public int getRowCount() {
1414 return selectedModifiers.size();
1418 public String getColumnName(int column) {
1421 return trans.get("table.col.parameter");
1423 return trans.get("table.col.current");
1425 return trans.get("table.col.min");
1427 return trans.get("table.col.max");
1429 throw new IndexOutOfBoundsException("column=" + column);
1435 public Class<?> getColumnClass(int column) {
1438 return String.class;
1440 return Double.class;
1442 return Double.class;
1444 return Double.class;
1446 throw new IndexOutOfBoundsException("column=" + column);
1451 public Object getValueAt(int row, int column) {
1453 SimulationModifier modifier = selectedModifiers.get(row);
1457 return modifier.getRelatedObject().toString() + ": " + modifier.getName();
1460 return getModifierUnit(row).toUnit(modifier.getCurrentSIValue(getSelectedSimulation()));
1461 } catch (OptimizationException e) {
1462 throw new BugException("Could not read current SI value from modifier " + modifier, e);
1465 return getModifierUnit(row).toUnit(modifier.getMinValue());
1467 return getModifierUnit(row).toUnit(modifier.getMaxValue());
1469 throw new IndexOutOfBoundsException("column=" + column);
1475 public void setValueAt(Object value, int row, int column) {
1477 if (row >= selectedModifiers.size()) {
1478 throw new BugException("setValueAt with invalid row: value=" + value + " row=" + row + " column=" + column +
1479 " selectedModifiers.size=" + selectedModifiers.size() + " selectedModifiers=" + selectedModifiers +
1480 " selectedModifierTable.getRowCount=" + selectedModifierTable.getRowCount());
1488 double min = (Double) value;
1489 min = getModifierUnit(row).fromUnit(min);
1490 selectedModifiers.get(row).setMinValue(min);
1497 double max = (Double) value;
1498 max = getModifierUnit(row).fromUnit(max);
1499 selectedModifiers.get(row).setMaxValue(max);
1503 throw new IndexOutOfBoundsException("column=" + column);
1505 this.fireTableRowsUpdated(row, row);
1510 public boolean isCellEditable(int row, int column) {
1521 throw new IndexOutOfBoundsException("column=" + column);
1528 private class DoubleCellRenderer extends DefaultTableCellRenderer {
1530 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
1531 boolean hasFocus, int row, int column) {
1533 super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1535 double val = (Double) value;
1536 Unit unit = getModifierUnit(row);
1538 val = unit.fromUnit(val);
1539 this.setText(unit.toStringUnit(val));
1546 private static class SimulationModifierComparator implements Comparator<SimulationModifier> {
1549 public int compare(SimulationModifier mod1, SimulationModifier mod2) {
1550 Object rel1 = mod1.getRelatedObject();
1551 Object rel2 = mod2.getRelatedObject();
1554 * Primarily order by related object:
1556 * - RocketComponents first
1557 * - Two RocketComponents are ordered based on their position in the rocket
1559 if (!rel1.equals(rel2)) {
1561 if (rel1 instanceof RocketComponent) {
1562 if (rel2 instanceof RocketComponent) {
1564 RocketComponent root = ((RocketComponent) rel1).getRoot();
1565 for (RocketComponent c : root) {
1566 if (c.equals(rel1)) {
1569 if (c.equals(rel2)) {
1574 throw new BugException("Error sorting modifiers, mod1=" + mod1 + " rel1=" + rel1 +
1575 " mod2=" + mod2 + " rel2=" + rel2);
1581 if (rel2 instanceof RocketComponent) {
1588 // Secondarily sort by name
1589 return collator.compare(mod1.getName(), mod2.getName());