create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / dialogs / optimization / GeneralOptimizationDialog.java
1 package net.sf.openrocket.gui.dialogs.optimization;
2
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;
10 import java.io.File;
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;
22 import java.util.Map;
23
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;
53
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;
96
97 import com.itextpdf.text.Font;
98
99
100 /**
101  * General rocket optimization dialog.
102  * 
103  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
104  */
105 public class GeneralOptimizationDialog extends JDialog {
106         private static final LogHelper log = Application.getLogger();
107         private static final Translator trans = Application.getTranslator();
108         
109         private static final Collator collator = Collator.getInstance();
110         
111         
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");
115         
116         private static final String START_TEXT = trans.get("btn.start");
117         private static final String STOP_TEXT = trans.get("btn.stop");
118         
119         
120         
121         private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>();
122         private final Map<Object, List<SimulationModifier>> simulationModifiers =
123                         new HashMap<Object, List<SimulationModifier>>();
124         
125         
126         private final OpenRocketDocument baseDocument;
127         private OpenRocketDocument documentCopy;
128         
129         
130         private final JButton addButton;
131         private final JButton removeButton;
132         private final JButton removeAllButton;
133         
134         private final ParameterSelectionTableModel selectedModifierTableModel;
135         private final JTable selectedModifierTable;
136         private final DescriptionArea selectedModifierDescription;
137         private final SimulationModifierTree availableModifierTree;
138         
139         private final JComboBox simulationSelectionCombo;
140         private final JComboBox optimizationParameterCombo;
141         
142         private final JComboBox optimizationGoalCombo;
143         private final JSpinner optimizationGoalSpinner;
144         private final UnitSelector optimizationGoalUnitSelector;
145         private final DoubleModel optimizationSeekValue;
146         
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;
155         
156         private final JLabel bestValueLabel;
157         private final JLabel stepCountLabel;
158         private final JLabel evaluationCountLabel;
159         private final JLabel stepSizeLabel;
160         
161         private final RocketFigure figure;
162         private final JToggleButton startButton;
163         private final JButton plotButton;
164         private final JButton saveButton;
165         
166         private final JButton applyButton;
167         private final JButton resetButton;
168         private final JButton closeButton;
169         
170         private final List<SimulationModifier> selectedModifiers = new ArrayList<SimulationModifier>();
171         
172         /** List of components to disable while optimization is running */
173         private final List<JComponent> disableComponents = new ArrayList<JComponent>();
174         
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;
179         
180         
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;
186         
187         private final Map<Point, FunctionEvaluationData> evaluationHistory = new LinkedHashMap<Point, FunctionEvaluationData>();
188         private final List<Point> optimizationPath = new LinkedList<Point>();
189         
190         
191         private boolean updating = false;
192         
193         
194         /**
195          * Sole constructor.
196          * 
197          * @param document      the document
198          * @param parent        the parent window
199          */
200         public GeneralOptimizationDialog(OpenRocketDocument document, Window parent) {
201                 super(parent, trans.get("title"));
202                 
203                 this.baseDocument = document;
204                 this.documentCopy = document.copy();
205                 
206                 loadOptimizationParameters();
207                 loadSimulationModifiers();
208                 
209                 JPanel sub;
210                 JLabel label;
211                 JScrollPane scroll;
212                 String tip;
213                 
214                 JPanel panel = new JPanel(new MigLayout("fill"));
215                 
216                 
217                 ChangeListener clearHistoryChangeListener = new ChangeListener() {
218                         @Override
219                         public void stateChanged(ChangeEvent e) {
220                                 clearHistory();
221                         }
222                 };
223                 ActionListener clearHistoryActionListener = new ActionListener() {
224                         @Override
225                         public void actionPerformed(ActionEvent e) {
226                                 clearHistory();
227                         }
228                 };
229                 
230                 
231                 
232                 //// Selected modifiers table
233                 
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);
240                 
241                 // Make sure spinner editor fits into the cell height
242                 selectedModifierTable.setRowHeight(new JSpinner().getPreferredSize().height - 4);
243                 
244                 selectedModifierTable.setDefaultEditor(Double.class, new DoubleCellEditor());
245                 selectedModifierTable.setDefaultEditor(Unit.class, new UnitCellEditor() {
246                         @Override
247                         protected UnitGroup getUnitGroup(Unit value, int row, int column) {
248                                 return selectedModifiers.get(row).getUnitGroup();
249                         }
250                 });
251                 
252                 disableComponents.add(selectedModifierTable);
253                 
254                 selectedModifierTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
255                         @Override
256                         public void valueChanged(ListSelectionEvent e) {
257                                 updateComponents();
258                         }
259                 });
260                 
261                 // Set column widths
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);
267                 
268                 scroll = new JScrollPane(selectedModifierTable);
269                 
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");
277                 
278                 
279                 
280                 //// Add/remove buttons
281                 sub = new JPanel(new MigLayout("fill"));
282                 
283                 addButton = new JButton(Chars.LEFT_ARROW + " " + trans.get("btn.add") + "   ");
284                 addButton.setToolTipText(trans.get("btn.add.ttip"));
285                 addButton.addActionListener(new ActionListener() {
286                         @Override
287                         public void actionPerformed(ActionEvent e) {
288                                 SimulationModifier mod = getSelectedAvailableModifier();
289                                 if (mod != null) {
290                                         addModifier(mod);
291                                         clearHistory();
292                                 } else {
293                                         log.error("Attempting to add simulation modifier when none is selected");
294                                 }
295                         }
296                 });
297                 disableComponents.add(addButton);
298                 sub.add(addButton, "wrap para, sg button");
299                 
300                 removeButton = new JButton("   " + trans.get("btn.remove") + " " + Chars.RIGHT_ARROW);
301                 removeButton.setToolTipText(trans.get("btn.remove.ttip"));
302                 removeButton.addActionListener(new ActionListener() {
303                         @Override
304                         public void actionPerformed(ActionEvent e) {
305                                 SimulationModifier mod = getSelectedModifier();
306                                 if (mod == null) {
307                                         log.error("Attempting to remove simulation modifier when none is selected");
308                                         return;
309                                 }
310                                 removeModifier(mod);
311                                 clearHistory();
312                         }
313                 });
314                 disableComponents.add(removeButton);
315                 sub.add(removeButton, "wrap para*2, sg button");
316                 
317                 removeAllButton = new JButton(trans.get("btn.removeAll"));
318                 removeAllButton.setToolTipText(trans.get("btn.removeAll.ttip"));
319                 removeAllButton.addActionListener(new ActionListener() {
320                         @Override
321                         public void actionPerformed(ActionEvent e) {
322                                 log.user("Removing all selected modifiers");
323                                 selectedModifiers.clear();
324                                 selectedModifierTableModel.fireTableDataChanged();
325                                 availableModifierTree.repaint();
326                                 clearHistory();
327                         }
328                 });
329                 disableComponents.add(removeAllButton);
330                 sub.add(removeAllButton, "wrap para, sg button");
331                 
332                 panel.add(sub);
333                 
334                 
335                 
336                 //// Available modifier tree
337                 availableModifierTree = new SimulationModifierTree(documentCopy.getRocket(), simulationModifiers, selectedModifiers);
338                 availableModifierTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
339                         @Override
340                         public void valueChanged(TreeSelectionEvent e) {
341                                 updateComponents();
342                         }
343                 });
344                 
345                 // Handle double-click
346                 availableModifierTree.addMouseListener(new MouseAdapter() {
347                         @Override
348                         public void mousePressed(MouseEvent e) {
349                                 if (e.getClickCount() == 2) {
350                                         SimulationModifier mod = getSelectedAvailableModifier();
351                                         if (mod != null) {
352                                                 addModifier(mod);
353                                                 clearHistory();
354                                         } else {
355                                                 log.user("Double-clicked non-available option");
356                                         }
357                                 }
358                         }
359                 });
360                 
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");
367                 
368                 
369                 
370                 
371                 ////  Optimization options sub-panel
372                 
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);
378                 
379                 
380                 //// Simulation to optimize
381                 
382                 label = new JLabel(trans.get("lbl.optimizeSim"));
383                 tip = trans.get("lbl.optimizeSim.ttip");
384                 label.setToolTipText(tip);
385                 disableComponents.add(label);
386                 sub.add(label, "");
387                 
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");
394                 
395                 
396                 
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);
402                 sub.add(label, "");
403                 
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");
410                 
411                 
412                 
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);
418                 sub.add(label, "");
419                 
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");
426                 
427                 
428                 //// Optimization custom value
429                 optimizationSeekValue = new DoubleModel(0, UnitGroup.UNITS_NONE);
430                 optimizationSeekValue.addChangeListener(clearHistoryChangeListener);
431                 
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");
438                 
439                 optimizationGoalUnitSelector = new UnitSelector(optimizationSeekValue);
440                 optimizationGoalUnitSelector.setToolTipText(tip);
441                 disableComponents.add(optimizationGoalUnitSelector);
442                 sub.add(optimizationGoalUnitSelector, "width 20lp, wrap unrel");
443                 
444                 
445                 panel.add(sub, "grow");
446                 
447                 
448                 
449                 ////  Required stability sub-panel
450                 
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);
456                 
457                 
458                 
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);
464                 
465                 
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() {
472                         @Override
473                         public void actionPerformed(ActionEvent e) {
474                                 updateComponents();
475                         }
476                 });
477                 disableComponents.add(minimumStabilitySelected);
478                 sub.add(minimumStabilitySelected);
479                 
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");
485                 
486                 minimumStabilityUnitSelector = new UnitSelector(minimumStability);
487                 minimumStabilityUnitSelector.setToolTipText(tip);
488                 disableComponents.add(minimumStabilityUnitSelector);
489                 sub.add(minimumStabilityUnitSelector, "growx, wrap unrel");
490                 
491                 
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() {
497                         @Override
498                         public void actionPerformed(ActionEvent e) {
499                                 updateComponents();
500                         }
501                 });
502                 disableComponents.add(maximumStabilitySelected);
503                 sub.add(maximumStabilitySelected);
504                 
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");
510                 
511                 maximumStabilityUnitSelector = new UnitSelector(maximumStability);
512                 maximumStabilityUnitSelector.setToolTipText(tip);
513                 disableComponents.add(maximumStabilityUnitSelector);
514                 sub.add(maximumStabilityUnitSelector, "growx, wrap para");
515                 
516                 
517                 
518                 //              DescriptionArea desc = new DescriptionArea("Stability requirements are verified during each time step of the simulation.",
519                 //                              2, -2, false);
520                 //              desc.setViewportBorder(null);
521                 //              disableComponents.add(desc);
522                 //              sub.add(desc, "span, growx");
523                 
524                 
525                 panel.add(sub, "span 2, grow, wrap para*2");
526                 
527                 
528                 
529                 
530                 ////  Rocket figure
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");
536                 
537                 
538                 sub = new JPanel(new MigLayout("fill"));
539                 
540                 
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");
545                 
546                 bestValueLabel = new JLabel();
547                 bestValueLabel.setToolTipText(tip);
548                 sub.add(bestValueLabel, "wmin 60lp, wrap rel");
549                 
550                 
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");
555                 
556                 stepCountLabel = new JLabel();
557                 stepCountLabel.setToolTipText(tip);
558                 sub.add(stepCountLabel, "wrap rel");
559                 
560                 
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");
565                 
566                 evaluationCountLabel = new JLabel();
567                 evaluationCountLabel.setToolTipText(tip);
568                 sub.add(evaluationCountLabel, "wrap rel");
569                 
570                 
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");
575                 
576                 stepSizeLabel = new JLabel();
577                 stepSizeLabel.setToolTipText(tip);
578                 sub.add(stepSizeLabel, "wrap para");
579                 
580                 
581                 //// Start/Stop button
582                 
583                 startButton = new JToggleButton(START_TEXT);
584                 startButton.addActionListener(new ActionListener() {
585                         @Override
586                         public void actionPerformed(ActionEvent e) {
587                                 if (updating) {
588                                         log.debug("Updating, ignoring event");
589                                         return;
590                                 }
591                                 if (running) {
592                                         log.user("Stopping optimization");
593                                         stopOptimization();
594                                 } else {
595                                         log.user("Starting optimization");
596                                         startOptimization();
597                                 }
598                         }
599                 });
600                 sub.add(startButton, "span, growx, wrap para*2");
601                 
602                 
603                 plotButton = new JButton(trans.get("btn.plotPath"));
604                 plotButton.setToolTipText(trans.get("btn.plotPath.ttip"));
605                 plotButton.addActionListener(new ActionListener() {
606                         @Override
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);
617                         }
618                 });
619                 disableComponents.add(plotButton);
620                 sub.add(plotButton, "span, growx, wrap");
621                 
622                 
623                 saveButton = new JButton(trans.get("btn.save"));
624                 saveButton.setToolTipText(trans.get("btn.save.ttip"));
625                 saveButton.addActionListener(new ActionListener() {
626                         @Override
627                         public void actionPerformed(ActionEvent e) {
628                                 log.user("User selected save path");
629                                 savePath();
630                         }
631                 });
632                 disableComponents.add(saveButton);
633                 sub.add(saveButton, "span, growx");
634                 
635                 
636                 
637                 panel.add(sub, "wrap para*2");
638                 
639                 
640                 
641                 
642                 ////  Bottom buttons
643                 
644                 applyButton = new JButton(trans.get("btn.apply"));
645                 applyButton.setToolTipText(trans.get("btn.apply.ttip"));
646                 applyButton.addActionListener(new ActionListener() {
647                         @Override
648                         public void actionPerformed(ActionEvent e) {
649                                 log.user("Applying optimization changes");
650                                 applyDesign();
651                         }
652                 });
653                 disableComponents.add(applyButton);
654                 panel.add(applyButton, "span, split, gapright para, right");
655                 
656                 resetButton = new JButton(trans.get("btn.reset"));
657                 resetButton.setToolTipText(trans.get("btn.reset.ttip"));
658                 resetButton.addActionListener(new ActionListener() {
659                         @Override
660                         public void actionPerformed(ActionEvent e) {
661                                 log.user("Resetting optimization design");
662                                 resetDesign();
663                         }
664                 });
665                 disableComponents.add(resetButton);
666                 panel.add(resetButton, "gapright para, right");
667                 
668                 closeButton = new JButton(trans.get("btn.close"));
669                 closeButton.setToolTipText(trans.get("btn.close.ttip"));
670                 closeButton.addActionListener(new ActionListener() {
671                         @Override
672                         public void actionPerformed(ActionEvent e) {
673                                 log.user("Closing optimization dialog");
674                                 stopOptimization();
675                                 GeneralOptimizationDialog.this.dispose();
676                         }
677                 });
678                 panel.add(closeButton, "right");
679                 
680                 
681                 this.add(panel);
682                 clearHistory();
683                 updateComponents();
684                 GUIUtil.setDisposableDialogOptions(this, null);
685         }
686         
687         
688         private void startOptimization() {
689                 if (running) {
690                         log.info("Optimization already running");
691                         return;
692                 }
693                 
694                 
695                 if (selectedModifiers.isEmpty()) {
696                         JOptionPane.showMessageDialog(this, trans.get("error.selectParams.text"),
697                                         trans.get("error.selectParams.title"), JOptionPane.ERROR_MESSAGE);
698                         updating = true;
699                         startButton.setSelected(false);
700                         startButton.setText(START_TEXT);
701                         updating = false;
702                         return;
703                 }
704                 
705                 
706                 running = true;
707                 
708                 // Update the button status
709                 updating = true;
710                 startButton.setSelected(true);
711                 startButton.setText(STOP_TEXT);
712                 updating = false;
713                 
714                 
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);
719                 
720                 OptimizableParameter parameter = getSelectedParameter();
721                 
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());
730                 } else {
731                         throw new BugException("optimizationGoalCombo had invalid value: " + value);
732                 }
733                 
734                 SimulationDomain domain;
735                 if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
736                         double min, max;
737                         boolean minAbsolute, maxAbsolute;
738                         
739                         /*
740                          * Make minAbsolute/maxAbsolute consistent with each other to produce reasonable
741                          * result in plot tool tips.  Yes, this is a bit ugly.
742                          */
743                         
744                         // Min stability
745                         Unit unit = minimumStability.getCurrentUnit();
746                         if (unit instanceof CaliberUnit) {
747                                 min = unit.toUnit(minimumStability.getValue());
748                                 minAbsolute = false;
749                         } else {
750                                 min = minimumStability.getValue();
751                                 minAbsolute = true;
752                         }
753                         
754                         // Max stability
755                         unit = maximumStability.getCurrentUnit();
756                         if (unit instanceof CaliberUnit) {
757                                 max = unit.toUnit(maximumStability.getValue());
758                                 maxAbsolute = false;
759                         } else {
760                                 max = maximumStability.getValue();
761                                 maxAbsolute = true;
762                         }
763                         
764                         
765                         if (!minimumStabilitySelected.isSelected()) {
766                                 min = Double.NaN;
767                                 minAbsolute = maxAbsolute;
768                         }
769                         if (!maximumStabilitySelected.isSelected()) {
770                                 max = Double.NaN;
771                                 maxAbsolute = minAbsolute;
772                         }
773                         
774                         domain = new StabilityDomain(min, minAbsolute, max, maxAbsolute);
775                 } else {
776                         domain = new IdentitySimulationDomain();
777                 }
778                 
779                 SimulationModifier[] modifiers = selectedModifiers.toArray(new SimulationModifier[0]);
780                 
781                 // Create and start the background worker
782                 worker = new OptimizationWorker(simulation, parameter, goal, domain, modifiers) {
783                         @Override
784                         protected void done(OptimizationException exception) {
785                                 log.info("Optimization finished, exception=" + exception, exception);
786                                 
787                                 if (exception != null) {
788                                         JOptionPane.showMessageDialog(GeneralOptimizationDialog.this,
789                                                         new Object[] {
790                                                                         trans.get("error.optimizationFailure.text"),
791                                                                         exception.getLocalizedMessage()
792                                                         }, trans.get("error.optimizationFailure.title"), JOptionPane.ERROR_MESSAGE);
793                                 }
794                                 
795                                 worker = null;
796                                 stopOptimization();
797                                 
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() {
802                                         @Override
803                                         public void actionPerformed(ActionEvent e) {
804                                                 startButton.setEnabled(true);
805                                         }
806                                 });
807                                 timer.setRepeats(false);
808                                 timer.start();
809                                 updateComponents();
810                         }
811                         
812                         @Override
813                         protected void functionEvaluated(List<FunctionEvaluationData> data) {
814                                 for (FunctionEvaluationData d : data) {
815                                         evaluationHistory.put(d.getPoint(), d);
816                                         evaluationCount++;
817                                 }
818                                 updateCounters();
819                         }
820                         
821                         @Override
822                         protected void optimizationStepTaken(List<OptimizationStepData> data) {
823                                 
824                                 // Add starting point to the path
825                                 if (optimizationPath.isEmpty()) {
826                                         optimizationPath.add(data.get(0).getOldPoint());
827                                 }
828                                 
829                                 // Add other points to the path
830                                 for (OptimizationStepData d : data) {
831                                         optimizationPath.add(d.getNewPoint());
832                                 }
833                                 
834                                 // Get function value from the latest point
835                                 OptimizationStepData latest = data.get(data.size() - 1);
836                                 Point newPoint = latest.getNewPoint();
837                                 
838                                 FunctionEvaluationData pointValue = evaluationHistory.get(newPoint);
839                                 if (pointValue != null && pointValue.getParameterValue() != null) {
840                                         bestValue = pointValue.getParameterValue().getValue();
841                                 } else {
842                                         bestValue = Double.NaN;
843                                 }
844                                 
845                                 // Update the simulation
846                                 Simulation sim = getSelectedSimulation();
847                                 for (int i = 0; i < newPoint.dim(); i++) {
848                                         try {
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);
853                                         }
854                                 }
855                                 figure.updateFigure();
856                                 
857                                 // Update other counter data
858                                 stepCount += data.size();
859                                 stepSize = latest.getStepSize();
860                                 updateCounters();
861                         }
862                 };
863                 worker.start();
864                 
865                 
866                 clearHistory();
867                 
868                 updateComponents();
869         }
870         
871         private void stopOptimization() {
872                 if (!running) {
873                         log.info("Optimization not running");
874                         return;
875                 }
876                 
877                 if (worker != null && worker.isAlive()) {
878                         log.info("Worker still running, interrupting it and setting to null");
879                         worker.interrupt();
880                         worker = null;
881                         return;
882                 }
883                 
884                 running = false;
885                 
886                 // Update the button status
887                 updating = true;
888                 startButton.setSelected(false);
889                 startButton.setText(START_TEXT);
890                 updating = false;
891                 
892                 
893                 updateComponents();
894         }
895         
896         
897         
898         
899         /**
900          * Reset the current optimization history and values.  This does not reset the design.
901          */
902         private void clearHistory() {
903                 evaluationHistory.clear();
904                 optimizationPath.clear();
905                 bestValue = Double.NaN;
906                 bestValueUnit = getSelectedParameter().getUnitGroup().getDefaultUnit();
907                 stepCount = 0;
908                 evaluationCount = 0;
909                 stepSize = 0.5;
910                 updateCounters();
911                 updateComponents();
912         }
913         
914         
915         private void applyDesign() {
916                 // TODO: MEDIUM: Apply also potential changes to simulations
917                 Rocket src = getSelectedSimulation().getRocket().copyWithOriginalID();
918                 Rocket dest = baseDocument.getRocket();
919                 try {
920                         baseDocument.startUndo(trans.get("undoText"));
921                         dest.freeze();
922                         
923                         // Remove all children
924                         while (dest.getChildCount() > 0) {
925                                 dest.removeChild(0);
926                         }
927                         
928                         // Move all children to the destination rocket
929                         while (src.getChildCount() > 0) {
930                                 RocketComponent c = src.getChild(0);
931                                 src.removeChild(0);
932                                 dest.addChild(c);
933                         }
934                         
935                 } finally {
936                         dest.thaw();
937                         baseDocument.stopUndo();
938                 }
939         }
940         
941         
942         private void resetDesign() {
943                 clearHistory();
944                 
945                 documentCopy = baseDocument.copy();
946                 
947                 loadOptimizationParameters();
948                 loadSimulationModifiers();
949                 
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);
956                                 if (index >= 0) {
957                                         SimulationModifier updated = newModifiers.get(index);
958                                         updated.setMinValue(original.getMinValue());
959                                         updated.setMaxValue(original.getMaxValue());
960                                         newSelected.add(updated);
961                                 }
962                         }
963                 }
964                 selectedModifiers.clear();
965                 selectedModifiers.addAll(newSelected);
966                 selectedModifierTableModel.fireTableDataChanged();
967                 
968                 // Update the available modifier tree
969                 availableModifierTree.populateTree(documentCopy.getRocket(), simulationModifiers);
970                 availableModifierTree.expandComponents();
971                 
972                 
973                 // Update selectable simulations
974                 populateSimulations();
975                 
976                 // Update selectable parameters
977                 populateParameters();
978                 
979         }
980         
981         
982         private void populateSimulations() {
983                 String current = null;
984                 Object selection = simulationSelectionCombo.getSelectedItem();
985                 if (selection != null) {
986                         current = selection.toString();
987                 }
988                 
989                 
990                 List<Named<Simulation>> simulations = new ArrayList<Named<Simulation>>();
991                 Rocket rocket = documentCopy.getRocket();
992                 
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));
997                 }
998                 
999                 for (String id : rocket.getMotorConfigurationIDs()) {
1000                         if (id == null) {
1001                                 continue;
1002                         }
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));
1007                 }
1008                 
1009                 
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));
1014                 
1015                 
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);
1022                                         break;
1023                                 }
1024                         }
1025                 }
1026         }
1027         
1028         
1029         private void populateParameters() {
1030                 String current = null;
1031                 Object selection = optimizationParameterCombo.getSelectedItem();
1032                 if (selection != null) {
1033                         current = selection.toString();
1034                 } else {
1035                         // Default to apogee altitude event if it is not the first one in the list
1036                         current = trans.get("MaximumAltitudeParameter.name");
1037                 }
1038                 
1039                 List<Named<OptimizableParameter>> parameters = new ArrayList<Named<OptimizableParameter>>();
1040                 for (OptimizableParameter p : optimizationParameters) {
1041                         parameters.add(new Named<OptimizableParameter>(p, p.getName()));
1042                 }
1043                 
1044                 optimizationParameterCombo.setModel(new DefaultComboBoxModel(parameters.toArray()));
1045                 
1046                 for (int i = 0; i < parameters.size(); i++) {
1047                         if (parameters.get(i).toString().equals(current)) {
1048                                 optimizationParameterCombo.setSelectedIndex(i);
1049                                 break;
1050                         }
1051                 }
1052         }
1053         
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));
1059         }
1060         
1061         
1062         private void loadOptimizationParameters() {
1063                 optimizationParameters.clear();
1064                 optimizationParameters.addAll(OptimizationServiceHelper.getOptimizableParameters(documentCopy));
1065                 
1066                 if (optimizationParameters.isEmpty()) {
1067                         throw new BugException("No rocket optimization parameters found, distribution built wrong.");
1068                 }
1069                 
1070                 Collections.sort(optimizationParameters, new Comparator<OptimizableParameter>() {
1071                         @Override
1072                         public int compare(OptimizableParameter o1, OptimizableParameter o2) {
1073                                 return o1.getName().compareTo(o2.getName());
1074                         }
1075                 });
1076         }
1077         
1078         
1079         private void loadSimulationModifiers() {
1080                 simulationModifiers.clear();
1081                 
1082                 for (SimulationModifier m : OptimizationServiceHelper.getSimulationModifiers(documentCopy)) {
1083                         Object key = m.getRelatedObject();
1084                         List<SimulationModifier> list = simulationModifiers.get(key);
1085                         if (list == null) {
1086                                 list = new ArrayList<SimulationModifier>();
1087                                 simulationModifiers.put(key, list);
1088                         }
1089                         list.add(m);
1090                 }
1091                 
1092                 for (Object key : simulationModifiers.keySet()) {
1093                         List<SimulationModifier> list = simulationModifiers.get(key);
1094                         Collections.sort(list, new Comparator<SimulationModifier>() {
1095                                 @Override
1096                                 public int compare(SimulationModifier o1, SimulationModifier o2) {
1097                                         return o1.getName().compareTo(o2.getName());
1098                                 }
1099                         });
1100                 }
1101                 
1102         }
1103         
1104         
1105         
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();
1113                 } else {
1114                         log.user(1, "Attempting to add an already existing simulation modifier " + mod);
1115                 }
1116         }
1117         
1118         
1119         private void removeModifier(SimulationModifier mod) {
1120                 log.user(1, "Removing simulation modifier " + mod);
1121                 selectedModifiers.remove(mod);
1122                 selectedModifierTableModel.fireTableDataChanged();
1123                 availableModifierTree.repaint();
1124         }
1125         
1126         
1127         
1128         /**
1129          * Update the enabled status of all components in the dialog.
1130          */
1131         private void updateComponents() {
1132                 boolean state;
1133                 
1134                 if (updating) {
1135                         log.debug("Ignoring updateComponents");
1136                         return;
1137                 }
1138                 
1139                 log.debug("Running updateComponents()");
1140                 
1141                 updating = true;
1142                 
1143                 
1144                 // First enable all components if optimization not running
1145                 if (!running) {
1146                         log.debug("Initially enabling all components");
1147                         for (JComponent c : disableComponents) {
1148                                 c.setEnabled(true);
1149                         }
1150                 }
1151                 
1152                 
1153                 // "Add" button
1154                 SimulationModifier mod = getSelectedAvailableModifier();
1155                 state = (mod != null && !selectedModifiers.contains(mod));
1156                 log.debug("addButton enabled: " + state);
1157                 addButton.setEnabled(state);
1158                 
1159                 // "Remove" button
1160                 state = (selectedModifierTable.getSelectedRow() >= 0);
1161                 log.debug("removeButton enabled: " + state);
1162                 removeButton.setEnabled(state);
1163                 
1164                 // "Remove all" button
1165                 state = (!selectedModifiers.isEmpty());
1166                 log.debug("removeAllButton enabled: " + state);
1167                 removeAllButton.setEnabled(state);
1168                 
1169                 
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);
1176                 
1177                 
1178                 // Minimum/maximum stability options
1179                 state = minimumStabilitySelected.isSelected();
1180                 log.debug("minimumStabilitySpinner & UnitSelector enabled: " + state);
1181                 minimumStabilitySpinner.setEnabled(state);
1182                 minimumStabilityUnitSelector.setEnabled(state);
1183                 
1184                 state = maximumStabilitySelected.isSelected();
1185                 log.debug("maximumStabilitySpimmer & UnitSelector enabled: " + state);
1186                 maximumStabilitySpinner.setEnabled(state);
1187                 maximumStabilityUnitSelector.setEnabled(state);
1188                 
1189                 
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);
1195                 
1196                 // Save button (enabled if path exists)
1197                 state = (!evaluationHistory.isEmpty());
1198                 log.debug("saveButton enabled: " + state);
1199                 saveButton.setEnabled(state);
1200                 
1201                 
1202                 // Last disable all components if optimization is running
1203                 if (running) {
1204                         log.debug("Disabling all components because optimization is running");
1205                         for (JComponent c : disableComponents) {
1206                                 c.setEnabled(false);
1207                         }
1208                 }
1209                 
1210                 
1211                 // Update description text
1212                 mod = getSelectedModifier();
1213                 if (mod != null) {
1214                         selectedModifierDescription.setText(mod.getDescription());
1215                 } else {
1216                         selectedModifierDescription.setText("");
1217                 }
1218                 
1219                 
1220                 // Update the figure
1221                 figure.setConfiguration(getSelectedSimulation().getConfiguration());
1222                 
1223                 updating = false;
1224         }
1225         
1226         
1227         private void savePath() {
1228                 
1229                 if (evaluationHistory.isEmpty()) {
1230                         throw new BugException("evaluation history is empty");
1231                 }
1232                 
1233                 CsvOptionPanel csvOptions = new CsvOptionPanel(GeneralOptimizationDialog.class,
1234                                 trans.get("export.header"), trans.get("export.header.ttip"));
1235                 
1236                 
1237                 JFileChooser chooser = new JFileChooser();
1238                 chooser.setFileFilter(FileHelper.CSV_FILE_FILTER);
1239                 chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
1240                 chooser.setAccessory(csvOptions);
1241                 
1242                 if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
1243                         return;
1244                 
1245                 File file = chooser.getSelectedFile();
1246                 if (file == null)
1247                         return;
1248                 
1249                 file = FileHelper.ensureExtension(file, "csv");
1250                 if (!FileHelper.confirmWrite(file, this)) {
1251                         return;
1252                 }
1253                 
1254                 String fieldSeparator = csvOptions.getFieldSeparator();
1255                 String commentCharacter = csvOptions.getCommentCharacter();
1256                 boolean includeHeader = csvOptions.getSelectionOption(0);
1257                 csvOptions.storePreferences();
1258                 
1259                 log.info("Saving optimization path to " + file + ", fieldSeparator=" + fieldSeparator +
1260                                 ", commentCharacter=" + commentCharacter + ", includeHeader=" + includeHeader);
1261                 
1262                 try {
1263                         Writer writer = new BufferedWriter(new FileWriter(file));
1264                         
1265                         // Write header
1266                         if (includeHeader) {
1267                                 FunctionEvaluationData data = evaluationHistory.values().iterator().next();
1268                                 
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);
1274                                 }
1275                                 if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
1276                                         writer.write(trans.get("export.stability") + " / " + data.getDomainReference().getUnit().getUnit());
1277                                         writer.write(fieldSeparator);
1278                                 }
1279                                 writer.write(getSelectedParameter().getName() + " / " +
1280                                                 getSelectedParameter().getUnitGroup().getDefaultUnit().getUnit());
1281                                 
1282                                 writer.write("\n");
1283                         }
1284                         
1285                         
1286                         for (FunctionEvaluationData data : evaluationHistory.values()) {
1287                                 Value[] state = data.getState();
1288                                 
1289                                 for (int i = 0; i < state.length; i++) {
1290                                         writer.write(TextUtil.doubleToString(state[i].getUnitValue()));
1291                                         writer.write(fieldSeparator);
1292                                 }
1293                                 
1294                                 if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
1295                                         writer.write(TextUtil.doubleToString(data.getDomainReference().getUnitValue()));
1296                                         writer.write(fieldSeparator);
1297                                 }
1298                                 
1299                                 if (data.getParameterValue() != null) {
1300                                         writer.write(TextUtil.doubleToString(data.getParameterValue().getUnitValue()));
1301                                 } else {
1302                                         writer.write("N/A");
1303                                 }
1304                                 writer.write("\n");
1305                         }
1306                         
1307                         writer.close();
1308                         log.info("File successfully saved");
1309                         
1310                 } catch (IOException e) {
1311                         FileHelper.errorWriting(e, this);
1312                 }
1313                 
1314         }
1315         
1316         /**
1317          * Return the currently selected available simulation modifier from the modifier tree,
1318          * or <code>null</code> if none selected.
1319          */
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;
1326                         }
1327                 }
1328                 return null;
1329         }
1330         
1331         /**
1332          * Return the currently selected simulation.
1333          * @return      the selected simulation.
1334          */
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();
1339                 if (item == null) {
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);
1344                         }
1345                         throw new BugException(s);
1346                 }
1347                 return ((Named<Simulation>) item).get();
1348         }
1349         
1350         
1351         /**
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>.
1355          */
1356         private SimulationModifier getSelectedModifier() {
1357                 int row = selectedModifierTable.getSelectedRow();
1358                 if (row < 0) {
1359                         return null;
1360                 }
1361                 row = selectedModifierTable.convertRowIndexToModel(row);
1362                 return selectedModifiers.get(row);
1363         }
1364         
1365         
1366         /**
1367          * Return the currently selected optimization parameter.
1368          * @return      the selected optimization parameter.
1369          */
1370         @SuppressWarnings("unchecked")
1371         private OptimizableParameter getSelectedParameter() {
1372                 return ((Named<OptimizableParameter>) optimizationParameterCombo.getSelectedItem()).get();
1373         }
1374         
1375         
1376         private Unit getModifierUnit(int index) {
1377                 return selectedModifiers.get(index).getUnitGroup().getDefaultUnit();
1378         }
1379         
1380         private String createSimulationName(String simulationName, String motorConfiguration) {
1381                 String name;
1382                 boolean hasParenthesis = motorConfiguration.matches("^[\\[\\(].*[\\]\\)]$");
1383                 name = simulationName + " ";
1384                 if (!hasParenthesis) {
1385                         name += "(";
1386                 }
1387                 name += motorConfiguration;
1388                 if (!hasParenthesis) {
1389                         name += ")";
1390                 }
1391                 return name;
1392         }
1393         
1394         /**
1395          * The table model for the parameter selection.
1396          * 
1397          * [Body tube: Length]  [min]  [max]  [unit]
1398          */
1399         private class ParameterSelectionTableModel extends AbstractTableModel {
1400                 
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;
1406                 
1407                 @Override
1408                 public int getColumnCount() {
1409                         return COUNT;
1410                 }
1411                 
1412                 @Override
1413                 public int getRowCount() {
1414                         return selectedModifiers.size();
1415                 }
1416                 
1417                 @Override
1418                 public String getColumnName(int column) {
1419                         switch (column) {
1420                         case PARAMETER:
1421                                 return trans.get("table.col.parameter");
1422                         case CURRENT:
1423                                 return trans.get("table.col.current");
1424                         case MIN:
1425                                 return trans.get("table.col.min");
1426                         case MAX:
1427                                 return trans.get("table.col.max");
1428                         default:
1429                                 throw new IndexOutOfBoundsException("column=" + column);
1430                         }
1431                         
1432                 }
1433                 
1434                 @Override
1435                 public Class<?> getColumnClass(int column) {
1436                         switch (column) {
1437                         case PARAMETER:
1438                                 return String.class;
1439                         case CURRENT:
1440                                 return Double.class;
1441                         case MIN:
1442                                 return Double.class;
1443                         case MAX:
1444                                 return Double.class;
1445                         default:
1446                                 throw new IndexOutOfBoundsException("column=" + column);
1447                         }
1448                 }
1449                 
1450                 @Override
1451                 public Object getValueAt(int row, int column) {
1452                         
1453                         SimulationModifier modifier = selectedModifiers.get(row);
1454                         
1455                         switch (column) {
1456                         case PARAMETER:
1457                                 return modifier.getRelatedObject().toString() + ": " + modifier.getName();
1458                         case CURRENT:
1459                                 try {
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);
1463                                 }
1464                         case MIN:
1465                                 return getModifierUnit(row).toUnit(modifier.getMinValue());
1466                         case MAX:
1467                                 return getModifierUnit(row).toUnit(modifier.getMaxValue());
1468                         default:
1469                                 throw new IndexOutOfBoundsException("column=" + column);
1470                         }
1471                         
1472                 }
1473                 
1474                 @Override
1475                 public void setValueAt(Object value, int row, int column) {
1476                         
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());
1481                         }
1482                         
1483                         switch (column) {
1484                         case PARAMETER:
1485                                 break;
1486                         
1487                         case MIN:
1488                                 double min = (Double) value;
1489                                 min = getModifierUnit(row).fromUnit(min);
1490                                 selectedModifiers.get(row).setMinValue(min);
1491                                 break;
1492                         
1493                         case CURRENT:
1494                                 break;
1495                         
1496                         case MAX:
1497                                 double max = (Double) value;
1498                                 max = getModifierUnit(row).fromUnit(max);
1499                                 selectedModifiers.get(row).setMaxValue(max);
1500                                 break;
1501                         
1502                         default:
1503                                 throw new IndexOutOfBoundsException("column=" + column);
1504                         }
1505                         this.fireTableRowsUpdated(row, row);
1506                         
1507                 }
1508                 
1509                 @Override
1510                 public boolean isCellEditable(int row, int column) {
1511                         switch (column) {
1512                         case PARAMETER:
1513                                 return false;
1514                         case CURRENT:
1515                                 return false;
1516                         case MIN:
1517                                 return true;
1518                         case MAX:
1519                                 return true;
1520                         default:
1521                                 throw new IndexOutOfBoundsException("column=" + column);
1522                         }
1523                 }
1524                 
1525         }
1526         
1527         
1528         private class DoubleCellRenderer extends DefaultTableCellRenderer {
1529                 @Override
1530                 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
1531                                 boolean hasFocus, int row, int column) {
1532                         
1533                         super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1534                         
1535                         double val = (Double) value;
1536                         Unit unit = getModifierUnit(row);
1537                         
1538                         val = unit.fromUnit(val);
1539                         this.setText(unit.toStringUnit(val));
1540                         
1541                         return this;
1542                 }
1543         }
1544         
1545         
1546         private static class SimulationModifierComparator implements Comparator<SimulationModifier> {
1547                 
1548                 @Override
1549                 public int compare(SimulationModifier mod1, SimulationModifier mod2) {
1550                         Object rel1 = mod1.getRelatedObject();
1551                         Object rel2 = mod2.getRelatedObject();
1552                         
1553                         /*
1554                          * Primarily order by related object:
1555                          * 
1556                          * - RocketComponents first
1557                          * - Two RocketComponents are ordered based on their position in the rocket
1558                          */
1559                         if (!rel1.equals(rel2)) {
1560                                 
1561                                 if (rel1 instanceof RocketComponent) {
1562                                         if (rel2 instanceof RocketComponent) {
1563                                                 
1564                                                 RocketComponent root = ((RocketComponent) rel1).getRoot();
1565                                                 for (RocketComponent c : root) {
1566                                                         if (c.equals(rel1)) {
1567                                                                 return -1;
1568                                                         }
1569                                                         if (c.equals(rel2)) {
1570                                                                 return 1;
1571                                                         }
1572                                                 }
1573                                                 
1574                                                 throw new BugException("Error sorting modifiers, mod1=" + mod1 + " rel1=" + rel1 +
1575                                                                 " mod2=" + mod2 + " rel2=" + rel2);
1576                                                 
1577                                         } else {
1578                                                 return -1;
1579                                         }
1580                                 } else {
1581                                         if (rel2 instanceof RocketComponent) {
1582                                                 return 1;
1583                                         }
1584                                 }
1585                                 
1586                         }
1587                         
1588                         // Secondarily sort by name
1589                         return collator.compare(mod1.getName(), mod2.getName());
1590                 }
1591         }
1592         
1593         
1594         
1595 }