source: branches/PerformanceComparison/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.cs @ 14101

Last change on this file since 14101 was 14101, checked in by abeham, 4 years ago

#2634: Fixed some remaining bugs in runcollection rld view regarding runs with unequal length

File size: 50.2 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.ComponentModel;
25using System.Drawing;
26using System.Globalization;
27using System.Linq;
28using System.Windows.Forms;
29using System.Windows.Forms.DataVisualization.Charting;
30using HeuristicLab.Analysis;
31using HeuristicLab.Collections;
32using HeuristicLab.Core.Views;
33using HeuristicLab.Data;
34using HeuristicLab.MainForm;
35using HeuristicLab.MainForm.WindowsForms;
36
37namespace HeuristicLab.Optimization.Views {
38  [View("Run-length Distribution View")]
39  [Content(typeof(RunCollection), false)]
40  public partial class RunCollectionRLDView : ItemView {
41    private List<Series> invisibleTargetSeries;
42
43    private const string AllRuns = "All Runs";
44
45    private static readonly Color[] colors = new[] {
46      Color.FromArgb(0x40, 0x6A, 0xB7),
47      Color.FromArgb(0xB1, 0x6D, 0x01),
48      Color.FromArgb(0x4E, 0x8A, 0x06),
49      Color.FromArgb(0x75, 0x50, 0x7B),
50      Color.FromArgb(0x72, 0x9F, 0xCF),
51      Color.FromArgb(0xA4, 0x00, 0x00),
52      Color.FromArgb(0xAD, 0x7F, 0xA8),
53      Color.FromArgb(0x29, 0x50, 0xCF),
54      Color.FromArgb(0x90, 0xB0, 0x60),
55      Color.FromArgb(0xF5, 0x89, 0x30),
56      Color.FromArgb(0x55, 0x57, 0x53),
57      Color.FromArgb(0xEF, 0x59, 0x59),
58      Color.FromArgb(0xED, 0xD4, 0x30),
59      Color.FromArgb(0x63, 0xC2, 0x16),
60    };
61    private static readonly ChartDashStyle[] lineStyles = new[] {
62      ChartDashStyle.Solid,
63      ChartDashStyle.Dash,
64      ChartDashStyle.DashDot,
65      ChartDashStyle.Dot
66    };
67    private static readonly DataRowVisualProperties.DataRowLineStyle[] hlLineStyles = new[] {
68      DataRowVisualProperties.DataRowLineStyle.Solid,
69      DataRowVisualProperties.DataRowLineStyle.Dash,
70      DataRowVisualProperties.DataRowLineStyle.DashDot,
71      DataRowVisualProperties.DataRowLineStyle.Dot
72    };
73
74    public new RunCollection Content {
75      get { return (RunCollection)base.Content; }
76      set { base.Content = value; }
77    }
78
79    private double[] targets;
80    private double[] budgets;
81
82    private bool suppressUpdates;
83    private readonly IndexedDataTable<double> byCostDataTable;
84    public IndexedDataTable<double> ByCostDataTable {
85      get { return byCostDataTable; }
86    }
87
88    public RunCollectionRLDView() {
89      InitializeComponent();
90      invisibleTargetSeries = new List<Series>();
91
92      targetChart.CustomizeAllChartAreas();
93      targetChart.ChartAreas[0].CursorX.Interval = 1;
94      targetChart.SuppressExceptions = true;
95      byCostDataTable = new IndexedDataTable<double>("ECDF by Cost", "A data table containing the ECDF of each of a number of groups.") {
96        VisualProperties = {
97          YAxisTitle = "Proportion of unused budgets",
98          YAxisMinimumFixedValue = 0,
99          YAxisMinimumAuto = false,
100          YAxisMaximumFixedValue = 1,
101          YAxisMaximumAuto = false
102        }
103      };
104      byCostViewHost.Content = byCostDataTable;
105      suppressUpdates = false;
106    }
107
108    #region Content events
109    protected override void RegisterContentEvents() {
110      base.RegisterContentEvents();
111      Content.ItemsAdded += Content_ItemsAdded;
112      Content.ItemsRemoved += Content_ItemsRemoved;
113      Content.CollectionReset += Content_CollectionReset;
114      Content.UpdateOfRunsInProgressChanged += Content_UpdateOfRunsInProgressChanged;
115      Content.OptimizerNameChanged += Content_AlgorithmNameChanged;
116    }
117    protected override void DeregisterContentEvents() {
118      Content.ItemsAdded -= Content_ItemsAdded;
119      Content.ItemsRemoved -= Content_ItemsRemoved;
120      Content.CollectionReset -= Content_CollectionReset;
121      Content.UpdateOfRunsInProgressChanged -= Content_UpdateOfRunsInProgressChanged;
122      Content.OptimizerNameChanged -= Content_AlgorithmNameChanged;
123      base.DeregisterContentEvents();
124    }
125
126    private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IRun> e) {
127      if (suppressUpdates) return;
128      if (InvokeRequired) {
129        Invoke(new CollectionItemsChangedEventHandler<IRun>(Content_ItemsAdded), sender, e);
130        return;
131      }
132      UpdateGroupAndProblemComboBox();
133      UpdateDataTableComboBox();
134      foreach (var run in e.Items)
135        RegisterRunEvents(run);
136    }
137    private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IRun> e) {
138      if (suppressUpdates) return;
139      if (InvokeRequired) {
140        Invoke(new CollectionItemsChangedEventHandler<IRun>(Content_ItemsRemoved), sender, e);
141        return;
142      }
143      UpdateGroupAndProblemComboBox();
144      UpdateDataTableComboBox();
145      foreach (var run in e.Items)
146        DeregisterRunEvents(run);
147    }
148    private void Content_CollectionReset(object sender, CollectionItemsChangedEventArgs<IRun> e) {
149      if (suppressUpdates) return;
150      if (InvokeRequired) {
151        Invoke(new CollectionItemsChangedEventHandler<IRun>(Content_CollectionReset), sender, e);
152        return;
153      }
154      UpdateGroupAndProblemComboBox();
155      UpdateDataTableComboBox();
156      foreach (var run in e.OldItems)
157        DeregisterRunEvents(run);
158    }
159    private void Content_AlgorithmNameChanged(object sender, EventArgs e) {
160      if (InvokeRequired)
161        Invoke(new EventHandler(Content_AlgorithmNameChanged), sender, e);
162      else UpdateCaption();
163    }
164    private void Content_UpdateOfRunsInProgressChanged(object sender, EventArgs e) {
165      if (InvokeRequired) {
166        Invoke(new EventHandler(Content_UpdateOfRunsInProgressChanged), sender, e);
167        return;
168      }
169      suppressUpdates = Content.UpdateOfRunsInProgress;
170      if (!suppressUpdates) {
171        UpdateDataTableComboBox();
172        UpdateGroupAndProblemComboBox();
173        UpdateRuns();
174      }
175    }
176
177    private void RegisterRunEvents(IRun run) {
178      run.PropertyChanged += run_PropertyChanged;
179    }
180    private void DeregisterRunEvents(IRun run) {
181      run.PropertyChanged -= run_PropertyChanged;
182    }
183    private void run_PropertyChanged(object sender, PropertyChangedEventArgs e) {
184      if (suppressUpdates) return;
185      if (InvokeRequired) {
186        Invoke((Action<object, PropertyChangedEventArgs>)run_PropertyChanged, sender, e);
187      } else {
188        if (e.PropertyName == "Visible")
189          UpdateRuns();
190      }
191    }
192    #endregion
193
194    protected override void OnContentChanged() {
195      base.OnContentChanged();
196      dataTableComboBox.Items.Clear();
197      groupComboBox.Items.Clear();
198      targetChart.ChartAreas[0].AxisX.IsLogarithmic = false;
199      targetChart.Series.Clear();
200      invisibleTargetSeries.Clear();
201      byCostDataTable.VisualProperties.XAxisLogScale = false;
202      byCostDataTable.Rows.Clear();
203
204      UpdateCaption();
205      if (Content != null) {
206        UpdateGroupAndProblemComboBox();
207        UpdateDataTableComboBox();
208      }
209    }
210
211
212    private void UpdateGroupAndProblemComboBox() {
213      var selectedGroupItem = (string)groupComboBox.SelectedItem;
214
215      var groupings = Content.ParameterNames.OrderBy(x => x).ToArray();
216      groupComboBox.Items.Clear();
217      groupComboBox.Items.Add(AllRuns);
218      groupComboBox.Items.AddRange(groupings);
219      if (selectedGroupItem != null && groupComboBox.Items.Contains(selectedGroupItem)) {
220        groupComboBox.SelectedItem = selectedGroupItem;
221      } else if (groupComboBox.Items.Count > 0) {
222        groupComboBox.SelectedItem = groupComboBox.Items[0];
223      }
224
225      var problems = new HashSet<ProblemDescription>();
226      foreach (var run in Content) {
227        problems.Add(new ProblemDescription(run));
228      }
229
230      var problemTypesDifferent = problems.Select(x => x.ProblemType).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
231      var problemNamesDifferent = problems.Select(x => x.ProblemName).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
232      var evaluatorDifferent = problems.Select(x => x.Evaluator).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
233      var maximizationDifferent = problems.Select(x => x.Maximization).Distinct().Count() > 1;
234      var allEqual = !problemTypesDifferent && !problemNamesDifferent && !evaluatorDifferent && !maximizationDifferent;
235
236      var selectedProblemItem = (ProblemDescription)problemComboBox.SelectedItem;
237      problemComboBox.Items.Clear();
238      problemComboBox.Items.Add(ProblemDescription.MatchAll);
239      if (selectedProblemItem == null || selectedProblemItem == ProblemDescription.MatchAll)
240        problemComboBox.SelectedIndex = 0;
241      foreach (var prob in problems.OrderBy(x => x.ToString()).ToList()) {
242        prob.DisplayProblemType = problemTypesDifferent;
243        prob.DisplayProblemName = problemNamesDifferent || allEqual;
244        prob.DisplayEvaluator = evaluatorDifferent;
245        prob.DisplayMaximization = maximizationDifferent;
246        problemComboBox.Items.Add(prob);
247        if (prob.Equals(selectedProblemItem)) problemComboBox.SelectedItem = prob;
248      }
249      SetEnabledStateOfControls();
250    }
251
252    private void UpdateDataTableComboBox() {
253      string selectedItem = (string)dataTableComboBox.SelectedItem;
254
255      dataTableComboBox.Items.Clear();
256      var dataTables = (from run in Content
257                        from result in run.Results
258                        where result.Value is IndexedDataTable<double>
259                        select result.Key).Distinct().ToArray();
260
261      dataTableComboBox.Items.AddRange(dataTables);
262      if (selectedItem != null && dataTableComboBox.Items.Contains(selectedItem)) {
263        dataTableComboBox.SelectedItem = selectedItem;
264      } else if (dataTableComboBox.Items.Count > 0) {
265        dataTableComboBox.SelectedItem = dataTableComboBox.Items[0];
266      }
267    }
268
269    protected override void SetEnabledStateOfControls() {
270      base.SetEnabledStateOfControls();
271      groupComboBox.Enabled = Content != null;
272      problemComboBox.Enabled = Content != null && problemComboBox.Items.Count > 1;
273      dataTableComboBox.Enabled = Content != null && dataTableComboBox.Items.Count > 1;
274      addTargetsAsResultButton.Enabled = Content != null && targets != null && dataTableComboBox.SelectedIndex >= 0;
275      addBudgetsAsResultButton.Enabled = Content != null && budgets != null && dataTableComboBox.SelectedIndex >= 0;
276    }
277
278    private Dictionary<string, Dictionary<ProblemDescription, Tuple<double, List<IRun>>>> GroupRuns() {
279      var groupedRuns = new Dictionary<string, Dictionary<ProblemDescription, Tuple<double, List<IRun>>>>();
280
281      var table = (string)dataTableComboBox.SelectedItem;
282      if (string.IsNullOrEmpty(table)) return groupedRuns;
283
284      var selectedGroup = (string)groupComboBox.SelectedItem;
285      if (string.IsNullOrEmpty(selectedGroup)) return groupedRuns;
286
287      var selectedProblem = (ProblemDescription)problemComboBox.SelectedItem;
288      if (selectedProblem == null) return groupedRuns;
289
290      var targetsPerProblem = CalculateBestTargetPerProblemInstance(table);
291
292      foreach (var x in (from r in Content
293                         where (selectedGroup == AllRuns || r.Parameters.ContainsKey(selectedGroup))
294                           && selectedProblem.Match(r)
295                           && r.Results.ContainsKey(table)
296                           && r.Visible
297                         group r by selectedGroup == AllRuns ? AllRuns : r.Parameters[selectedGroup].ToString() into g
298                         select Tuple.Create(g.Key, g.ToList()))) {
299        var pDict = new Dictionary<ProblemDescription, Tuple<double, List<IRun>>>();
300        foreach (var y in (from r in x.Item2
301                           let pd = new ProblemDescription(r)
302                           group r by pd into g
303                           select Tuple.Create(g.Key, g.ToList()))) {
304          pDict[y.Item1] = Tuple.Create(targetsPerProblem[y.Item1], y.Item2);
305        }
306        groupedRuns[x.Item1] = pDict;
307      }
308
309      return groupedRuns;
310    }
311
312    #region Performance analysis by (multiple) target(s)
313    private void UpdateResultsByTarget() {
314      // necessary to reset log scale -> empty chart cannot use log scaling
315      targetChart.ChartAreas[0].AxisX.IsLogarithmic = false;
316      targetChart.Series.Clear();
317      invisibleTargetSeries.Clear();
318
319      var table = (string)dataTableComboBox.SelectedItem;
320      if (string.IsNullOrEmpty(table)) return;
321
322      if (targets == null) GenerateDefaultTargets();
323
324      var groupedRuns = GroupRuns();
325      if (groupedRuns.Count == 0) return;
326
327      var xAxisTitles = new HashSet<string>();
328      var colorCount = 0;
329      var lineStyleCount = 0;
330
331      // if the group contains multiple different problem instances we want to use the
332      // minimal maximal observed effort otherwise we run into situations where we don't
333      // have data for a certain problem instance anymore this is a special case when
334      // aggregating over multiple problem instances     
335      var maxEfforts = new Dictionary<ProblemDescription, double>();
336      double minEff = double.MaxValue, maxEff = double.MinValue;
337      foreach (var group in groupedRuns) {
338        foreach (var problem in group.Value) {
339          double problemSpecificMaxEff;
340          if (!maxEfforts.TryGetValue(problem.Key, out problemSpecificMaxEff)) {
341            problemSpecificMaxEff = 0;
342          }
343          var bestKnownTarget = problem.Value.Item1;
344          var max = problem.Key.IsMaximization();
345          var worstTarget = (max ? (1 - targets.Max()) : (1 + targets.Max())) * bestKnownTarget;
346          var bestTarget = (max ? (1 - targets.Min()) : (1 + targets.Min())) * bestKnownTarget;
347          foreach (var run in problem.Value.Item2) {
348            var row = ((IndexedDataTable<double>)run.Results[table]).Rows.First().Values;
349            var a = row.FirstOrDefault(x => max ? x.Item2 >= worstTarget : x.Item2 <= worstTarget);
350            var b = row.FirstOrDefault(x => max ? x.Item2 >= bestTarget : x.Item2 <= bestTarget);
351            var firstEff = (a == default(Tuple<double, double>)) ? row.Last().Item1 : a.Item1;
352            var lastEff = (b == default(Tuple<double, double>)) ? row.Last().Item1 : b.Item1;
353            if (minEff > firstEff) minEff = firstEff;
354            if (maxEff < lastEff) maxEff = lastEff;
355            if (problemSpecificMaxEff < lastEff) problemSpecificMaxEff = lastEff;
356          }
357          maxEfforts[problem.Key] = problemSpecificMaxEff;
358        }
359      }
360      maxEff = Math.Min(maxEff, maxEfforts.Values.Min());
361
362      var minZeros = (int)Math.Floor(Math.Log10(minEff));
363      var maxZeros = (int)Math.Floor(Math.Log10(maxEff));
364      var axisMin = (decimal)Math.Pow(10, minZeros);
365      var axisMax = (decimal)Math.Pow(10, maxZeros);
366      if (!targetLogScalingCheckBox.Checked) {
367        var minAdd = (decimal)Math.Pow(10, minZeros - 1) * 2;
368        var maxAdd = (decimal)Math.Pow(10, maxZeros - 1) * 2;
369        while (axisMin + minAdd < (decimal)minEff) axisMin += minAdd;
370        while (axisMax <= (decimal)maxEff) axisMax += maxAdd;
371      } else axisMax = (decimal)Math.Pow(10, (int)Math.Ceiling(Math.Log10(maxEff)));
372      targetChart.ChartAreas[0].AxisX.Minimum = (double)axisMin;
373      targetChart.ChartAreas[0].AxisX.Maximum = (double)axisMax;
374
375      foreach (var group in groupedRuns) {
376        // hits describes the number of target hits at a certain time for a certain group
377        var hits = new Dictionary<string, SortedList<double, int>>();
378        // misses describes the number of target misses after a certain time for a certain group
379        // for instance when a run ends, but has not achieved all targets, misses describes
380        // how many targets have been left open at the point when the run ended
381        var misses = new Dictionary<string, SortedList<double, int>>();
382        var maxLength = 0.0;
383
384        var noRuns = 0;
385        foreach (var problem in group.Value) {
386          foreach (var run in problem.Value.Item2) {
387            var resultsTable = (IndexedDataTable<double>)run.Results[table];
388            xAxisTitles.Add(resultsTable.VisualProperties.XAxisTitle);
389
390            if (aggregateTargetsCheckBox.Checked) {
391              var length = CalculateHitsForAllTargets(hits, misses, resultsTable.Rows.First(), problem.Key, group.Key, problem.Value.Item1, maxEff);
392              maxLength = Math.Max(length, maxLength);
393            } else {
394              CalculateHitsForEachTarget(hits, misses, resultsTable.Rows.First(), problem.Key, group.Key, problem.Value.Item1, maxEff);
395            }
396            noRuns++;
397          }
398        }
399        foreach (var list in hits) {
400          var row = new Series(list.Key) {
401            ChartType = SeriesChartType.StepLine,
402            BorderWidth = 2,
403            Color = colors[colorCount],
404            BorderDashStyle = lineStyles[lineStyleCount],
405          };
406          var rowShade = new Series(list.Key + "-range") {
407            IsVisibleInLegend = false,
408            ChartType = SeriesChartType.Range,
409            Color = Color.FromArgb(32, colors[colorCount])
410          };
411
412          var ecdf = 0.0;
413          var missedecdf = 0.0;
414          var iter = misses[list.Key].GetEnumerator();
415          var moreMisses = iter.MoveNext();
416          var totalTargets = noRuns;
417          if (aggregateTargetsCheckBox.Checked) totalTargets *= targets.Length;
418          var movingTargets = totalTargets;
419          foreach (var h in list.Value) {
420            while (moreMisses && iter.Current.Key <= h.Key) {
421              missedecdf += iter.Current.Value;
422              movingTargets -= iter.Current.Value;
423              if (row.Points.Count > 0 && row.Points.Last().XValue == iter.Current.Key)
424                row.Points.Last().SetValueY(ecdf / movingTargets);
425              else row.Points.AddXY(iter.Current.Key, ecdf / movingTargets);
426              if (boundShadingCheckBox.Checked) {
427                if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == iter.Current.Key)
428                  rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
429                else rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
430              }
431              moreMisses = iter.MoveNext();
432            }
433            ecdf += h.Value;
434            if (row.Points.Count > 0 && row.Points.Last().XValue == h.Key)
435              row.Points.Last().SetValueY(ecdf / movingTargets);
436            else row.Points.AddXY(h.Key, ecdf / movingTargets);
437            if (missedecdf > 0 && boundShadingCheckBox.Checked) {
438              if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == h.Key)
439                rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
440              else rowShade.Points.Add(new DataPoint(h.Key, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
441            }
442          }
443
444          while (moreMisses) {
445            // if there are misses beyond the last hit we extend the shaded area
446            missedecdf += iter.Current.Value;
447            //movingTargets -= iter.Current.Value;
448            if (row.Points.Count > 0 && row.Points.Last().XValue == iter.Current.Key)
449              row.Points.Last().SetValueY(ecdf / movingTargets);
450            else row.Points.AddXY(iter.Current.Key, ecdf / movingTargets);
451            if (boundShadingCheckBox.Checked) {
452              if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == iter.Current.Key)
453                rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
454              else rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
455            }
456            moreMisses = iter.MoveNext();
457          }
458
459          if (maxLength > 0 && (row.Points.Count == 0 || row.Points.Last().XValue < maxLength))
460            row.Points.AddXY(maxLength, ecdf / movingTargets);
461
462          if (row.Points.Count > 0) {
463            var point = row.Points.Last();
464            point.Label = row.Name;
465            point.MarkerStyle = MarkerStyle.Cross;
466            point.MarkerBorderWidth = 1;
467          }
468
469          ConfigureSeries(row);
470          targetChart.Series.Add(rowShade);
471          targetChart.Series.Add(row);
472        }
473        colorCount = (colorCount + 1) % colors.Length;
474        if (colorCount == 0) lineStyleCount = (lineStyleCount + 1) % lineStyles.Length;
475      }
476
477      if (targets.Length == 1)
478        targetChart.ChartAreas[0].AxisY.Title = "Probability to be " + (targets[0] * 100) + "% worse than best";
479      else targetChart.ChartAreas[0].AxisY.Title = "Proportion of reached targets";
480      targetChart.ChartAreas[0].AxisX.Title = string.Join(" / ", xAxisTitles);
481      targetChart.ChartAreas[0].AxisX.IsLogarithmic = CanDisplayLogarithmic();
482      targetChart.ChartAreas[0].CursorY.Interval = 0.05;
483      UpdateErtTables(groupedRuns);
484    }
485
486    private void GenerateDefaultTargets() {
487      targets = new[] { 0.1, 0.05, 0.02, 0.01, 0 };
488      suppressTargetsEvents = true;
489      targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%";
490      suppressTargetsEvents = false;
491    }
492
493    private void CalculateHitsForEachTarget(Dictionary<string, SortedList<double, int>> hits,
494                                            Dictionary<string, SortedList<double, int>> misses,
495                                            IndexedDataRow<double> row, ProblemDescription problem,
496                                            string group, double bestTarget, double maxEffort) {
497      foreach (var t in targets.Select(x => Tuple.Create((problem.IsMaximization() ? (1 - x) : (1 + x)) * bestTarget, x))) {
498        var l = t.Item1;
499        var key = group + "_" + (t.Item2 * 100) + "%_" + l;
500        if (!hits.ContainsKey(key)) {
501          hits.Add(key, new SortedList<double, int>());
502          misses.Add(key, new SortedList<double, int>());
503        }
504        var hit = false;
505        foreach (var v in row.Values) {
506          if (v.Item1 > maxEffort) break;
507          if (problem.IsMaximization() && v.Item2 >= l || !problem.IsMaximization() && v.Item2 <= l) {
508            if (hits[key].ContainsKey(v.Item1))
509              hits[key][v.Item1]++;
510            else hits[key][v.Item1] = 1;
511            hit = true;
512            break;
513          }
514        }
515        if (!hit) {
516          var max = Math.Min(row.Values.Last().Item1, maxEffort);
517          if (misses[key].ContainsKey(max))
518            misses[key][max]++;
519          else misses[key][max] = 1;
520        }
521      }
522    }
523
524    private double CalculateHitsForAllTargets(Dictionary<string, SortedList<double, int>> hits,
525                                              Dictionary<string, SortedList<double, int>> misses,
526                                              IndexedDataRow<double> row, ProblemDescription problem,
527                                              string group, double bestTarget, double maxEffort) {
528      var values = row.Values;
529      if (!hits.ContainsKey(group)) {
530        hits.Add(group, new SortedList<double, int>());
531        misses.Add(group, new SortedList<double, int>());
532      }
533
534      var i = 0;
535      var j = 0;
536      while (i < targets.Length && j < values.Count) {
537        var target = (problem.IsMaximization() ? (1 - targets[i]) : (1 + targets[i])) * bestTarget;
538        var current = values[j];
539        if (current.Item1 > maxEffort) break;
540        if (problem.IsMaximization() && current.Item2 >= target
541          || !problem.IsMaximization() && current.Item2 <= target) {
542          if (hits[group].ContainsKey(current.Item1)) hits[group][current.Item1]++;
543          else hits[group][current.Item1] = 1;
544          i++;
545        } else {
546          j++;
547        }
548      }
549      if (j == values.Count) j--;
550      var effort = Math.Min(values[j].Item1, maxEffort);
551      if (i < targets.Length) {
552        if (misses[group].ContainsKey(effort))
553          misses[group][effort] += targets.Length - i;
554        else misses[group][effort] = targets.Length - i;
555      }
556      return effort;
557    }
558
559    private void UpdateErtTables(Dictionary<string, Dictionary<ProblemDescription, Tuple<double, List<IRun>>>> groupedRuns) {
560      ertTableView.Content = null;
561      var columns = 1 + targets.Length + 1;
562      var matrix = new string[groupedRuns.Count * groupedRuns.Max(x => x.Value.Count) + groupedRuns.Max(x => x.Value.Count), columns];
563      var rowCount = 0;
564
565      var tableName = (string)dataTableComboBox.SelectedItem;
566      if (string.IsNullOrEmpty(tableName)) return;
567
568      var targetsPerProblem = CalculateBestTargetPerProblemInstance(tableName);
569
570      var colNames = new string[columns];
571      colNames[0] = colNames[colNames.Length - 1] = string.Empty;
572      for (var i = 0; i < targets.Length; i++) {
573        colNames[i + 1] = targets[i].ToString("0.0%");
574      }
575      var problems = groupedRuns.SelectMany(x => x.Value.Keys).Distinct().ToList();
576
577      foreach (var problem in problems) {
578        matrix[rowCount, 0] = problem.ToString();
579        for (var i = 0; i < targets.Length; i++) {
580          matrix[rowCount, i + 1] = (targetsPerProblem[problem] * (problem.IsMaximization() ? (1 - targets[i]) : (1 + targets[i]))).ToString(CultureInfo.CurrentCulture.NumberFormat);
581        }
582        matrix[rowCount, columns - 1] = "#succ";
583        rowCount++;
584
585        foreach (var group in groupedRuns) {
586          matrix[rowCount, 0] = group.Key;
587          if (!group.Value.ContainsKey(problem)) {
588            matrix[rowCount, columns - 1] = "N/A";
589            rowCount++;
590            continue;
591          }
592          var runs = group.Value[problem].Item2;
593          ErtCalculationResult result = default(ErtCalculationResult);
594          for (var i = 0; i < targets.Length; i++) {
595            result = ExpectedRuntimeHelper.CalculateErt(runs, tableName, (problem.IsMaximization() ? (1 - targets[i]) : (1 + targets[i])) * group.Value[problem].Item1, problem.IsMaximization());
596            matrix[rowCount, i + 1] = result.ToString();
597          }
598          matrix[rowCount, columns - 1] = targets.Length > 0 ? result.SuccessfulRuns + "/" + result.TotalRuns : "-";
599          rowCount++;
600        }
601      }
602      ertTableView.Content = new StringMatrix(matrix) { ColumnNames = colNames };
603      ertTableView.DataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
604    }
605    #endregion
606
607    #region Performance analysis by (multiple) budget(s)
608    private void UpdateResultsByCost() {
609      // necessary to reset log scale -> empty chart cannot use log scaling
610      byCostDataTable.VisualProperties.XAxisLogScale = false;
611      byCostDataTable.Rows.Clear();
612
613      var table = (string)dataTableComboBox.SelectedItem;
614      if (string.IsNullOrEmpty(table)) return;
615
616      if (budgets == null) GenerateDefaultBudgets(table);
617
618      var groupedRuns = GroupRuns();
619      if (groupedRuns.Count == 0) return;
620
621      var colorCount = 0;
622      var lineStyleCount = 0;
623
624      var targetsPerProblem = CalculateBestTargetPerProblemInstance((string)dataTableComboBox.SelectedItem);
625
626      foreach (var group in groupedRuns) {
627        var hits = new Dictionary<string, SortedList<double, double>>();
628
629        foreach (var problem in group.Value) {
630          foreach (var run in problem.Value.Item2) {
631            var resultsTable = (IndexedDataTable<double>)run.Results[table];
632
633            if (eachOrAllBudgetsCheckBox.Checked) {
634              CalculateHitsForEachBudget(hits, resultsTable.Rows.First(), group.Value.Count, problem.Key, group.Key, problem.Value.Item2.Count, targetsPerProblem[problem.Key]);
635            } else {
636              CalculateHitsForAllBudgets(hits, resultsTable.Rows.First(), group.Value.Count, problem.Key, group.Key, problem.Value.Item2.Count, targetsPerProblem[problem.Key]);
637            }
638          }
639        }
640
641        foreach (var list in hits) {
642          var row = new IndexedDataRow<double>(list.Key) {
643            VisualProperties = {
644              ChartType = DataRowVisualProperties.DataRowChartType.StepLine,
645              LineWidth = 2,
646              Color = colors[colorCount],
647              LineStyle = hlLineStyles[lineStyleCount],
648              StartIndexZero = false
649            }
650          };
651
652          var total = 0.0;
653          foreach (var h in list.Value) {
654            total += h.Value;
655            row.Values.Add(Tuple.Create(h.Key, total));
656          }
657
658          byCostDataTable.Rows.Add(row);
659        }
660        colorCount = (colorCount + 1) % colors.Length;
661        if (colorCount == 0) lineStyleCount = (lineStyleCount + 1) % lineStyles.Length;
662      }
663
664      byCostDataTable.VisualProperties.XAxisTitle = "Targets to Best-Known Ratio";
665      byCostDataTable.VisualProperties.XAxisLogScale = byCostDataTable.Rows.Count > 0 && budgetLogScalingCheckBox.Checked;
666    }
667
668    private void GenerateDefaultBudgets(string table) {
669      var runs = GroupRuns().SelectMany(x => x.Value.Values).SelectMany(x => x.Item2).ToList();
670      var min = runs.Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Select(y => y.Item1).Min()).Min();
671      var max = runs.Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Select(y => y.Item1).Max()).Max();
672
673      var maxMagnitude = (int)Math.Ceiling(Math.Log10(max));
674      var minMagnitude = (int)Math.Floor(Math.Log10(min));
675      if (maxMagnitude - minMagnitude >= 3) {
676        budgets = new double[maxMagnitude - minMagnitude];
677        for (var i = minMagnitude; i < maxMagnitude; i++) {
678          budgets[i - minMagnitude] = Math.Pow(10, i);
679        }
680      } else {
681        var range = max - min;
682        budgets = Enumerable.Range(0, 6).Select(x => min + (x / 5.0) * range).ToArray();
683      }
684      suppressBudgetsEvents = true;
685      budgetsTextBox.Text = string.Join(" ; ", budgets);
686      suppressBudgetsEvents = false;
687    }
688
689    private void CalculateHitsForEachBudget(Dictionary<string, SortedList<double, double>> hits, IndexedDataRow<double> row, int groupCount, ProblemDescription problem, string groupName, int problemCount, double bestTarget) {
690      foreach (var b in budgets) {
691        var key = groupName + "-" + b;
692        if (!hits.ContainsKey(key)) hits.Add(key, new SortedList<double, double>());
693        Tuple<double, double> prev = null;
694        foreach (var v in row.Values) {
695          if (v.Item1 >= b) {
696            // the budget may be too low to achieve any target
697            if (prev == null && v.Item1 != b) break;
698            var tgt = ((prev == null || v.Item1 == b) ? v.Item2 : prev.Item2);
699            tgt = problem.IsMaximization() ? bestTarget / tgt : tgt / bestTarget;
700            if (hits[key].ContainsKey(tgt))
701              hits[key][tgt] += 1.0 / (groupCount * problemCount);
702            else hits[key][tgt] = 1.0 / (groupCount * problemCount);
703            break;
704          }
705          prev = v;
706        }
707        if (hits[key].Count == 0) hits.Remove(key);
708      }
709    }
710
711    private void CalculateHitsForAllBudgets(Dictionary<string, SortedList<double, double>> hits, IndexedDataRow<double> row, int groupCount, ProblemDescription problem, string groupName, int problemCount, double bestTarget) {
712      var values = row.Values;
713      if (!hits.ContainsKey(groupName)) hits.Add(groupName, new SortedList<double, double>());
714
715      var i = 0;
716      var j = 0;
717      Tuple<double, double> prev = null;
718      while (i < budgets.Length && j < values.Count) {
719        var current = values[j];
720        if (current.Item1 >= budgets[i]) {
721          if (prev != null || current.Item1 == budgets[i]) {
722            var tgt = (prev == null || current.Item1 == budgets[i]) ? current.Item2 : prev.Item2;
723            tgt = problem.IsMaximization() ? bestTarget / tgt : tgt / bestTarget;
724            if (!hits[groupName].ContainsKey(tgt)) hits[groupName][tgt] = 0;
725            hits[groupName][tgt] += 1.0 / (groupCount * problemCount * budgets.Length);
726          }
727          i++;
728        } else {
729          j++;
730          prev = current;
731        }
732      }
733      var lastTgt = values.Last().Item2;
734      lastTgt = problem.IsMaximization() ? bestTarget / lastTgt : lastTgt / bestTarget;
735      if (i < budgets.Length && !hits[groupName].ContainsKey(lastTgt)) hits[groupName][lastTgt] = 0;
736      while (i < budgets.Length) {
737        hits[groupName][lastTgt] += 1.0 / (groupCount * problemCount * budgets.Length);
738        i++;
739      }
740    }
741    #endregion
742
743    private void UpdateCaption() {
744      Caption = Content != null ? Content.OptimizerName + " RLD View" : ViewAttribute.GetViewName(GetType());
745    }
746
747    private void groupComboBox_SelectedIndexChanged(object sender, EventArgs e) {
748      UpdateRuns();
749      SetEnabledStateOfControls();
750    }
751    private void problemComboBox_SelectedIndexChanged(object sender, EventArgs e) {
752      UpdateRuns();
753      SetEnabledStateOfControls();
754    }
755    private void dataTableComboBox_SelectedIndexChanged(object sender, EventArgs e) {
756      if (dataTableComboBox.SelectedIndex >= 0)
757        GenerateDefaultBudgets((string)dataTableComboBox.SelectedItem);
758      UpdateRuns();
759      SetEnabledStateOfControls();
760    }
761
762    private void logScalingCheckBox_CheckedChanged(object sender, EventArgs e) {
763      UpdateResultsByTarget();
764      byCostDataTable.VisualProperties.XAxisLogScale = byCostDataTable.Rows.Count > 0 && budgetLogScalingCheckBox.Checked;
765    }
766
767    private void boundShadingCheckBox_CheckedChanged(object sender, EventArgs e) {
768      UpdateResultsByTarget();
769    }
770
771    #region Event handlers for target analysis
772    private bool suppressTargetsEvents;
773    private void targetsTextBox_Validating(object sender, CancelEventArgs e) {
774      if (suppressTargetsEvents) return;
775      var targetStrings = targetsTextBox.Text.Split(new[] { '%', ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
776      var targetList = new List<decimal>();
777      foreach (var ts in targetStrings) {
778        decimal t;
779        if (!decimal.TryParse(ts, out t)) {
780          errorProvider.SetError(targetsTextBox, "Not all targets can be parsed: " + ts);
781          e.Cancel = true;
782          return;
783        }
784        targetList.Add(t / 100);
785      }
786      if (targetList.Count == 0) {
787        errorProvider.SetError(targetsTextBox, "Give at least one target value!");
788        e.Cancel = true;
789        return;
790      }
791      e.Cancel = false;
792      errorProvider.SetError(targetsTextBox, null);
793      targets = targetList.Select(x => (double)x).OrderByDescending(x => x).ToArray();
794      suppressTargetsEvents = true;
795      try { targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%"; } finally { suppressTargetsEvents = false; }
796
797      UpdateResultsByTarget();
798      SetEnabledStateOfControls();
799    }
800
801    private void aggregateTargetsCheckBox_CheckedChanged(object sender, EventArgs e) {
802      SuspendRepaint();
803      try {
804        UpdateResultsByTarget();
805      } finally { ResumeRepaint(true); }
806    }
807
808    private void generateTargetsButton_Click(object sender, EventArgs e) {
809      decimal max = 1, min = 0, count = 10;
810      if (targets != null) {
811        max = (decimal)targets.Max();
812        min = (decimal)targets.Min();
813        count = targets.Length;
814      } else if (Content.Count > 0 && dataTableComboBox.SelectedIndex >= 0) {
815        var table = (string)dataTableComboBox.SelectedItem;
816        max = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Max(y => y.Item2)).Max();
817        min = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Min(y => y.Item2)).Min();
818        count = 6;
819      }
820      using (var dialog = new DefineArithmeticProgressionDialog(false, min, max, (max - min) / count)) {
821        if (dialog.ShowDialog() == DialogResult.OK) {
822          if (dialog.Values.Any()) {
823            targets = dialog.Values.Select(x => (double)x).ToArray();
824            suppressTargetsEvents = true;
825            targetsTextBox.Text = string.Join("% ; ", targets);
826            suppressTargetsEvents = false;
827
828            UpdateResultsByTarget();
829            SetEnabledStateOfControls();
830          }
831        }
832      }
833    }
834
835    private void addTargetsAsResultButton_Click(object sender, EventArgs e) {
836      var table = (string)dataTableComboBox.SelectedItem;
837
838      var targetsPerProblem = CalculateBestTargetPerProblemInstance(table);
839
840      foreach (var run in Content) {
841        if (!run.Results.ContainsKey(table)) continue;
842        var resultsTable = (IndexedDataTable<double>)run.Results[table];
843        var values = resultsTable.Rows.First().Values;
844        var i = 0;
845        var j = 0;
846        var pd = new ProblemDescription(run);
847        while (i < targets.Length && j < values.Count) {
848          var target = (pd.IsMaximization() ? (1 - targets[i]) : (1 + targets[i])) * targetsPerProblem[pd];
849          var current = values[j];
850          if (pd.IsMaximization() && current.Item2 >= target
851              || !pd.IsMaximization() && current.Item2 <= target) {
852            run.Results[table + ".Target" + target] = new DoubleValue(current.Item1);
853            i++;
854          } else {
855            j++;
856          }
857        }
858      }
859    }
860    #endregion
861
862    #region Event handlers for cost analysis
863    private bool suppressBudgetsEvents;
864    private void budgetsTextBox_Validating(object sender, CancelEventArgs e) {
865      if (suppressBudgetsEvents) return;
866      var budgetStrings = budgetsTextBox.Text.Split(new[] { ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
867      var budgetList = new List<double>();
868      foreach (var ts in budgetStrings) {
869        double b;
870        if (!double.TryParse(ts, out b)) {
871          errorProvider.SetError(budgetsTextBox, "Not all targets can be parsed: " + ts);
872          e.Cancel = true;
873          return;
874        }
875        budgetList.Add(b);
876      }
877      if (budgetList.Count == 0) {
878        errorProvider.SetError(budgetsTextBox, "Give at least one target value!");
879        e.Cancel = true;
880        return;
881      }
882      e.Cancel = false;
883      errorProvider.SetError(budgetsTextBox, null);
884      budgets = budgetList.ToArray();
885      UpdateResultsByCost();
886      SetEnabledStateOfControls();
887    }
888
889    private void eachOrAllBudgetsCheckBox_CheckedChanged(object sender, EventArgs e) {
890      var each = eachOrAllBudgetsCheckBox.Checked;
891      eachOrAllBudgetsCheckBox.Text = each ? "each" : "all";
892      SuspendRepaint();
893      try {
894        UpdateResultsByCost();
895      } finally { ResumeRepaint(true); }
896    }
897
898    private void generateBudgetsButton_Click(object sender, EventArgs e) {
899      decimal max = 1, min = 0, count = 10;
900      if (budgets != null) {
901        max = (decimal)budgets.Max();
902        min = (decimal)budgets.Min();
903        count = budgets.Length;
904      } else if (Content.Count > 0 && dataTableComboBox.SelectedIndex >= 0) {
905        var table = (string)dataTableComboBox.SelectedItem;
906        min = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Min(y => y.Item1)).Min();
907        max = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Max(y => y.Item1)).Max();
908        count = 6;
909      }
910      using (var dialog = new DefineArithmeticProgressionDialog(false, min, max, (max - min) / count)) {
911        if (dialog.ShowDialog() == DialogResult.OK) {
912          if (dialog.Values.Any()) {
913            budgets = dialog.Values.OrderBy(x => x).Select(x => (double)x).ToArray();
914
915            suppressBudgetsEvents = true;
916            budgetsTextBox.Text = string.Join(" ; ", budgets);
917            suppressBudgetsEvents = false;
918
919            UpdateResultsByCost();
920            SetEnabledStateOfControls();
921          }
922        }
923      }
924    }
925
926    private void addBudgetsAsResultButton_Click(object sender, EventArgs e) {
927      var table = (string)dataTableComboBox.SelectedItem;
928      var budgetStrings = budgetsTextBox.Text.Split(new[] { ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
929      if (budgetStrings.Length == 0) {
930        MessageBox.Show("Define a number of budgets.");
931        return;
932      }
933      var budgetList = new List<double>();
934      foreach (var bs in budgetStrings) {
935        double v;
936        if (!double.TryParse(bs, out v)) {
937          MessageBox.Show("Budgets must be a valid number: " + bs);
938          return;
939        }
940        budgetList.Add(v);
941      }
942      budgetList.Sort();
943
944      foreach (var run in Content) {
945        if (!run.Results.ContainsKey(table)) continue;
946        var resultsTable = (IndexedDataTable<double>)run.Results[table];
947        var values = resultsTable.Rows.First().Values;
948        var i = 0;
949        var j = 0;
950        Tuple<double, double> prev = null;
951        while (i < budgetList.Count && j < values.Count) {
952          var current = values[j];
953          if (current.Item1 >= budgetList[i]) {
954            if (prev != null || current.Item1 == budgetList[i]) {
955              var tgt = (prev == null || current.Item1 == budgetList[i]) ? current.Item2 : prev.Item2;
956              run.Results[table + ".Cost" + budgetList[i]] = new DoubleValue(tgt);
957            }
958            i++;
959          } else {
960            j++;
961            prev = current;
962          }
963        }
964      }
965    }
966    #endregion
967
968    #region Helpers
969    private Dictionary<ProblemDescription, double> CalculateBestTargetPerProblemInstance(string table) {
970      return (from r in Content
971              where r.Visible
972              let pd = new ProblemDescription(r)
973              let target = r.Parameters.ContainsKey("BestKnownQuality")
974                           && r.Parameters["BestKnownQuality"] is DoubleValue
975                ? ((DoubleValue)r.Parameters["BestKnownQuality"]).Value
976                : ((IndexedDataTable<double>)r.Results[table]).Rows.First().Values.Last().Item2
977              group target by pd into g
978              select new { Problem = g.Key, Target = g.Key.IsMaximization() ? g.Max() : g.Min() })
979        .ToDictionary(x => x.Problem, x => x.Target);
980    }
981
982    private void UpdateRuns() {
983      if (InvokeRequired) {
984        Invoke((Action)UpdateRuns);
985        return;
986      }
987      SuspendRepaint();
988      try {
989        UpdateResultsByTarget();
990        UpdateResultsByCost();
991      } finally { ResumeRepaint(true); }
992    }
993    #endregion
994
995    private void ConfigureSeries(Series series) {
996      series.SmartLabelStyle.Enabled = true;
997      series.SmartLabelStyle.AllowOutsidePlotArea = LabelOutsidePlotAreaStyle.No;
998      series.SmartLabelStyle.CalloutLineAnchorCapStyle = LineAnchorCapStyle.None;
999      series.SmartLabelStyle.CalloutLineColor = series.Color;
1000      series.SmartLabelStyle.CalloutLineWidth = 2;
1001      series.SmartLabelStyle.CalloutStyle = LabelCalloutStyle.Underlined;
1002      series.SmartLabelStyle.IsOverlappedHidden = false;
1003      series.SmartLabelStyle.MaxMovingDistance = 200;
1004      series.ToolTip = series.LegendText + " X = #VALX, Y = #VALY";
1005    }
1006
1007    private void chart_MouseDown(object sender, MouseEventArgs e) {
1008      HitTestResult result = targetChart.HitTest(e.X, e.Y);
1009      if (result.ChartElementType == ChartElementType.LegendItem) {
1010        ToggleTargetChartSeriesVisible(result.Series);
1011      }
1012    }
1013    private void chart_MouseMove(object sender, MouseEventArgs e) {
1014      HitTestResult result = targetChart.HitTest(e.X, e.Y);
1015      if (result.ChartElementType == ChartElementType.LegendItem)
1016        this.Cursor = Cursors.Hand;
1017      else
1018        this.Cursor = Cursors.Default;
1019    }
1020    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
1021      foreach (LegendItem legendItem in e.LegendItems) {
1022        var series = targetChart.Series[legendItem.SeriesName];
1023        if (series != null) {
1024          bool seriesIsInvisible = invisibleTargetSeries.Any(x => x.Name == series.Name);
1025          foreach (LegendCell cell in legendItem.Cells) {
1026            cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black;
1027          }
1028        }
1029      }
1030    }
1031
1032    private void ToggleTargetChartSeriesVisible(Series series) {
1033      var indexList = invisibleTargetSeries.FindIndex(x => x.Name == series.Name);
1034      var indexChart = targetChart.Series.IndexOf(series);
1035      if (targetChart.Series.Count == 1) targetChart.ChartAreas[0].AxisX.IsLogarithmic = false;
1036      targetChart.Series.RemoveAt(indexChart);
1037      var s = indexList >= 0 ? invisibleTargetSeries[indexList] : new Series(series.Name) {
1038        Color = series.Color,
1039        ChartType = series.ChartType,
1040        BorderWidth = series.BorderWidth,
1041        BorderDashStyle = series.BorderDashStyle
1042      };
1043      if (indexList < 0) {
1044        // hide
1045        invisibleTargetSeries.Add(series);
1046        var shadeSeries = targetChart.Series.FirstOrDefault(x => x.Name == series.Name + "-range");
1047        if (shadeSeries != null) {
1048          if (targetChart.Series.Count == 1) targetChart.ChartAreas[0].AxisX.IsLogarithmic = false;
1049          targetChart.Series.Remove(shadeSeries);
1050          invisibleTargetSeries.Add(shadeSeries);
1051          indexChart--;
1052        }
1053      } else {
1054        // show
1055        invisibleTargetSeries.RemoveAt(indexList);
1056        var shadeSeries = invisibleTargetSeries.FirstOrDefault(x => x.Name == series.Name + "-range");
1057        if (shadeSeries != null) {
1058          invisibleTargetSeries.Remove(shadeSeries);
1059          InsertOrAddSeries(indexChart, shadeSeries);
1060          indexChart++;
1061        }
1062      }
1063      InsertOrAddSeries(indexChart, s);
1064      targetChart.ChartAreas[0].AxisX.IsLogarithmic = CanDisplayLogarithmic();
1065    }
1066
1067    private bool CanDisplayLogarithmic() {
1068      return targetLogScalingCheckBox.Checked
1069        && targetChart.Series.Count > 0 // must have a series
1070        && targetChart.Series.Any(x => x.Points.Count > 0) // at least one series must have points
1071        && targetChart.Series.All(s => s.Points.All(p => p.XValue > 0)); // all points must be positive
1072    }
1073
1074    private void InsertOrAddSeries(int index, Series s) {
1075      if (targetChart.Series.Count <= index)
1076        targetChart.Series.Add(s);
1077      else targetChart.Series.Insert(index, s);
1078    }
1079
1080    private class ProblemDescription {
1081      private readonly bool matchAll;
1082      public static readonly ProblemDescription MatchAll = new ProblemDescription() {
1083        ProblemName = "All with Best-Known"
1084      };
1085
1086      private ProblemDescription() {
1087        ProblemType = string.Empty;
1088        ProblemName = string.Empty;
1089        Evaluator = string.Empty;
1090        Maximization = string.Empty;
1091        DisplayProblemType = false;
1092        DisplayProblemName = false;
1093        DisplayEvaluator = false;
1094        DisplayMaximization = false;
1095        matchAll = true;
1096      }
1097
1098      public ProblemDescription(IRun run) {
1099        ProblemType = GetStringValueOrEmpty(run, "Problem Type");
1100        ProblemName = GetStringValueOrEmpty(run, "Problem Name");
1101        Evaluator = GetStringValueOrEmpty(run, "Evaluator");
1102        Maximization = GetMaximizationValueOrEmpty(run, "Maximization");
1103        DisplayProblemType = !string.IsNullOrEmpty(ProblemType);
1104        DisplayProblemName = !string.IsNullOrEmpty(ProblemName);
1105        DisplayEvaluator = !string.IsNullOrEmpty(Evaluator);
1106        DisplayMaximization = !string.IsNullOrEmpty(Maximization);
1107        matchAll = false;
1108      }
1109
1110      public bool DisplayProblemType { get; set; }
1111      public string ProblemType { get; set; }
1112      public bool DisplayProblemName { get; set; }
1113      public string ProblemName { get; set; }
1114      public bool DisplayEvaluator { get; set; }
1115      public string Evaluator { get; set; }
1116      public bool DisplayMaximization { get; set; }
1117      public string Maximization { get; set; }
1118
1119      public bool IsMaximization() {
1120        return Maximization == "MAX";
1121      }
1122
1123      public bool Match(IRun run) {
1124        return matchAll ||
1125               GetStringValueOrEmpty(run, "Problem Type") == ProblemType
1126               && GetStringValueOrEmpty(run, "Problem Name") == ProblemName
1127               && GetStringValueOrEmpty(run, "Evaluator") == Evaluator
1128               && GetMaximizationValueOrEmpty(run, "Maximization") == Maximization;
1129      }
1130
1131      private string GetStringValueOrEmpty(IRun run, string key) {
1132        return run.Parameters.ContainsKey(key) ? ((StringValue)run.Parameters[key]).Value : string.Empty;
1133      }
1134
1135      private string GetMaximizationValueOrEmpty(IRun run, string key) {
1136        return run.Parameters.ContainsKey(key) ? (((BoolValue)run.Parameters[key]).Value ? "MAX" : "MIN") : string.Empty;
1137      }
1138
1139      public override bool Equals(object obj) {
1140        var other = obj as ProblemDescription;
1141        if (other == null) return false;
1142        return ProblemType == other.ProblemType
1143               && ProblemName == other.ProblemName
1144               && Evaluator == other.Evaluator
1145               && Maximization == other.Maximization;
1146      }
1147
1148      public override int GetHashCode() {
1149        return ProblemType.GetHashCode() ^ ProblemName.GetHashCode() ^ Evaluator.GetHashCode() ^ Maximization.GetHashCode();
1150      }
1151
1152      public override string ToString() {
1153        return string.Join("  --  ", new[] {
1154          (DisplayProblemType ? ProblemType : string.Empty),
1155          (DisplayProblemName ? ProblemName : string.Empty),
1156          (DisplayEvaluator ? Evaluator : string.Empty),
1157          (DisplayMaximization ? Maximization : string.Empty)}.Where(x => !string.IsNullOrEmpty(x)));
1158      }
1159    }
1160  }
1161}
Note: See TracBrowser for help on using the repository browser.