major optimization updates
[debian/openrocket] / 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.border.TitledBorder;
41 import javax.swing.event.ChangeEvent;
42 import javax.swing.event.ChangeListener;
43 import javax.swing.event.ListSelectionEvent;
44 import javax.swing.event.ListSelectionListener;
45 import javax.swing.event.TreeSelectionEvent;
46 import javax.swing.event.TreeSelectionListener;
47 import javax.swing.table.AbstractTableModel;
48 import javax.swing.table.DefaultTableCellRenderer;
49 import javax.swing.table.TableColumnModel;
50 import javax.swing.tree.DefaultMutableTreeNode;
51 import javax.swing.tree.TreePath;
52
53 import net.miginfocom.swing.MigLayout;
54 import net.sf.openrocket.document.OpenRocketDocument;
55 import net.sf.openrocket.document.Simulation;
56 import net.sf.openrocket.gui.SpinnerEditor;
57 import net.sf.openrocket.gui.adaptors.DoubleModel;
58 import net.sf.openrocket.gui.components.CsvOptionPanel;
59 import net.sf.openrocket.gui.components.DescriptionArea;
60 import net.sf.openrocket.gui.components.DoubleCellEditor;
61 import net.sf.openrocket.gui.components.StyledLabel;
62 import net.sf.openrocket.gui.components.StyledLabel.Style;
63 import net.sf.openrocket.gui.components.UnitCellEditor;
64 import net.sf.openrocket.gui.components.UnitSelector;
65 import net.sf.openrocket.gui.scalefigure.RocketFigure;
66 import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
67 import net.sf.openrocket.l10n.Translator;
68 import net.sf.openrocket.logging.LogHelper;
69 import net.sf.openrocket.optimization.general.OptimizationException;
70 import net.sf.openrocket.optimization.general.Point;
71 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
72 import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
73 import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
74 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
75 import net.sf.openrocket.optimization.rocketoptimization.domains.IdentitySimulationDomain;
76 import net.sf.openrocket.optimization.rocketoptimization.domains.StabilityDomain;
77 import net.sf.openrocket.optimization.rocketoptimization.goals.MaximizationGoal;
78 import net.sf.openrocket.optimization.rocketoptimization.goals.MinimizationGoal;
79 import net.sf.openrocket.optimization.rocketoptimization.goals.ValueSeekGoal;
80 import net.sf.openrocket.optimization.services.OptimizationServiceHelper;
81 import net.sf.openrocket.rocketcomponent.Rocket;
82 import net.sf.openrocket.rocketcomponent.RocketComponent;
83 import net.sf.openrocket.startup.Application;
84 import net.sf.openrocket.unit.CaliberUnit;
85 import net.sf.openrocket.unit.Unit;
86 import net.sf.openrocket.unit.UnitGroup;
87 import net.sf.openrocket.unit.Value;
88 import net.sf.openrocket.util.BugException;
89 import net.sf.openrocket.util.Chars;
90 import net.sf.openrocket.util.FileHelper;
91 import net.sf.openrocket.util.GUIUtil;
92 import net.sf.openrocket.util.Named;
93 import net.sf.openrocket.util.Prefs;
94 import net.sf.openrocket.util.TextUtil;
95
96 import com.itextpdf.text.Font;
97
98 /**
99  * General rocket optimization dialog.
100  * 
101  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
102  */
103 public class GeneralOptimizationDialog extends JDialog {
104         private static final LogHelper log = Application.getLogger();
105         private static final Translator trans = Application.getTranslator();
106         
107         private static final Collator collator = Collator.getInstance();
108         
109
110         private static final String GOAL_MAXIMIZE = trans.get("goal.maximize");
111         private static final String GOAL_MINIMIZE = trans.get("goal.minimize");
112         private static final String GOAL_SEEK = trans.get("goal.seek");
113         
114         private static final String START_TEXT = trans.get("btn.start");
115         private static final String STOP_TEXT = trans.get("btn.stop");
116         
117
118
119         private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>();
120         private final Map<Object, List<SimulationModifier>> simulationModifiers =
121                         new HashMap<Object, List<SimulationModifier>>();
122         
123
124         private final OpenRocketDocument baseDocument;
125         private OpenRocketDocument documentCopy;
126         
127
128         private final JButton addButton;
129         private final JButton removeButton;
130         private final JButton removeAllButton;
131         
132         private final ParameterSelectionTableModel selectedModifierTableModel;
133         private final JTable selectedModifierTable;
134         private final DescriptionArea selectedModifierDescription;
135         private final SimulationModifierTree availableModifierTree;
136         
137         private final JComboBox simulationSelectionCombo;
138         private final JComboBox optimizationParameterCombo;
139         
140         private final JComboBox optimizationGoalCombo;
141         private final JSpinner optimizationGoalSpinner;
142         private final UnitSelector optimizationGoalUnitSelector;
143         private final DoubleModel optimizationSeekValue;
144         
145         private DoubleModel minimumStability;
146         private DoubleModel maximumStability;
147         private final JCheckBox minimumStabilitySelected;
148         private final JSpinner minimumStabilitySpinner;
149         private final UnitSelector minimumStabilityUnitSelector;
150         private final JCheckBox maximumStabilitySelected;
151         private final JSpinner maximumStabilitySpinner;
152         private final UnitSelector maximumStabilityUnitSelector;
153         
154         private final JLabel bestValueLabel;
155         private final JLabel stepCountLabel;
156         private final JLabel evaluationCountLabel;
157         private final JLabel stepSizeLabel;
158         
159         private final RocketFigure figure;
160         private final JToggleButton startButton;
161         private final JButton plotButton;
162         private final JButton saveButton;
163         
164         private final JButton applyButton;
165         private final JButton resetButton;
166         private final JButton closeButton;
167         
168         private final List<SimulationModifier> selectedModifiers = new ArrayList<SimulationModifier>();
169         
170         /** List of components to disable while optimization is running */
171         private final List<JComponent> disableComponents = new ArrayList<JComponent>();
172         
173         /** Whether optimization is currently running or not */
174         private boolean running = false;
175         /** The optimization worker that is running */
176         private OptimizationWorker worker = null;
177         
178
179         private double bestValue = Double.NaN;
180         private Unit bestValueUnit = Unit.NOUNIT2;
181         private int stepCount = 0;
182         private int evaluationCount = 0;
183         private double stepSize = 0;
184         
185         private final Map<Point, FunctionEvaluationData> evaluationHistory = new LinkedHashMap<Point, FunctionEvaluationData>();
186         private final List<Point> optimizationPath = new LinkedList<Point>();
187         
188
189         private boolean updating = false;
190         
191         
192         /**
193          * Sole constructor.
194          * 
195          * @param document      the document
196          * @param parent        the parent window
197          */
198         public GeneralOptimizationDialog(OpenRocketDocument document, Window parent) {
199                 super(parent, trans.get("title"));
200                 
201                 this.baseDocument = document;
202                 this.documentCopy = document.copy();
203                 
204                 loadOptimizationParameters();
205                 loadSimulationModifiers();
206                 
207                 JPanel sub;
208                 JLabel label;
209                 JScrollPane scroll;
210                 String tip;
211                 
212                 JPanel panel = new JPanel(new MigLayout("fill"));
213                 
214
215                 ChangeListener clearHistoryChangeListener = new ChangeListener() {
216                         @Override
217                         public void stateChanged(ChangeEvent e) {
218                                 clearHistory();
219                         }
220                 };
221                 ActionListener clearHistoryActionListener = new ActionListener() {
222                         @Override
223                         public void actionPerformed(ActionEvent e) {
224                                 clearHistory();
225                         }
226                 };
227                 
228
229
230                 //// Selected modifiers table
231                 
232                 selectedModifierTableModel = new ParameterSelectionTableModel();
233                 selectedModifierTable = new JTable(selectedModifierTableModel);
234                 selectedModifierTable.setDefaultRenderer(Double.class, new DoubleCellRenderer());
235                 selectedModifierTable.setRowSelectionAllowed(true);
236                 selectedModifierTable.setColumnSelectionAllowed(false);
237                 selectedModifierTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
238                 
239                 // Make sure spinner editor fits into the cell height
240                 selectedModifierTable.setRowHeight(new JSpinner().getPreferredSize().height - 4);
241                 
242                 selectedModifierTable.setDefaultEditor(Double.class, new DoubleCellEditor());
243                 selectedModifierTable.setDefaultEditor(Unit.class, new UnitCellEditor() {
244                         @Override
245                         protected UnitGroup getUnitGroup(Unit value, int row, int column) {
246                                 return selectedModifiers.get(row).getUnitGroup();
247                         }
248                 });
249                 
250                 disableComponents.add(selectedModifierTable);
251                 
252                 selectedModifierTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
253                         @Override
254                         public void valueChanged(ListSelectionEvent e) {
255                                 updateComponents();
256                         }
257                 });
258                 
259                 // Set column widths
260                 TableColumnModel columnModel = selectedModifierTable.getColumnModel();
261                 columnModel.getColumn(0).setPreferredWidth(150);
262                 columnModel.getColumn(1).setPreferredWidth(40);
263                 columnModel.getColumn(2).setPreferredWidth(40);
264                 columnModel.getColumn(3).setPreferredWidth(40);
265                 
266                 scroll = new JScrollPane(selectedModifierTable);
267                 
268                 label = new StyledLabel(trans.get("lbl.paramsToOptimize"), Style.BOLD);
269                 disableComponents.add(label);
270                 panel.add(label, "split 3, flowy");
271                 panel.add(scroll, "wmin 300lp, height 200lp, grow");
272                 selectedModifierDescription = new DescriptionArea(2, -3);
273                 disableComponents.add(selectedModifierDescription);
274                 panel.add(selectedModifierDescription, "growx");
275                 
276
277
278                 //// Add/remove buttons
279                 sub = new JPanel(new MigLayout("fill"));
280                 
281                 addButton = new JButton(Chars.LEFT_ARROW + " " + trans.get("btn.add") + "   ");
282                 addButton.setToolTipText(trans.get("btn.add.ttip"));
283                 addButton.addActionListener(new ActionListener() {
284                         @Override
285                         public void actionPerformed(ActionEvent e) {
286                                 SimulationModifier mod = getSelectedAvailableModifier();
287                                 if (mod != null) {
288                                         addModifier(mod);
289                                         clearHistory();
290                                 } else {
291                                         log.error("Attempting to add simulation modifier when none is selected");
292                                 }
293                         }
294                 });
295                 disableComponents.add(addButton);
296                 sub.add(addButton, "wrap para, sg button");
297                 
298                 removeButton = new JButton("   " + trans.get("btn.remove") + " " + Chars.RIGHT_ARROW);
299                 removeButton.setToolTipText(trans.get("btn.remove.ttip"));
300                 removeButton.addActionListener(new ActionListener() {
301                         @Override
302                         public void actionPerformed(ActionEvent e) {
303                                 SimulationModifier mod = getSelectedModifier();
304                                 if (mod == null) {
305                                         log.error("Attempting to remove simulation modifier when none is selected");
306                                         return;
307                                 }
308                                 removeModifier(mod);
309                                 clearHistory();
310                         }
311                 });
312                 disableComponents.add(removeButton);
313                 sub.add(removeButton, "wrap para*2, sg button");
314                 
315                 removeAllButton = new JButton(trans.get("btn.removeAll"));
316                 removeAllButton.setToolTipText(trans.get("btn.removeAll.ttip"));
317                 removeAllButton.addActionListener(new ActionListener() {
318                         @Override
319                         public void actionPerformed(ActionEvent e) {
320                                 log.user("Removing all selected modifiers");
321                                 selectedModifiers.clear();
322                                 selectedModifierTableModel.fireTableDataChanged();
323                                 availableModifierTree.repaint();
324                                 clearHistory();
325                         }
326                 });
327                 disableComponents.add(removeAllButton);
328                 sub.add(removeAllButton, "wrap para, sg button");
329                 
330                 panel.add(sub);
331                 
332
333
334                 //// Available modifier tree
335                 availableModifierTree = new SimulationModifierTree(documentCopy.getRocket(), simulationModifiers, selectedModifiers);
336                 availableModifierTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
337                         @Override
338                         public void valueChanged(TreeSelectionEvent e) {
339                                 updateComponents();
340                         }
341                 });
342                 
343                 // Handle double-click
344                 availableModifierTree.addMouseListener(new MouseAdapter() {
345                         @Override
346                         public void mousePressed(MouseEvent e) {
347                                 if (e.getClickCount() == 2) {
348                                         SimulationModifier mod = getSelectedAvailableModifier();
349                                         if (mod != null) {
350                                                 addModifier(mod);
351                                                 clearHistory();
352                                         } else {
353                                                 log.user("Double-clicked non-available option");
354                                         }
355                                 }
356                         }
357                 });
358                 
359                 disableComponents.add(availableModifierTree);
360                 scroll = new JScrollPane(availableModifierTree);
361                 label = new StyledLabel(trans.get("lbl.availableParams"), Style.BOLD);
362                 disableComponents.add(label);
363                 panel.add(label, "split 2, flowy");
364                 panel.add(scroll, "width 300lp, height 200lp, grow, wrap para*2");
365                 
366
367
368
369                 ////  Optimization options sub-panel
370                 
371                 sub = new JPanel(new MigLayout("fill"));
372                 TitledBorder border = BorderFactory.createTitledBorder(trans.get("lbl.optimizationOpts"));
373                 GUIUtil.changeFontStyle(border, Font.BOLD);
374                 sub.setBorder(border);
375                 disableComponents.add(sub);
376                 
377
378                 //// Simulation to optimize
379                 
380                 label = new JLabel(trans.get("lbl.optimizeSim"));
381                 tip = trans.get("lbl.optimizeSim.ttip");
382                 label.setToolTipText(tip);
383                 disableComponents.add(label);
384                 sub.add(label, "");
385                 
386                 simulationSelectionCombo = new JComboBox();
387                 simulationSelectionCombo.setToolTipText(tip);
388                 populateSimulations();
389                 simulationSelectionCombo.addActionListener(clearHistoryActionListener);
390                 disableComponents.add(simulationSelectionCombo);
391                 sub.add(simulationSelectionCombo, "growx, wrap unrel");
392                 
393
394
395                 //// Value to optimize
396                 label = new JLabel(trans.get("lbl.optimizeValue"));
397                 tip = trans.get("lbl.optimizeValue.ttip");
398                 label.setToolTipText(tip);
399                 disableComponents.add(label);
400                 sub.add(label, "");
401                 
402                 optimizationParameterCombo = new JComboBox();
403                 optimizationParameterCombo.setToolTipText(tip);
404                 populateParameters();
405                 optimizationParameterCombo.addActionListener(clearHistoryActionListener);
406                 disableComponents.add(optimizationParameterCombo);
407                 sub.add(optimizationParameterCombo, "growx, wrap unrel");
408                 
409
410
411                 //// Optimization goal
412                 label = new JLabel(trans.get("lbl.optimizeGoal"));
413                 tip = trans.get("lbl.optimizeGoal");
414                 label.setToolTipText(tip);
415                 disableComponents.add(label);
416                 sub.add(label, "");
417                 
418                 optimizationGoalCombo = new JComboBox(new String[] { GOAL_MAXIMIZE, GOAL_MINIMIZE, GOAL_SEEK });
419                 optimizationGoalCombo.setToolTipText(tip);
420                 optimizationGoalCombo.setEditable(false);
421                 optimizationGoalCombo.addActionListener(clearHistoryActionListener);
422                 disableComponents.add(optimizationGoalCombo);
423                 sub.add(optimizationGoalCombo, "growx");
424                 
425
426                 //// Optimization custom value
427                 optimizationSeekValue = new DoubleModel(0, UnitGroup.UNITS_NONE);
428                 optimizationSeekValue.addChangeListener(clearHistoryChangeListener);
429                 
430                 optimizationGoalSpinner = new JSpinner(optimizationSeekValue.getSpinnerModel());
431                 tip = trans.get("lbl.optimizeGoalValue.ttip");
432                 optimizationGoalSpinner.setToolTipText(tip);
433                 optimizationGoalSpinner.setEditor(new SpinnerEditor(optimizationGoalSpinner));
434                 disableComponents.add(optimizationGoalSpinner);
435                 sub.add(optimizationGoalSpinner, "width 30lp");
436                 
437                 optimizationGoalUnitSelector = new UnitSelector(optimizationSeekValue);
438                 optimizationGoalUnitSelector.setToolTipText(tip);
439                 disableComponents.add(optimizationGoalUnitSelector);
440                 sub.add(optimizationGoalUnitSelector, "width 20lp, wrap unrel");
441                 
442
443                 panel.add(sub, "grow");
444                 
445
446
447                 ////  Required stability sub-panel
448                 
449                 sub = new JPanel(new MigLayout("fill"));
450                 border = BorderFactory.createTitledBorder(trans.get("lbl.requireStability"));
451                 GUIUtil.changeFontStyle(border, Font.BOLD);
452                 sub.setBorder(border);
453                 disableComponents.add(sub);
454                 
455
456
457                 double ref = CaliberUnit.calculateCaliber(baseDocument.getRocket());
458                 minimumStability = new DoubleModel(ref, UnitGroup.stabilityUnits(ref));
459                 maximumStability = new DoubleModel(5 * ref, UnitGroup.stabilityUnits(ref));
460                 minimumStability.addChangeListener(clearHistoryChangeListener);
461                 maximumStability.addChangeListener(clearHistoryChangeListener);
462                 
463
464                 //// Minimum stability
465                 tip = trans.get("lbl.requireMinStability.ttip");
466                 minimumStabilitySelected = new JCheckBox(trans.get("lbl.requireMinStability"));
467                 minimumStabilitySelected.setSelected(true);
468                 minimumStabilitySelected.setToolTipText(tip);
469                 minimumStabilitySelected.addActionListener(new ActionListener() {
470                         @Override
471                         public void actionPerformed(ActionEvent e) {
472                                 updateComponents();
473                         }
474                 });
475                 disableComponents.add(minimumStabilitySelected);
476                 sub.add(minimumStabilitySelected);
477                 
478                 minimumStabilitySpinner = new JSpinner(minimumStability.getSpinnerModel());
479                 minimumStabilitySpinner.setToolTipText(tip);
480                 minimumStabilitySpinner.setEditor(new SpinnerEditor(minimumStabilitySpinner));
481                 disableComponents.add(minimumStabilitySpinner);
482                 sub.add(minimumStabilitySpinner, "growx");
483                 
484                 minimumStabilityUnitSelector = new UnitSelector(minimumStability);
485                 minimumStabilityUnitSelector.setToolTipText(tip);
486                 disableComponents.add(minimumStabilityUnitSelector);
487                 sub.add(minimumStabilityUnitSelector, "growx, wrap unrel");
488                 
489
490                 //// Maximum stability
491                 tip = trans.get("lbl.requireMaxStability.ttip");
492                 maximumStabilitySelected = new JCheckBox(trans.get("lbl.requireMaxStability"));
493                 maximumStabilitySelected.setToolTipText(tip);
494                 maximumStabilitySelected.addActionListener(new ActionListener() {
495                         @Override
496                         public void actionPerformed(ActionEvent e) {
497                                 updateComponents();
498                         }
499                 });
500                 disableComponents.add(maximumStabilitySelected);
501                 sub.add(maximumStabilitySelected);
502                 
503                 maximumStabilitySpinner = new JSpinner(maximumStability.getSpinnerModel());
504                 maximumStabilitySpinner.setToolTipText(tip);
505                 maximumStabilitySpinner.setEditor(new SpinnerEditor(maximumStabilitySpinner));
506                 disableComponents.add(maximumStabilitySpinner);
507                 sub.add(maximumStabilitySpinner, "growx");
508                 
509                 maximumStabilityUnitSelector = new UnitSelector(maximumStability);
510                 maximumStabilityUnitSelector.setToolTipText(tip);
511                 disableComponents.add(maximumStabilityUnitSelector);
512                 sub.add(maximumStabilityUnitSelector, "growx, wrap para");
513                 
514
515
516                 //              DescriptionArea desc = new DescriptionArea("Stability requirements are verified during each time step of the simulation.",
517                 //                              2, -2, false);
518                 //              desc.setViewportBorder(null);
519                 //              disableComponents.add(desc);
520                 //              sub.add(desc, "span, growx");
521                 
522
523                 panel.add(sub, "span 2, grow, wrap para*2");
524                 
525
526
527
528                 ////  Rocket figure
529                 figure = new RocketFigure(getSelectedSimulation().getConfiguration());
530                 figure.setBorderPixels(1, 1);
531                 ScaleScrollPane figureScrollPane = new ScaleScrollPane(figure);
532                 figureScrollPane.setFitting(true);
533                 panel.add(figureScrollPane, "span, split, height 200lp, grow");
534                 
535
536                 sub = new JPanel(new MigLayout("fill"));
537                 
538
539                 label = new JLabel(trans.get("status.bestValue"));
540                 tip = trans.get("status.bestValue.ttip");
541                 label.setToolTipText(tip);
542                 sub.add(label, "gapright unrel");
543                 
544                 bestValueLabel = new JLabel();
545                 bestValueLabel.setToolTipText(tip);
546                 sub.add(bestValueLabel, "wmin 60lp, wrap rel");
547                 
548
549                 label = new JLabel(trans.get("status.stepCount"));
550                 tip = trans.get("status.stepCount.ttip");
551                 label.setToolTipText(tip);
552                 sub.add(label, "gapright unrel");
553                 
554                 stepCountLabel = new JLabel();
555                 stepCountLabel.setToolTipText(tip);
556                 sub.add(stepCountLabel, "wrap rel");
557                 
558
559                 label = new JLabel(trans.get("status.evalCount"));
560                 tip = trans.get("status.evalCount");
561                 label.setToolTipText(tip);
562                 sub.add(label, "gapright unrel");
563                 
564                 evaluationCountLabel = new JLabel();
565                 evaluationCountLabel.setToolTipText(tip);
566                 sub.add(evaluationCountLabel, "wrap rel");
567                 
568
569                 label = new JLabel(trans.get("status.stepSize"));
570                 tip = trans.get("status.stepSize.ttip");
571                 label.setToolTipText(tip);
572                 sub.add(label, "gapright unrel");
573                 
574                 stepSizeLabel = new JLabel();
575                 stepSizeLabel.setToolTipText(tip);
576                 sub.add(stepSizeLabel, "wrap para");
577                 
578
579                 //// Start/Stop button
580                 
581                 startButton = new JToggleButton(START_TEXT);
582                 startButton.addActionListener(new ActionListener() {
583                         @Override
584                         public void actionPerformed(ActionEvent e) {
585                                 if (updating) {
586                                         log.debug("Updating, ignoring event");
587                                         return;
588                                 }
589                                 if (running) {
590                                         log.user("Stopping optimization");
591                                         stopOptimization();
592                                 } else {
593                                         log.user("Starting optimization");
594                                         startOptimization();
595                                 }
596                         }
597                 });
598                 sub.add(startButton, "span, growx, wrap para*2");
599                 
600
601                 plotButton = new JButton(trans.get("btn.plotPath"));
602                 plotButton.setToolTipText(trans.get("btn.plotPath.ttip"));
603                 plotButton.addActionListener(new ActionListener() {
604                         @Override
605                         public void actionPerformed(ActionEvent e) {
606                                 log.user("Plotting optimization path, dimensionality=" + selectedModifiers.size());
607                                 OptimizationPlotDialog dialog = new OptimizationPlotDialog(optimizationPath, evaluationHistory,
608                                                 selectedModifiers, getSelectedParameter(),
609                                                 UnitGroup.stabilityUnits(getSelectedSimulation().getRocket()),
610                                                 GeneralOptimizationDialog.this);
611                                 dialog.setVisible(true);
612                         }
613                 });
614                 disableComponents.add(plotButton);
615                 sub.add(plotButton, "span, growx, wrap");
616                 
617
618                 saveButton = new JButton(trans.get("btn.save"));
619                 saveButton.setToolTipText(trans.get("btn.save.ttip"));
620                 saveButton.addActionListener(new ActionListener() {
621                         @Override
622                         public void actionPerformed(ActionEvent e) {
623                                 log.user("User selected save path");
624                                 savePath();
625                         }
626                 });
627                 disableComponents.add(saveButton);
628                 sub.add(saveButton, "span, growx");
629                 
630
631
632                 panel.add(sub, "wrap para*2");
633                 
634
635
636
637                 ////  Bottom buttons
638                 
639                 applyButton = new JButton(trans.get("btn.apply"));
640                 applyButton.setToolTipText(trans.get("btn.apply.ttip"));
641                 applyButton.addActionListener(new ActionListener() {
642                         @Override
643                         public void actionPerformed(ActionEvent e) {
644                                 log.user("Applying optimization changes");
645                                 applyDesign();
646                         }
647                 });
648                 disableComponents.add(applyButton);
649                 panel.add(applyButton, "span, split, gapright para, right");
650                 
651                 resetButton = new JButton(trans.get("btn.reset"));
652                 resetButton.setToolTipText(trans.get("btn.reset.ttip"));
653                 resetButton.addActionListener(new ActionListener() {
654                         @Override
655                         public void actionPerformed(ActionEvent e) {
656                                 log.user("Resetting optimization design");
657                                 resetDesign();
658                         }
659                 });
660                 disableComponents.add(resetButton);
661                 panel.add(resetButton, "gapright para, right");
662                 
663                 closeButton = new JButton(trans.get("btn.close"));
664                 closeButton.setToolTipText(trans.get("btn.close.ttip"));
665                 closeButton.addActionListener(new ActionListener() {
666                         @Override
667                         public void actionPerformed(ActionEvent e) {
668                                 log.user("Closing optimization dialog");
669                                 stopOptimization();
670                                 GeneralOptimizationDialog.this.dispose();
671                         }
672                 });
673                 panel.add(closeButton, "right");
674                 
675
676                 this.add(panel);
677                 clearHistory();
678                 updateComponents();
679                 GUIUtil.setDisposableDialogOptions(this, null);
680         }
681         
682         
683         private void startOptimization() {
684                 if (running) {
685                         log.info("Optimization already running");
686                         return;
687                 }
688                 
689
690                 if (selectedModifiers.isEmpty()) {
691                         JOptionPane.showMessageDialog(this, trans.get("error.selectParams.text"),
692                                         trans.get("error.selectParams.title"), JOptionPane.ERROR_MESSAGE);
693                         updating = true;
694                         startButton.setSelected(false);
695                         startButton.setText(START_TEXT);
696                         updating = false;
697                         return;
698                 }
699                 
700
701                 running = true;
702                 
703                 // Update the button status
704                 updating = true;
705                 startButton.setSelected(true);
706                 startButton.setText(STOP_TEXT);
707                 updating = false;
708                 
709
710                 // Create a copy of the simulation (we're going to modify the original in the current thread)
711                 Simulation simulation = getSelectedSimulation();
712                 Rocket rocketCopy = simulation.getRocket().copyWithOriginalID();
713                 simulation = simulation.duplicateSimulation(rocketCopy);
714                 
715                 OptimizableParameter parameter = getSelectedParameter();
716                 
717                 OptimizationGoal goal;
718                 String value = (String) optimizationGoalCombo.getSelectedItem();
719                 if (GOAL_MAXIMIZE.equals(value)) {
720                         goal = new MaximizationGoal();
721                 } else if (GOAL_MINIMIZE.equals(value)) {
722                         goal = new MinimizationGoal();
723                 } else if (GOAL_SEEK.equals(value)) {
724                         goal = new ValueSeekGoal(optimizationSeekValue.getValue());
725                 } else {
726                         throw new BugException("optimizationGoalCombo had invalid value: " + value);
727                 }
728                 
729                 SimulationDomain domain;
730                 if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
731                         double min, max;
732                         boolean minAbsolute, maxAbsolute;
733                         
734                         /*
735                          * Make minAbsolute/maxAbsolute consistent with each other to produce reasonable
736                          * result in plot tool tips.  Yes, this is a bit ugly.
737                          */
738
739                         // Min stability
740                         Unit unit = minimumStability.getCurrentUnit();
741                         if (unit instanceof CaliberUnit) {
742                                 min = unit.toUnit(minimumStability.getValue());
743                                 minAbsolute = false;
744                         } else {
745                                 min = minimumStability.getValue();
746                                 minAbsolute = true;
747                         }
748                         
749                         // Max stability
750                         unit = maximumStability.getCurrentUnit();
751                         if (unit instanceof CaliberUnit) {
752                                 max = unit.toUnit(maximumStability.getValue());
753                                 maxAbsolute = false;
754                         } else {
755                                 max = maximumStability.getValue();
756                                 maxAbsolute = true;
757                         }
758                         
759
760                         if (!minimumStabilitySelected.isSelected()) {
761                                 min = Double.NaN;
762                                 minAbsolute = maxAbsolute;
763                         }
764                         if (!maximumStabilitySelected.isSelected()) {
765                                 max = Double.NaN;
766                                 maxAbsolute = minAbsolute;
767                         }
768                         
769                         domain = new StabilityDomain(min, minAbsolute, max, maxAbsolute);
770                 } else {
771                         domain = new IdentitySimulationDomain();
772                 }
773                 
774                 SimulationModifier[] modifiers = selectedModifiers.toArray(new SimulationModifier[0]);
775                 
776                 // Create and start the background worker
777                 worker = new OptimizationWorker(simulation, parameter, goal, domain, modifiers) {
778                         @Override
779                         protected void done(OptimizationException exception) {
780                                 log.info("Optimization finished, exception=" + exception, exception);
781                                 
782                                 if (exception != null) {
783                                         JOptionPane.showMessageDialog(GeneralOptimizationDialog.this,
784                                                         new Object[] {
785                                                                         trans.get("error.optimizationFailure.text"),
786                                                                         exception.getLocalizedMessage()
787                                         }, trans.get("error.optimizationFailure.title"), JOptionPane.ERROR_MESSAGE);
788                                 }
789                                 
790                                 worker = null;
791                                 stopOptimization();
792                         }
793                         
794                         @Override
795                         protected void functionEvaluated(List<FunctionEvaluationData> data) {
796                                 for (FunctionEvaluationData d : data) {
797                                         evaluationHistory.put(d.getPoint(), d);
798                                         evaluationCount++;
799                                 }
800                                 updateCounters();
801                         }
802                         
803                         @Override
804                         protected void optimizationStepTaken(List<OptimizationStepData> data) {
805                                 
806                                 // Add starting point to the path
807                                 if (optimizationPath.isEmpty()) {
808                                         optimizationPath.add(data.get(0).getOldPoint());
809                                 }
810                                 
811                                 // Add other points to the path
812                                 for (OptimizationStepData d : data) {
813                                         optimizationPath.add(d.getNewPoint());
814                                 }
815                                 
816                                 // Get function value from the latest point
817                                 OptimizationStepData latest = data.get(data.size() - 1);
818                                 Point newPoint = latest.getNewPoint();
819                                 
820                                 FunctionEvaluationData pointValue = evaluationHistory.get(newPoint);
821                                 if (pointValue != null) {
822                                         bestValue = pointValue.getParameterValue().getValue();
823                                 } else {
824                                         log.error("History does not contain point " + newPoint);
825                                         bestValue = Double.NaN;
826                                 }
827                                 
828                                 // Update the simulation
829                                 Simulation sim = getSelectedSimulation();
830                                 for (int i = 0; i < newPoint.dim(); i++) {
831                                         try {
832                                                 selectedModifiers.get(i).modify(sim, newPoint.get(i));
833                                         } catch (OptimizationException e) {
834                                                 throw new BugException("Simulation modifier failed to modify the base simulation " +
835                                                                 "modifier=" + selectedModifiers.get(i), e);
836                                         }
837                                 }
838                                 figure.updateFigure();
839                                 
840                                 // Update other counter data
841                                 stepCount += data.size();
842                                 stepSize = latest.getStepSize();
843                                 updateCounters();
844                         }
845                 };
846                 worker.start();
847                 
848
849                 clearHistory();
850                 
851                 updateComponents();
852         }
853         
854         private void stopOptimization() {
855                 if (!running) {
856                         log.info("Optimization not running");
857                         return;
858                 }
859                 
860                 if (worker != null && worker.isAlive()) {
861                         log.info("Worker still running, interrupting it and setting to null");
862                         worker.interrupt();
863                         worker = null;
864                         return;
865                 }
866                 
867                 running = false;
868                 
869                 // Update the button status
870                 updating = true;
871                 startButton.setSelected(false);
872                 startButton.setText(START_TEXT);
873                 updating = false;
874                 
875
876                 updateComponents();
877         }
878         
879         
880
881
882         /**
883          * Reset the current optimization history and values.  This does not reset the design.
884          */
885         private void clearHistory() {
886                 evaluationHistory.clear();
887                 optimizationPath.clear();
888                 bestValue = Double.NaN;
889                 bestValueUnit = getSelectedParameter().getUnitGroup().getDefaultUnit();
890                 stepCount = 0;
891                 evaluationCount = 0;
892                 stepSize = 0.5;
893                 updateCounters();
894                 updateComponents();
895         }
896         
897         
898         private void applyDesign() {
899                 // TODO: MEDIUM: Apply also potential changes to simulations
900                 Rocket src = getSelectedSimulation().getRocket().copyWithOriginalID();
901                 Rocket dest = baseDocument.getRocket();
902                 try {
903                         baseDocument.startUndo(trans.get("undoText"));
904                         dest.freeze();
905                         
906                         // Remove all children
907                         while (dest.getChildCount() > 0) {
908                                 dest.removeChild(0);
909                         }
910                         
911                         // Move all children to the destination rocket
912                         while (src.getChildCount() > 0) {
913                                 RocketComponent c = src.getChild(0);
914                                 src.removeChild(0);
915                                 dest.addChild(c);
916                         }
917                         
918                 } finally {
919                         dest.thaw();
920                         baseDocument.stopUndo();
921                 }
922         }
923         
924         
925         private void resetDesign() {
926                 clearHistory();
927                 
928                 documentCopy = baseDocument.copy();
929                 
930                 loadOptimizationParameters();
931                 loadSimulationModifiers();
932                 
933                 // Replace selected modifiers with corresponding new modifiers
934                 List<SimulationModifier> newSelected = new ArrayList<SimulationModifier>();
935                 for (SimulationModifier original : selectedModifiers) {
936                         List<SimulationModifier> newModifiers = simulationModifiers.get(original.getRelatedObject());
937                         if (newModifiers != null) {
938                                 int index = newModifiers.indexOf(original);
939                                 if (index >= 0) {
940                                         newSelected.add(newModifiers.get(index));
941                                 }
942                         }
943                 }
944                 selectedModifiers.clear();
945                 selectedModifiers.addAll(newSelected);
946                 selectedModifierTableModel.fireTableDataChanged();
947                 
948                 // Update the available modifier tree
949                 availableModifierTree.populateTree(documentCopy.getRocket(), simulationModifiers);
950                 availableModifierTree.expandComponents();
951                 
952
953                 // Update selectable simulations
954                 populateSimulations();
955                 
956                 // Update selectable parameters
957                 populateParameters();
958                 
959         }
960         
961         
962         private void populateSimulations() {
963                 String current = null;
964                 Object selection = simulationSelectionCombo.getSelectedItem();
965                 if (selection != null) {
966                         current = selection.toString();
967                 }
968                 
969
970                 List<Named<Simulation>> simulations = new ArrayList<Named<Simulation>>();
971                 Rocket rocket = documentCopy.getRocket();
972                 
973                 for (Simulation s : documentCopy.getSimulations()) {
974                         String id = s.getConfiguration().getMotorConfigurationID();
975                         String name = createSimulationName(s.getName(), rocket.getMotorConfigurationNameOrDescription(id));
976                         simulations.add(new Named<Simulation>(s, name));
977                 }
978                 
979                 for (String id : rocket.getMotorConfigurationIDs()) {
980                         if (id == null) {
981                                 continue;
982                         }
983                         Simulation sim = new Simulation(rocket);
984                         sim.getConfiguration().setMotorConfigurationID(id);
985                         String name = createSimulationName(trans.get("basicSimulationName"), rocket.getMotorConfigurationNameOrDescription(id));
986                         simulations.add(new Named<Simulation>(sim, name));
987                 }
988                 
989
990                 Simulation sim = new Simulation(rocket);
991                 sim.getConfiguration().setMotorConfigurationID(null);
992                 String name = createSimulationName(trans.get("noSimulationName"), rocket.getMotorConfigurationNameOrDescription(null));
993                 simulations.add(new Named<Simulation>(sim, name));
994                 
995
996                 simulationSelectionCombo.setModel(new DefaultComboBoxModel(simulations.toArray()));
997                 
998                 if (current != null) {
999                         for (int i = 0; i < simulations.size(); i++) {
1000                                 if (simulations.get(i).toString().equals(current)) {
1001                                         simulationSelectionCombo.setSelectedIndex(i);
1002                                         break;
1003                                 }
1004                         }
1005                 }
1006         }
1007         
1008         
1009         private void populateParameters() {
1010                 String current = null;
1011                 Object selection = optimizationParameterCombo.getSelectedItem();
1012                 if (selection != null) {
1013                         current = selection.toString();
1014                 } else {
1015                         // Default to apogee altitude event if it is not the first one in the list
1016                         current = trans.get("MaximumAltitudeParameter.name");
1017                 }
1018                 
1019                 List<Named<OptimizableParameter>> parameters = new ArrayList<Named<OptimizableParameter>>();
1020                 for (OptimizableParameter p : optimizationParameters) {
1021                         parameters.add(new Named<OptimizableParameter>(p, p.getName()));
1022                 }
1023                 
1024                 optimizationParameterCombo.setModel(new DefaultComboBoxModel(parameters.toArray()));
1025                 
1026                 for (int i = 0; i < parameters.size(); i++) {
1027                         if (parameters.get(i).toString().equals(current)) {
1028                                 optimizationParameterCombo.setSelectedIndex(i);
1029                                 break;
1030                         }
1031                 }
1032         }
1033         
1034         private void updateCounters() {
1035                 bestValueLabel.setText(bestValueUnit.toStringUnit(bestValue));
1036                 stepCountLabel.setText("" + stepCount);
1037                 evaluationCountLabel.setText("" + evaluationCount);
1038                 stepSizeLabel.setText(UnitGroup.UNITS_RELATIVE.toStringUnit(stepSize));
1039         }
1040         
1041         
1042         private void loadOptimizationParameters() {
1043                 optimizationParameters.clear();
1044                 optimizationParameters.addAll(OptimizationServiceHelper.getOptimizableParameters(documentCopy));
1045                 
1046                 if (optimizationParameters.isEmpty()) {
1047                         throw new BugException("No rocket optimization parameters found, distribution built wrong.");
1048                 }
1049                 
1050                 Collections.sort(optimizationParameters, new Comparator<OptimizableParameter>() {
1051                         @Override
1052                         public int compare(OptimizableParameter o1, OptimizableParameter o2) {
1053                                 return o1.getName().compareTo(o2.getName());
1054                         }
1055                 });
1056         }
1057         
1058         
1059         private void loadSimulationModifiers() {
1060                 simulationModifiers.clear();
1061                 
1062                 for (SimulationModifier m : OptimizationServiceHelper.getSimulationModifiers(documentCopy)) {
1063                         Object key = m.getRelatedObject();
1064                         List<SimulationModifier> list = simulationModifiers.get(key);
1065                         if (list == null) {
1066                                 list = new ArrayList<SimulationModifier>();
1067                                 simulationModifiers.put(key, list);
1068                         }
1069                         list.add(m);
1070                 }
1071                 
1072                 for (Object key : simulationModifiers.keySet()) {
1073                         List<SimulationModifier> list = simulationModifiers.get(key);
1074                         Collections.sort(list, new Comparator<SimulationModifier>() {
1075                                 @Override
1076                                 public int compare(SimulationModifier o1, SimulationModifier o2) {
1077                                         return o1.getName().compareTo(o2.getName());
1078                                 }
1079                         });
1080                 }
1081                 
1082         }
1083         
1084         
1085
1086         private void addModifier(SimulationModifier mod) {
1087                 if (!selectedModifiers.contains(mod)) {
1088                         log.user(1, "Adding simulation modifier " + mod);
1089                         selectedModifiers.add(mod);
1090                         Collections.sort(selectedModifiers, new SimulationModifierComparator());
1091                         selectedModifierTableModel.fireTableDataChanged();
1092                         availableModifierTree.repaint();
1093                 } else {
1094                         log.user(1, "Attempting to add an already existing simulation modifier " + mod);
1095                 }
1096         }
1097         
1098         
1099         private void removeModifier(SimulationModifier mod) {
1100                 log.user(1, "Removing simulation modifier " + mod);
1101                 selectedModifiers.remove(mod);
1102                 selectedModifierTableModel.fireTableDataChanged();
1103                 availableModifierTree.repaint();
1104         }
1105         
1106         
1107
1108         /**
1109          * Update the enabled status of all components in the dialog.
1110          */
1111         private void updateComponents() {
1112                 
1113                 if (updating) {
1114                         return;
1115                 }
1116                 
1117                 updating = true;
1118                 
1119
1120                 // First enable all components if optimization not running
1121                 if (!running) {
1122                         for (JComponent c : disableComponents) {
1123                                 c.setEnabled(true);
1124                         }
1125                 }
1126                 
1127
1128                 // "Add" button
1129                 SimulationModifier mod = getSelectedAvailableModifier();
1130                 if (mod != null && !selectedModifiers.contains(mod)) {
1131                         addButton.setEnabled(true);
1132                 } else {
1133                         addButton.setEnabled(false);
1134                 }
1135                 
1136                 // "Remove" button
1137                 removeButton.setEnabled(selectedModifierTable.getSelectedRow() >= 0);
1138                 
1139                 // "Remove all" button
1140                 removeAllButton.setEnabled(!selectedModifiers.isEmpty());
1141                 
1142
1143                 // Optimization goal
1144                 String selected = (String) optimizationGoalCombo.getSelectedItem();
1145                 if (GOAL_SEEK.equals(selected)) {
1146                         optimizationGoalSpinner.setVisible(true);
1147                         optimizationGoalUnitSelector.setVisible(true);
1148                 } else {
1149                         optimizationGoalSpinner.setVisible(false);
1150                         optimizationGoalUnitSelector.setVisible(false);
1151                 }
1152                 
1153
1154                 // Minimum/maximum stability options
1155                 minimumStabilitySpinner.setEnabled(minimumStabilitySelected.isSelected());
1156                 minimumStabilityUnitSelector.setEnabled(minimumStabilitySelected.isSelected());
1157                 maximumStabilitySpinner.setEnabled(maximumStabilitySelected.isSelected());
1158                 maximumStabilityUnitSelector.setEnabled(maximumStabilitySelected.isSelected());
1159                 
1160
1161                 // Plot button (enabled if path exists and dimensionality is 1 or 2)
1162                 plotButton.setEnabled(!optimizationPath.isEmpty() && (selectedModifiers.size() == 1 || selectedModifiers.size() == 2));
1163                 
1164                 // Save button (enabled if path exists)
1165                 saveButton.setEnabled(!optimizationPath.isEmpty());
1166                 
1167
1168                 // Last disable all components if optimization is running
1169                 if (running) {
1170                         for (JComponent c : disableComponents) {
1171                                 c.setEnabled(false);
1172                         }
1173                 }
1174                 
1175
1176                 // Update description text
1177                 mod = getSelectedModifier();
1178                 if (mod != null) {
1179                         selectedModifierDescription.setText(mod.getDescription());
1180                 } else {
1181                         selectedModifierDescription.setText("");
1182                 }
1183                 
1184
1185                 // Update the figure
1186                 figure.setConfiguration(getSelectedSimulation().getConfiguration());
1187                 
1188                 updating = false;
1189         }
1190         
1191         
1192         private void savePath() {
1193                 
1194                 if (evaluationHistory.isEmpty()) {
1195                         throw new BugException("evaluation history is empty");
1196                 }
1197                 
1198                 CsvOptionPanel csvOptions = new CsvOptionPanel(GeneralOptimizationDialog.class,
1199                                 trans.get("export.header"), trans.get("export.header.ttip"));
1200                 
1201
1202                 JFileChooser chooser = new JFileChooser();
1203                 chooser.setFileFilter(FileHelper.CSV_FILE_FILTER);
1204                 chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
1205                 chooser.setAccessory(csvOptions);
1206                 
1207                 if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
1208                         return;
1209                 
1210                 File file = chooser.getSelectedFile();
1211                 if (file == null)
1212                         return;
1213                 
1214                 file = FileHelper.ensureExtension(file, "csv");
1215                 if (!FileHelper.confirmWrite(file, this)) {
1216                         return;
1217                 }
1218                 
1219                 String fieldSeparator = csvOptions.getFieldSeparator();
1220                 String commentCharacter = csvOptions.getCommentCharacter();
1221                 boolean includeHeader = csvOptions.getSelectionOption(0);
1222                 csvOptions.storePreferences();
1223                 
1224                 log.info("Saving optimization path to " + file + ", fieldSeparator=" + fieldSeparator +
1225                                 ", commentCharacter=" + commentCharacter + ", includeHeader=" + includeHeader);
1226                 
1227                 try {
1228                         Writer writer = new BufferedWriter(new FileWriter(file));
1229                         
1230                         // Write header
1231                         if (includeHeader) {
1232                                 FunctionEvaluationData data = evaluationHistory.values().iterator().next();
1233                                 
1234                                 writer.write(commentCharacter);
1235                                 for (SimulationModifier mod : selectedModifiers) {
1236                                         writer.write(mod.getRelatedObject().toString() + ": " + mod.getName() + " / " +
1237                                                         mod.getUnitGroup().getDefaultUnit().getUnit());
1238                                         writer.write(fieldSeparator);
1239                                 }
1240                                 if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
1241                                         writer.write(trans.get("export.stability") + " / " + data.getDomainReference().getUnit().getUnit());
1242                                         writer.write(fieldSeparator);
1243                                 }
1244                                 writer.write(getSelectedParameter().getName() + " / " +
1245                                                 getSelectedParameter().getUnitGroup().getDefaultUnit().getUnit());
1246                                 
1247                                 writer.write("\n");
1248                         }
1249                         
1250
1251                         for (FunctionEvaluationData data : evaluationHistory.values()) {
1252                                 Value[] state = data.getState();
1253                                 
1254                                 for (int i = 0; i < state.length; i++) {
1255                                         writer.write(TextUtil.doubleToString(state[i].getUnitValue()));
1256                                         writer.write(fieldSeparator);
1257                                 }
1258                                 
1259                                 if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
1260                                         writer.write(TextUtil.doubleToString(data.getDomainReference().getUnitValue()));
1261                                         writer.write(fieldSeparator);
1262                                 }
1263                                 
1264                                 if (data.getParameterValue() != null) {
1265                                         writer.write(TextUtil.doubleToString(data.getParameterValue().getUnitValue()));
1266                                 } else {
1267                                         writer.write("N/A");
1268                                 }
1269                                 writer.write("\n");
1270                         }
1271                         
1272                         writer.close();
1273                         log.info("File successfully saved");
1274                         
1275                 } catch (IOException e) {
1276                         FileHelper.errorWriting(e, this);
1277                 }
1278                 
1279         }
1280         
1281         /**
1282          * Return the currently selected available simulation modifier from the modifier tree,
1283          * or <code>null</code> if none selected.
1284          */
1285         private SimulationModifier getSelectedAvailableModifier() {
1286                 TreePath treepath = availableModifierTree.getSelectionPath();
1287                 if (treepath != null) {
1288                         Object o = ((DefaultMutableTreeNode) treepath.getLastPathComponent()).getUserObject();
1289                         if (o instanceof SimulationModifier) {
1290                                 return (SimulationModifier) o;
1291                         }
1292                 }
1293                 return null;
1294         }
1295         
1296         /**
1297          * Return the currently selected simulation.
1298          * @return      the selected simulation.
1299          */
1300         @SuppressWarnings("unchecked")
1301         private Simulation getSelectedSimulation() {
1302                 return ((Named<Simulation>) simulationSelectionCombo.getSelectedItem()).get();
1303         }
1304         
1305         
1306         /**
1307          * Return the currently selected simulation modifier from the table,
1308          * or <code>null</code> if none selected.
1309          * @return      the selected modifier or <code>null</code>.
1310          */
1311         private SimulationModifier getSelectedModifier() {
1312                 int row = selectedModifierTable.getSelectedRow();
1313                 if (row < 0) {
1314                         return null;
1315                 }
1316                 row = selectedModifierTable.convertRowIndexToModel(row);
1317                 return selectedModifiers.get(row);
1318         }
1319         
1320         
1321         /**
1322          * Return the currently selected optimization parameter.
1323          * @return      the selected optimization parameter.
1324          */
1325         @SuppressWarnings("unchecked")
1326         private OptimizableParameter getSelectedParameter() {
1327                 return ((Named<OptimizableParameter>) optimizationParameterCombo.getSelectedItem()).get();
1328         }
1329         
1330         
1331         private Unit getModifierUnit(int index) {
1332                 return selectedModifiers.get(index).getUnitGroup().getDefaultUnit();
1333         }
1334         
1335         private String createSimulationName(String simulationName, String motorConfiguration) {
1336                 String name;
1337                 boolean hasParenthesis = motorConfiguration.matches("^[\\[\\(].*[\\]\\)]$");
1338                 name = simulationName + " ";
1339                 if (!hasParenthesis) {
1340                         name += "(";
1341                 }
1342                 name += motorConfiguration;
1343                 if (!hasParenthesis) {
1344                         name += ")";
1345                 }
1346                 return name;
1347         }
1348         
1349         /**
1350          * The table model for the parameter selection.
1351          * 
1352          * [Body tube: Length]  [min]  [max]  [unit]
1353          */
1354         private class ParameterSelectionTableModel extends AbstractTableModel {
1355                 
1356                 private static final int PARAMETER = 0;
1357                 private static final int CURRENT = 1;
1358                 private static final int MIN = 2;
1359                 private static final int MAX = 3;
1360                 private static final int COUNT = 4;
1361                 
1362                 @Override
1363                 public int getColumnCount() {
1364                         return COUNT;
1365                 }
1366                 
1367                 @Override
1368                 public int getRowCount() {
1369                         return selectedModifiers.size();
1370                 }
1371                 
1372                 @Override
1373                 public String getColumnName(int column) {
1374                         switch (column) {
1375                         case PARAMETER:
1376                                 return trans.get("table.col.parameter");
1377                         case CURRENT:
1378                                 return trans.get("table.col.current");
1379                         case MIN:
1380                                 return trans.get("table.col.min");
1381                         case MAX:
1382                                 return trans.get("table.col.max");
1383                         default:
1384                                 throw new IndexOutOfBoundsException("column=" + column);
1385                         }
1386                         
1387                 }
1388                 
1389                 @Override
1390                 public Class<?> getColumnClass(int column) {
1391                         switch (column) {
1392                         case PARAMETER:
1393                                 return String.class;
1394                         case CURRENT:
1395                                 return Double.class;
1396                         case MIN:
1397                                 return Double.class;
1398                         case MAX:
1399                                 return Double.class;
1400                         default:
1401                                 throw new IndexOutOfBoundsException("column=" + column);
1402                         }
1403                 }
1404                 
1405                 @Override
1406                 public Object getValueAt(int row, int column) {
1407                         
1408                         SimulationModifier modifier = selectedModifiers.get(row);
1409                         
1410                         switch (column) {
1411                         case PARAMETER:
1412                                 return modifier.getRelatedObject().toString() + ": " + modifier.getName();
1413                         case CURRENT:
1414                                 try {
1415                                         return getModifierUnit(row).toUnit(modifier.getCurrentSIValue(getSelectedSimulation()));
1416                                 } catch (OptimizationException e) {
1417                                         throw new BugException("Could not read current SI value from modifier " + modifier, e);
1418                                 }
1419                         case MIN:
1420                                 return getModifierUnit(row).toUnit(modifier.getMinValue());
1421                         case MAX:
1422                                 return getModifierUnit(row).toUnit(modifier.getMaxValue());
1423                         default:
1424                                 throw new IndexOutOfBoundsException("column=" + column);
1425                         }
1426                         
1427                 }
1428                 
1429                 @Override
1430                 public void setValueAt(Object value, int row, int column) {
1431                         
1432                         switch (column) {
1433                         case PARAMETER:
1434                                 break;
1435                         
1436                         case MIN:
1437                                 double min = (Double) value;
1438                                 min = getModifierUnit(row).fromUnit(min);
1439                                 selectedModifiers.get(row).setMinValue(min);
1440                                 break;
1441                         
1442                         case CURRENT:
1443                                 break;
1444                         
1445                         case MAX:
1446                                 double max = (Double) value;
1447                                 max = getModifierUnit(row).fromUnit(max);
1448                                 selectedModifiers.get(row).setMaxValue(max);
1449                                 break;
1450                         
1451                         default:
1452                                 throw new IndexOutOfBoundsException("column=" + column);
1453                         }
1454                         this.fireTableRowsUpdated(row, row);
1455                         
1456                 }
1457                 
1458                 @Override
1459                 public boolean isCellEditable(int row, int column) {
1460                         switch (column) {
1461                         case PARAMETER:
1462                                 return false;
1463                         case CURRENT:
1464                                 return false;
1465                         case MIN:
1466                                 return true;
1467                         case MAX:
1468                                 return true;
1469                         default:
1470                                 throw new IndexOutOfBoundsException("column=" + column);
1471                         }
1472                 }
1473                 
1474         }
1475         
1476         
1477         private class DoubleCellRenderer extends DefaultTableCellRenderer {
1478                 @Override
1479                 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
1480                                 boolean hasFocus, int row, int column) {
1481                         
1482                         super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1483                         
1484                         double val = (Double) value;
1485                         Unit unit = getModifierUnit(row);
1486                         
1487                         val = unit.fromUnit(val);
1488                         this.setText(unit.toStringUnit(val));
1489                         
1490                         return this;
1491                 }
1492         }
1493         
1494         
1495         private static class SimulationModifierComparator implements Comparator<SimulationModifier> {
1496                 
1497                 @Override
1498                 public int compare(SimulationModifier mod1, SimulationModifier mod2) {
1499                         Object rel1 = mod1.getRelatedObject();
1500                         Object rel2 = mod2.getRelatedObject();
1501                         
1502                         /*
1503                          * Primarily order by related object:
1504                          * 
1505                          * - RocketComponents first
1506                          * - Two RocketComponents are ordered based on their position in the rocket
1507                          */
1508                         if (!rel1.equals(rel2)) {
1509                                 
1510                                 if (rel1 instanceof RocketComponent) {
1511                                         if (rel2 instanceof RocketComponent) {
1512                                                 
1513                                                 RocketComponent root = ((RocketComponent) rel1).getRoot();
1514                                                 for (RocketComponent c : root) {
1515                                                         if (c.equals(rel1)) {
1516                                                                 return -1;
1517                                                         }
1518                                                         if (c.equals(rel2)) {
1519                                                                 return 1;
1520                                                         }
1521                                                 }
1522                                                 
1523                                                 throw new BugException("Error sorting modifiers, mod1=" + mod1 + " rel1=" + rel1 +
1524                                                                 " mod2=" + mod2 + " rel2=" + rel2);
1525                                                 
1526                                         } else {
1527                                                 return -1;
1528                                         }
1529                                 } else {
1530                                         if (rel2 instanceof RocketComponent) {
1531                                                 return 1;
1532                                         }
1533                                 }
1534                                 
1535                         }
1536                         
1537                         // Secondarily sort by name
1538                         return collator.compare(mod1.getName(), mod2.getName());
1539                 }
1540         }
1541         
1542
1543
1544 }