source: trunk/sources/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.cs @ 14652

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

#2634:

  • changed relative/absolute checkbox button into combobox
  • added optional markers
  • added translucency of line in case run-data is more scarce (e.g. because they're already terminated)
File size: 60.3 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 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;
33using HeuristicLab.Core.Views;
34using HeuristicLab.Data;
35using HeuristicLab.MainForm;
36using HeuristicLab.MainForm.WindowsForms;
37
38namespace HeuristicLab.Optimization.Views {
39  [View("Run-length Distribution View")]
40  [Content(typeof(RunCollection), false)]
41  public partial class RunCollectionRLDView : ItemView {
42    private List<Series> invisibleTargetSeries;
43
44    private const string AllRuns = "All Runs";
45
46    private static readonly Color[] colors = new[] {
47      Color.FromArgb(0x40, 0x6A, 0xB7),
48      Color.FromArgb(0xB1, 0x6D, 0x01),
49      Color.FromArgb(0x4E, 0x8A, 0x06),
50      Color.FromArgb(0x75, 0x50, 0x7B),
51      Color.FromArgb(0x72, 0x9F, 0xCF),
52      Color.FromArgb(0xA4, 0x00, 0x00),
53      Color.FromArgb(0xAD, 0x7F, 0xA8),
54      Color.FromArgb(0x29, 0x50, 0xCF),
55      Color.FromArgb(0x90, 0xB0, 0x60),
56      Color.FromArgb(0xF5, 0x89, 0x30),
57      Color.FromArgb(0x55, 0x57, 0x53),
58      Color.FromArgb(0xEF, 0x59, 0x59),
59      Color.FromArgb(0xED, 0xD4, 0x30),
60      Color.FromArgb(0x63, 0xC2, 0x16),
61    };
62    private static readonly ChartDashStyle[] lineStyles = new[] {
63      ChartDashStyle.Solid,
64      ChartDashStyle.Dash,
65      ChartDashStyle.DashDot,
66      ChartDashStyle.Dot
67    };
68    private static readonly DataRowVisualProperties.DataRowLineStyle[] hlLineStyles = new[] {
69      DataRowVisualProperties.DataRowLineStyle.Solid,
70      DataRowVisualProperties.DataRowLineStyle.Dash,
71      DataRowVisualProperties.DataRowLineStyle.DashDot,
72      DataRowVisualProperties.DataRowLineStyle.Dot
73    };
74
75    public new RunCollection Content {
76      get { return (RunCollection)base.Content; }
77      set { base.Content = value; }
78    }
79
80    private double[] targets;
81    private double[] budgets;
82    private bool targetsAreRelative = true;
83    private readonly BindingList<ProblemInstance> problems;
84
85    private bool suppressUpdates;
86    private readonly IndexedDataTable<double> byCostDataTable;
87    public IndexedDataTable<double> ByCostDataTable {
88      get { return byCostDataTable; }
89    }
90
91    public RunCollectionRLDView() {
92      InitializeComponent();
93      invisibleTargetSeries = new List<Series>();
94
95      targetChart.CustomizeAllChartAreas();
96      targetChart.ChartAreas[0].CursorX.Interval = 1;
97      targetChart.SuppressExceptions = true;
98      byCostDataTable = new IndexedDataTable<double>("ECDF by Cost", "A data table containing the ECDF of each of a number of groups.") {
99        VisualProperties = {
100          YAxisTitle = "Proportion of unused budgets",
101          YAxisMinimumFixedValue = 0,
102          YAxisMinimumAuto = false,
103          YAxisMaximumFixedValue = 1,
104          YAxisMaximumAuto = false
105        }
106      };
107      byCostViewHost.Content = byCostDataTable;
108      suppressUpdates = true;
109      relativeOrAbsoluteComboBox.SelectedItem = targetsAreRelative ? "relative" : "absolute";
110      problems = new BindingList<ProblemInstance>();
111      problemComboBox.DataSource = new BindingSource() { DataSource = problems };
112      problemComboBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
113      suppressUpdates = false;
114    }
115
116    #region Content events
117    protected override void RegisterContentEvents() {
118      base.RegisterContentEvents();
119      Content.ItemsAdded += Content_ItemsAdded;
120      Content.ItemsRemoved += Content_ItemsRemoved;
121      Content.CollectionReset += Content_CollectionReset;
122      Content.UpdateOfRunsInProgressChanged += Content_UpdateOfRunsInProgressChanged;
123      Content.OptimizerNameChanged += Content_AlgorithmNameChanged;
124    }
125    protected override void DeregisterContentEvents() {
126      Content.ItemsAdded -= Content_ItemsAdded;
127      Content.ItemsRemoved -= Content_ItemsRemoved;
128      Content.CollectionReset -= Content_CollectionReset;
129      Content.UpdateOfRunsInProgressChanged -= Content_UpdateOfRunsInProgressChanged;
130      Content.OptimizerNameChanged -= Content_AlgorithmNameChanged;
131      base.DeregisterContentEvents();
132    }
133
134    private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IRun> e) {
135      if (suppressUpdates) return;
136      if (InvokeRequired) {
137        Invoke(new CollectionItemsChangedEventHandler<IRun>(Content_ItemsAdded), sender, e);
138        return;
139      }
140      UpdateGroupAndProblemComboBox();
141      UpdateDataTableComboBox();
142      foreach (var run in e.Items)
143        RegisterRunEvents(run);
144    }
145    private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IRun> e) {
146      if (suppressUpdates) return;
147      if (InvokeRequired) {
148        Invoke(new CollectionItemsChangedEventHandler<IRun>(Content_ItemsRemoved), sender, e);
149        return;
150      }
151      UpdateGroupAndProblemComboBox();
152      UpdateDataTableComboBox();
153      foreach (var run in e.Items)
154        DeregisterRunEvents(run);
155    }
156    private void Content_CollectionReset(object sender, CollectionItemsChangedEventArgs<IRun> e) {
157      if (suppressUpdates) return;
158      if (InvokeRequired) {
159        Invoke(new CollectionItemsChangedEventHandler<IRun>(Content_CollectionReset), sender, e);
160        return;
161      }
162      UpdateGroupAndProblemComboBox();
163      UpdateDataTableComboBox();
164      foreach (var run in e.OldItems)
165        DeregisterRunEvents(run);
166    }
167    private void Content_AlgorithmNameChanged(object sender, EventArgs e) {
168      if (InvokeRequired)
169        Invoke(new EventHandler(Content_AlgorithmNameChanged), sender, e);
170      else UpdateCaption();
171    }
172    private void Content_UpdateOfRunsInProgressChanged(object sender, EventArgs e) {
173      if (InvokeRequired) {
174        Invoke(new EventHandler(Content_UpdateOfRunsInProgressChanged), sender, e);
175        return;
176      }
177      suppressUpdates = Content.UpdateOfRunsInProgress;
178      if (!suppressUpdates) {
179        UpdateDataTableComboBox();
180        UpdateGroupAndProblemComboBox();
181        UpdateRuns();
182      }
183    }
184
185    private void RegisterRunEvents(IRun run) {
186      run.PropertyChanged += run_PropertyChanged;
187    }
188    private void DeregisterRunEvents(IRun run) {
189      run.PropertyChanged -= run_PropertyChanged;
190    }
191    private void run_PropertyChanged(object sender, PropertyChangedEventArgs e) {
192      if (suppressUpdates) return;
193      if (InvokeRequired) {
194        Invoke((Action<object, PropertyChangedEventArgs>)run_PropertyChanged, sender, e);
195      } else {
196        if (e.PropertyName == "Visible")
197          UpdateRuns();
198      }
199    }
200    #endregion
201
202    protected override void OnContentChanged() {
203      base.OnContentChanged();
204      dataTableComboBox.Items.Clear();
205      groupComboBox.Items.Clear();
206      targetChart.ChartAreas[0].AxisX.IsLogarithmic = false;
207      targetChart.Series.Clear();
208      invisibleTargetSeries.Clear();
209      byCostDataTable.VisualProperties.XAxisLogScale = false;
210      byCostDataTable.Rows.Clear();
211
212      UpdateCaption();
213      if (Content != null) {
214        UpdateGroupAndProblemComboBox();
215        UpdateDataTableComboBox();
216      }
217    }
218
219
220    private void UpdateGroupAndProblemComboBox() {
221      var selectedGroupItem = (string)groupComboBox.SelectedItem;
222
223      var groupings = Content.ParameterNames.OrderBy(x => x).ToArray();
224      groupComboBox.Items.Clear();
225      groupComboBox.Items.Add(AllRuns);
226      groupComboBox.Items.AddRange(groupings);
227      if (selectedGroupItem != null && groupComboBox.Items.Contains(selectedGroupItem)) {
228        groupComboBox.SelectedItem = selectedGroupItem;
229      } else if (groupComboBox.Items.Count > 0) {
230        groupComboBox.SelectedItem = groupComboBox.Items[0];
231      }
232
233      var table = (string)dataTableComboBox.SelectedItem;
234      var problemDict = CalculateBestTargetPerProblemInstance(table);
235
236      var problemTypesDifferent = problemDict.Keys.Select(x => x.ProblemType).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
237      var problemNamesDifferent = problemDict.Keys.Select(x => x.ProblemName).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
238      var evaluatorDifferent = problemDict.Keys.Select(x => x.Evaluator).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
239      var maximizationDifferent = problemDict.Keys.Select(x => x.Maximization).Distinct().Count() > 1;
240      var allEqual = !problemTypesDifferent && !problemNamesDifferent && !evaluatorDifferent && !maximizationDifferent;
241
242      var selectedProblemItem = (ProblemInstance)problemComboBox.SelectedItem;
243      problems.Clear();
244      problems.Add(ProblemInstance.MatchAll);
245      if (problems[0].Equals(selectedProblemItem)) problemComboBox.SelectedItem = problems[0];
246      foreach (var p in problemDict.ToList()) {
247        p.Key.BestKnownQuality = p.Value;
248        p.Key.DisplayProblemType = problemTypesDifferent;
249        p.Key.DisplayProblemName = problemNamesDifferent || allEqual;
250        p.Key.DisplayEvaluator = evaluatorDifferent;
251        p.Key.DisplayMaximization = maximizationDifferent;
252        problems.Add(p.Key);
253        if (p.Key.Equals(selectedProblemItem)) problemComboBox.SelectedItem = p.Key;
254      }
255      SetEnabledStateOfControls();
256    }
257
258    private void UpdateDataTableComboBox() {
259      string selectedItem = (string)dataTableComboBox.SelectedItem;
260
261      dataTableComboBox.Items.Clear();
262      var dataTables = (from run in Content
263                        from result in run.Results
264                        where result.Value is IndexedDataTable<double>
265                        select result.Key).Distinct().ToArray();
266
267      dataTableComboBox.Items.AddRange(dataTables);
268      if (selectedItem != null && dataTableComboBox.Items.Contains(selectedItem)) {
269        dataTableComboBox.SelectedItem = selectedItem;
270      } else if (dataTableComboBox.Items.Count > 0) {
271        dataTableComboBox.SelectedItem = dataTableComboBox.Items[0];
272      }
273    }
274
275    protected override void SetEnabledStateOfControls() {
276      base.SetEnabledStateOfControls();
277      groupComboBox.Enabled = Content != null;
278      problemComboBox.Enabled = Content != null && problemComboBox.Items.Count > 1;
279      dataTableComboBox.Enabled = Content != null && dataTableComboBox.Items.Count > 1;
280      addTargetsAsResultButton.Enabled = Content != null && targets != null && dataTableComboBox.SelectedIndex >= 0;
281      addBudgetsAsResultButton.Enabled = Content != null && budgets != null && dataTableComboBox.SelectedIndex >= 0;
282    }
283
284    private Dictionary<string, Dictionary<ProblemInstance, List<IRun>>> GroupRuns() {
285      var groupedRuns = new Dictionary<string, Dictionary<ProblemInstance, List<IRun>>>();
286
287      var table = (string)dataTableComboBox.SelectedItem;
288      if (string.IsNullOrEmpty(table)) return groupedRuns;
289
290      var selectedGroup = (string)groupComboBox.SelectedItem;
291      if (string.IsNullOrEmpty(selectedGroup)) return groupedRuns;
292
293      var selectedProblem = (ProblemInstance)problemComboBox.SelectedItem;
294      if (selectedProblem == null) return groupedRuns;
295     
296      foreach (var x in (from r in Content
297                         where (selectedGroup == AllRuns || r.Parameters.ContainsKey(selectedGroup))
298                           && selectedProblem.Match(r)
299                           && r.Results.ContainsKey(table)
300                           && r.Visible
301                         group r by selectedGroup == AllRuns ? AllRuns : r.Parameters[selectedGroup].ToString() into g
302                         select Tuple.Create(g.Key, g.ToList()))) {
303        var pDict = problems.ToDictionary(r => r, _ => new List<IRun>());
304        foreach (var y in x.Item2) {
305          var pd = new ProblemInstance(y);
306          List<IRun> l;
307          if (pDict.TryGetValue(pd, out l)) l.Add(y);
308        }
309        groupedRuns[x.Item1] = pDict.Where(a => a.Value.Count > 0).ToDictionary(a => a.Key, a => a.Value);
310      }
311
312      return groupedRuns;
313    }
314
315    #region Performance analysis by (multiple) target(s)
316    private void UpdateResultsByTarget() {
317      // necessary to reset log scale -> empty chart cannot use log scaling
318      targetChart.ChartAreas[0].AxisX.IsLogarithmic = false;
319      targetChart.Series.Clear();
320      invisibleTargetSeries.Clear();
321
322      var table = (string)dataTableComboBox.SelectedItem;
323      if (string.IsNullOrEmpty(table)) return;
324
325      if (targets == null) GenerateDefaultTargets();
326
327      var groupedRuns = GroupRuns();
328      if (groupedRuns.Count == 0) return;
329
330      var xAxisTitles = new HashSet<string>();
331      var colorCount = 0;
332      var lineStyleCount = 0;
333
334      // if the group contains multiple different problem instances we want to use the
335      // minimal maximal observed effort otherwise we run into situations where we don't
336      // have data for a certain problem instance anymore this is a special case when
337      // aggregating over multiple problem instances     
338      var maxEfforts = new Dictionary<ProblemInstance, double>();
339      double minEff = double.MaxValue, maxEff = double.MinValue;
340      foreach (var group in groupedRuns) {
341        foreach (var problem in group.Value) {
342          double problemSpecificMaxEff;
343          if (!maxEfforts.TryGetValue(problem.Key, out problemSpecificMaxEff)) {
344            problemSpecificMaxEff = 0;
345          }
346          var max = problem.Key.IsMaximization();
347          var absTargets = GetAbsoluteTargetsWorstToBest(max, problem.Key.BestKnownQuality);
348          var worstTarget = absTargets.First();
349          var bestTarget = absTargets.Last();
350          foreach (var run in problem.Value) {
351            var row = ((IndexedDataTable<double>)run.Results[table]).Rows.First().Values;
352            var a = row.FirstOrDefault(x => max ? x.Item2 >= worstTarget : x.Item2 <= worstTarget);
353            var b = row.FirstOrDefault(x => max ? x.Item2 >= bestTarget : x.Item2 <= bestTarget);
354            var firstEff = (a == default(Tuple<double, double>)) ? row.Last().Item1 : a.Item1;
355            var lastEff = (b == default(Tuple<double, double>)) ? row.Last().Item1 : b.Item1;
356            if (minEff > firstEff) minEff = firstEff;
357            if (maxEff < lastEff) maxEff = lastEff;
358            if (problemSpecificMaxEff < lastEff) problemSpecificMaxEff = lastEff;
359          }
360          maxEfforts[problem.Key] = problemSpecificMaxEff;
361        }
362      }
363      maxEff = Math.Min(maxEff, maxEfforts.Values.Min());
364
365      var minZeros = (int)Math.Floor(Math.Log10(minEff));
366      var maxZeros = (int)Math.Floor(Math.Log10(maxEff));
367      var axisMin = (decimal)Math.Pow(10, minZeros);
368      var axisMax = (decimal)Math.Pow(10, maxZeros);
369      if (!targetLogScalingCheckBox.Checked) {
370        var minAdd = (decimal)Math.Pow(10, minZeros - 1) * 2;
371        var maxAdd = (decimal)Math.Pow(10, maxZeros - 1) * 2;
372        while (axisMin + minAdd < (decimal)minEff) axisMin += minAdd;
373        while (axisMax <= (decimal)maxEff) axisMax += maxAdd;
374      } else axisMax = (decimal)Math.Pow(10, (int)Math.Ceiling(Math.Log10(maxEff)));
375      targetChart.ChartAreas[0].AxisX.Minimum = (double)axisMin;
376      targetChart.ChartAreas[0].AxisX.Maximum = (double)axisMax;
377
378      foreach (var group in groupedRuns) {
379        // hits describes the number of target hits at a certain time for a certain group
380        var hits = new Dictionary<string, SortedList<double, int>>();
381        // misses describes the number of target misses after a certain time for a certain group
382        // for instance when a run ends, but has not achieved all targets, misses describes
383        // how many targets have been left open at the point when the run ended
384        var misses = new Dictionary<string, SortedList<double, int>>();
385        var maxLength = 0.0;
386
387        var noRuns = 0;
388        foreach (var problem in group.Value) {
389          foreach (var run in problem.Value) {
390            var resultsTable = (IndexedDataTable<double>)run.Results[table];
391            xAxisTitles.Add(resultsTable.VisualProperties.XAxisTitle);
392
393            if (aggregateTargetsCheckBox.Checked) {
394              var length = CalculateHitsForAllTargets(hits, misses, resultsTable.Rows.First(), problem.Key, group.Key, problem.Key.BestKnownQuality, maxEff);
395              maxLength = Math.Max(length, maxLength);
396            } else {
397              CalculateHitsForEachTarget(hits, misses, resultsTable.Rows.First(), problem.Key, group.Key, problem.Key.BestKnownQuality, maxEff);
398            }
399            noRuns++;
400          }
401        }
402        var showMarkers = markerCheckBox.Checked;
403        foreach (var list in hits) {
404          var row = new Series(list.Key) {
405            ChartType = SeriesChartType.StepLine,
406            BorderWidth = 3,
407            Color = colors[colorCount],
408            BorderDashStyle = lineStyles[lineStyleCount],
409          };
410          var rowShade = new Series(list.Key + "-range") {
411            IsVisibleInLegend = false,
412            ChartType = SeriesChartType.Range,
413            Color = Color.FromArgb(32, colors[colorCount]),
414            YValuesPerPoint = 2
415          };
416
417          var ecdf = 0.0;
418          var missedecdf = 0.0;
419          var iter = misses[list.Key].GetEnumerator();
420          var moreMisses = iter.MoveNext();
421          var totalTargets = noRuns;
422          if (aggregateTargetsCheckBox.Checked) totalTargets *= targets.Length;
423          var movingTargets = totalTargets;
424          var labelPrinted = false;
425          foreach (var h in list.Value) {
426            var prevmissedecdf = missedecdf;
427            while (moreMisses && iter.Current.Key <= h.Key) {
428              if (!labelPrinted && row.Points.Count > 0) {
429                var point = row.Points.Last();
430                point.Label = row.Name;
431                point.MarkerStyle = MarkerStyle.Cross;
432                point.MarkerBorderWidth = 1;
433                point.MarkerSize = 10;
434                labelPrinted = true;
435                rowShade.Points.Add(new DataPoint(point.XValue, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
436              }
437              missedecdf += iter.Current.Value;
438              movingTargets -= iter.Current.Value;
439              if (row.Points.Count > 0 && row.Points.Last().XValue == iter.Current.Key) {
440                row.Points.Last().SetValueY(ecdf / movingTargets);
441                row.Points.Last().Color = Color.FromArgb(255 - (int)Math.Floor(255 * (prevmissedecdf / totalTargets)), colors[colorCount]);
442              } else {
443                var dp = new DataPoint(iter.Current.Key, ecdf / movingTargets) {
444                  Color = Color.FromArgb(255 - (int)Math.Floor(255 * (prevmissedecdf / totalTargets)), colors[colorCount])
445                };
446                if (showMarkers) {
447                  dp.MarkerStyle = MarkerStyle.Circle;
448                  dp.MarkerBorderWidth = 1;
449                  dp.MarkerSize = 5;
450                }
451                row.Points.Add(dp);
452                prevmissedecdf = missedecdf;
453              }
454              if (boundShadingCheckBox.Checked) {
455                if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == iter.Current.Key)
456                  rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
457                else rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
458              }
459              moreMisses = iter.MoveNext();
460              if (!labelPrinted) {
461                var point = row.Points.Last();
462                point.Label = row.Name;
463                point.MarkerStyle = MarkerStyle.Cross;
464                point.MarkerBorderWidth = 1;
465                point.MarkerSize = 10;
466                labelPrinted = true;
467              }
468            }
469            ecdf += h.Value;
470            if (row.Points.Count > 0 && row.Points.Last().XValue == h.Key) {
471              row.Points.Last().SetValueY(ecdf / movingTargets);
472              row.Points.Last().Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount]);
473            } else {
474              var dp = new DataPoint(h.Key, ecdf / movingTargets) {
475                Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount])
476              };
477              if (showMarkers) {
478                dp.MarkerStyle = MarkerStyle.Circle;
479                dp.MarkerBorderWidth = 1;
480                dp.MarkerSize = 5;
481              }
482              row.Points.Add(dp);
483            }
484            if (missedecdf > 0 && boundShadingCheckBox.Checked) {
485              if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == h.Key)
486                rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
487              else rowShade.Points.Add(new DataPoint(h.Key, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
488            }
489          }
490
491          if (!labelPrinted && row.Points.Count > 0) {
492            var point = row.Points.Last();
493            point.Label = row.Name;
494            point.MarkerStyle = MarkerStyle.Cross;
495            point.MarkerBorderWidth = 1;
496            point.MarkerSize = 10;
497            rowShade.Points.Add(new DataPoint(point.XValue, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
498          }
499
500          while (moreMisses) {
501            // if there are misses beyond the last hit we extend the shaded area
502            missedecdf += iter.Current.Value;
503            //movingTargets -= iter.Current.Value;
504            if (row.Points.Count > 0 && row.Points.Last().XValue == iter.Current.Key) {
505              row.Points.Last().SetValueY(ecdf / movingTargets);
506              row.Points.Last().Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount]);
507            } else {
508              var dp = new DataPoint(iter.Current.Key, ecdf / movingTargets) {
509                Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount])
510              };
511              if (showMarkers) {
512                dp.MarkerStyle = MarkerStyle.Circle;
513                dp.MarkerBorderWidth = 1;
514                dp.MarkerSize = 5;
515              }
516              row.Points.Add(dp);
517            }
518            if (boundShadingCheckBox.Checked) {
519              if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == iter.Current.Key)
520                rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
521              else rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
522            }
523            moreMisses = iter.MoveNext();
524          }
525
526          if (maxLength > 0 && (row.Points.Count == 0 || row.Points.Last().XValue < maxLength)) {
527            var dp = new DataPoint(maxLength, ecdf / movingTargets) {
528              Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount])
529            };
530            if (showMarkers) {
531              dp.MarkerStyle = MarkerStyle.Circle;
532              dp.MarkerBorderWidth = 1;
533              dp.MarkerSize = 5;
534            }
535            row.Points.Add(dp);
536          }
537
538          ConfigureSeries(row);
539          targetChart.Series.Add(rowShade);
540          targetChart.Series.Add(row);
541        }
542        colorCount = (colorCount + 1) % colors.Length;
543        if (colorCount == 0) lineStyleCount = (lineStyleCount + 1) % lineStyles.Length;
544      }
545
546      if (targets.Length == 1) {
547        if (targetsAreRelative)
548          targetChart.ChartAreas[0].AxisY.Title = "Probability to be " + (targets[0] * 100) + "% worse than best";
549        else targetChart.ChartAreas[0].AxisY.Title = "Probability to reach at least a fitness of " + targets[0];
550      } else targetChart.ChartAreas[0].AxisY.Title = "Proportion of reached targets";
551      targetChart.ChartAreas[0].AxisX.Title = string.Join(" / ", xAxisTitles);
552      targetChart.ChartAreas[0].AxisX.IsLogarithmic = CanDisplayLogarithmic();
553      targetChart.ChartAreas[0].CursorY.Interval = 0.05;
554      UpdateErtTables(groupedRuns);
555    }
556   
557    private IEnumerable<double> GetAbsoluteTargets(bool maximization, double bestKnown) {
558      if (!targetsAreRelative) return targets;
559      IEnumerable<double> tmp = null;
560      if (bestKnown > 0) {
561        tmp = targets.Select(x => (maximization ? (1 - x) : (1 + x)) * bestKnown);
562      } else if (bestKnown < 0) {
563        tmp = targets.Select(x => (!maximization ? (1 - x) : (1 + x)) * bestKnown);
564      } else {
565        // relative to 0 is impossible
566        tmp = targets;
567      }
568      return tmp;
569    }
570
571    private double[] GetAbsoluteTargetsWorstToBest(bool maximization, double bestKnown) {
572      var absTargets = GetAbsoluteTargets(maximization, bestKnown);
573      return maximization ? absTargets.OrderBy(x => x).ToArray() : absTargets.OrderByDescending(x => x).ToArray();
574    }
575
576    private void GenerateDefaultTargets() {
577      targets = new[] { 0.1, 0.05, 0.02, 0.01, 0 };
578      suppressTargetsEvents = true;
579      targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%";
580      suppressTargetsEvents = false;
581    }
582
583    private void CalculateHitsForEachTarget(Dictionary<string, SortedList<double, int>> hits,
584                                            Dictionary<string, SortedList<double, int>> misses,
585                                            IndexedDataRow<double> row, ProblemInstance problem,
586                                            string group, double bestTarget, double maxEffort) {
587      var maximization = problem.IsMaximization();
588      foreach (var t in targets.Zip(GetAbsoluteTargets(maximization, bestTarget), (rt, at) => Tuple.Create(at, rt))) {
589        var l = t.Item1;
590        var key = group + "_" + (targetsAreRelative ? (t.Item2 * 100) + "%_" + l : l.ToString());
591        if (!hits.ContainsKey(key)) {
592          hits.Add(key, new SortedList<double, int>());
593          misses.Add(key, new SortedList<double, int>());
594        }
595        var hit = false;
596        foreach (var v in row.Values) {
597          if (v.Item1 > maxEffort) break;
598          if (maximization && v.Item2 >= l || !maximization && v.Item2 <= l) {
599            if (hits[key].ContainsKey(v.Item1))
600              hits[key][v.Item1]++;
601            else hits[key][v.Item1] = 1;
602            hit = true;
603            break;
604          }
605        }
606        if (!hit) {
607          var max = Math.Min(row.Values.Last().Item1, maxEffort);
608          if (misses[key].ContainsKey(max))
609            misses[key][max]++;
610          else misses[key][max] = 1;
611        }
612      }
613    }
614
615    private double CalculateHitsForAllTargets(Dictionary<string, SortedList<double, int>> hits,
616                                              Dictionary<string, SortedList<double, int>> misses,
617                                              IndexedDataRow<double> row, ProblemInstance problem,
618                                              string group, double bestTarget, double maxEffort) {
619      var values = row.Values;
620      if (!hits.ContainsKey(group)) {
621        hits.Add(group, new SortedList<double, int>());
622        misses.Add(group, new SortedList<double, int>());
623      }
624
625      var i = 0;
626      var j = 0;
627      var max = problem.IsMaximization();
628      var absTargets = GetAbsoluteTargetsWorstToBest(max, bestTarget);
629      while (i < absTargets.Length && j < values.Count) {
630        var target = absTargets[i];
631        var current = values[j];
632        if (current.Item1 > maxEffort) break;
633        if (problem.IsMaximization() && current.Item2 >= target
634          || !problem.IsMaximization() && current.Item2 <= target) {
635          if (hits[group].ContainsKey(current.Item1)) hits[group][current.Item1]++;
636          else hits[group][current.Item1] = 1;
637          i++;
638        } else {
639          j++;
640        }
641      }
642      if (j == values.Count) j--;
643      var effort = Math.Min(values[j].Item1, maxEffort);
644      if (i < absTargets.Length) {
645        if (misses[group].ContainsKey(effort))
646          misses[group][effort] += absTargets.Length - i;
647        else misses[group][effort] = absTargets.Length - i;
648      }
649      return effort;
650    }
651
652    private void UpdateErtTables(Dictionary<string, Dictionary<ProblemInstance, List<IRun>>> groupedRuns) {
653      ertTableView.Content = null;
654      var columns = 1 + targets.Length + 1;
655      var matrix = new string[groupedRuns.Count * groupedRuns.Max(x => x.Value.Count) + groupedRuns.Max(x => x.Value.Count), columns];
656      var rowCount = 0;
657
658      var tableName = (string)dataTableComboBox.SelectedItem;
659      if (string.IsNullOrEmpty(tableName)) return;
660     
661      var problems = groupedRuns.SelectMany(x => x.Value.Keys).Distinct().ToList();
662
663      foreach (var problem in problems) {
664        var max = problem.IsMaximization();
665        matrix[rowCount, 0] = problem.ToString();
666        var absTargets = GetAbsoluteTargetsWorstToBest(max, problem.BestKnownQuality);
667        for (var i = 0; i < absTargets.Length; i++) {
668          matrix[rowCount, i + 1] = absTargets[i].ToString(CultureInfo.CurrentCulture.NumberFormat);
669        }
670        matrix[rowCount, columns - 1] = "#succ";
671        rowCount++;
672
673        foreach (var group in groupedRuns) {
674          matrix[rowCount, 0] = group.Key;
675          if (!group.Value.ContainsKey(problem)) {
676            matrix[rowCount, columns - 1] = "N/A";
677            rowCount++;
678            continue;
679          }
680          var runs = group.Value[problem];
681          var result = default(ErtCalculationResult);
682          for (var i = 0; i < absTargets.Length; i++) {
683            result = ExpectedRuntimeHelper.CalculateErt(runs, tableName, absTargets[i], max);
684            matrix[rowCount, i + 1] = result.ToString();
685          }
686          matrix[rowCount, columns - 1] = targets.Length > 0 ? result.SuccessfulRuns + "/" + result.TotalRuns : "-";
687          rowCount++;
688        }
689      }
690      ertTableView.Content = new StringMatrix(matrix);
691      ertTableView.DataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
692    }
693    #endregion
694
695    #region Performance analysis by (multiple) budget(s)
696    private void UpdateResultsByCost() {
697      // necessary to reset log scale -> empty chart cannot use log scaling
698      byCostDataTable.VisualProperties.XAxisLogScale = false;
699      byCostDataTable.Rows.Clear();
700
701      var table = (string)dataTableComboBox.SelectedItem;
702      if (string.IsNullOrEmpty(table)) return;
703
704      if (budgets == null) GenerateDefaultBudgets(table);
705
706      var groupedRuns = GroupRuns();
707      if (groupedRuns.Count == 0) return;
708
709      var colorCount = 0;
710      var lineStyleCount = 0;
711     
712      foreach (var group in groupedRuns) {
713        var hits = new Dictionary<string, SortedList<double, double>>();
714
715        foreach (var problem in group.Value) {
716          foreach (var run in problem.Value) {
717            var resultsTable = (IndexedDataTable<double>)run.Results[table];
718
719            if (aggregateBudgetsCheckBox.Checked) {
720              CalculateHitsForAllBudgets(hits, resultsTable.Rows.First(), group.Value.Count, problem.Key, group.Key, problem.Value.Count);
721            } else {
722              CalculateHitsForEachBudget(hits, resultsTable.Rows.First(), group.Value.Count, problem.Key, group.Key, problem.Value.Count);
723            }
724          }
725        }
726
727        foreach (var list in hits) {
728          var row = new IndexedDataRow<double>(list.Key) {
729            VisualProperties = {
730              ChartType = DataRowVisualProperties.DataRowChartType.StepLine,
731              LineWidth = 2,
732              Color = colors[colorCount],
733              LineStyle = hlLineStyles[lineStyleCount],
734              StartIndexZero = false
735            }
736          };
737
738          var total = 0.0;
739          foreach (var h in list.Value) {
740            total += h.Value;
741            row.Values.Add(Tuple.Create(h.Key, total));
742          }
743
744          byCostDataTable.Rows.Add(row);
745        }
746        colorCount = (colorCount + 1) % colors.Length;
747        if (colorCount == 0) lineStyleCount = (lineStyleCount + 1) % lineStyles.Length;
748      }
749
750      byCostDataTable.VisualProperties.XAxisTitle = "Targets to Best-Known Ratio";
751      byCostDataTable.VisualProperties.XAxisLogScale = byCostDataTable.Rows.Count > 0 && budgetLogScalingCheckBox.Checked;
752    }
753
754    private void GenerateDefaultBudgets(string table) {
755      var runs = GroupRuns().SelectMany(x => x.Value.Values).SelectMany(x => x).ToList();
756      var min = runs.Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Select(y => y.Item1).Min()).Min();
757      var max = runs.Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Select(y => y.Item1).Max()).Max();
758
759      var maxMagnitude = (int)Math.Ceiling(Math.Log10(max));
760      var minMagnitude = (int)Math.Floor(Math.Log10(min));
761      if (maxMagnitude - minMagnitude >= 3) {
762        budgets = new double[maxMagnitude - minMagnitude];
763        for (var i = minMagnitude; i < maxMagnitude; i++) {
764          budgets[i - minMagnitude] = Math.Pow(10, i);
765        }
766      } else {
767        var range = max - min;
768        budgets = Enumerable.Range(0, 6).Select(x => min + (x / 5.0) * range).ToArray();
769      }
770      suppressBudgetsEvents = true;
771      budgetsTextBox.Text = string.Join(" ; ", budgets);
772      suppressBudgetsEvents = false;
773    }
774
775    private void CalculateHitsForEachBudget(Dictionary<string, SortedList<double, double>> hits, IndexedDataRow<double> row, int groupCount, ProblemInstance problem, string groupName, int problemCount) {
776      var max = problem.IsMaximization();
777      foreach (var b in budgets) {
778        var key = groupName + "-" + b;
779        if (!hits.ContainsKey(key)) hits.Add(key, new SortedList<double, double>());
780        Tuple<double, double> prev = null;
781        foreach (var v in row.Values) {
782          if (v.Item1 >= b) {
783            // the budget may be too low to achieve any target
784            if (prev == null && v.Item1 != b) break;
785            var tgt = ((prev == null || v.Item1 == b) ? v.Item2 : prev.Item2);
786            var relTgt = CalculateRelativeDifference(max, problem.BestKnownQuality, tgt);
787            if (hits[key].ContainsKey(relTgt))
788              hits[key][relTgt] += 1.0 / (groupCount * problemCount);
789            else hits[key][relTgt] = 1.0 / (groupCount * problemCount);
790            break;
791          }
792          prev = v;
793        }
794        if (hits[key].Count == 0) hits.Remove(key);
795      }
796    }
797
798    private void CalculateHitsForAllBudgets(Dictionary<string, SortedList<double, double>> hits, IndexedDataRow<double> row, int groupCount, ProblemInstance problem, string groupName, int problemCount) {
799      var values = row.Values;
800      if (!hits.ContainsKey(groupName)) hits.Add(groupName, new SortedList<double, double>());
801
802      var i = 0;
803      var j = 0;
804      Tuple<double, double> prev = null;
805      var max = problem.IsMaximization();
806      while (i < budgets.Length && j < values.Count) {
807        var current = values[j];
808        if (current.Item1 >= budgets[i]) {
809          if (prev != null || current.Item1 == budgets[i]) {
810            var tgt = (prev == null || current.Item1 == budgets[i]) ? current.Item2 : prev.Item2;
811            var relTgt = CalculateRelativeDifference(max, problem.BestKnownQuality, tgt);
812            if (!hits[groupName].ContainsKey(relTgt)) hits[groupName][relTgt] = 0;
813            hits[groupName][relTgt] += 1.0 / (groupCount * problemCount * budgets.Length);
814          }
815          i++;
816        } else {
817          j++;
818          prev = current;
819        }
820      }
821      var lastTgt = values.Last().Item2;
822      var lastRelTgt = CalculateRelativeDifference(max, problem.BestKnownQuality, lastTgt);
823      if (i < budgets.Length && !hits[groupName].ContainsKey(lastRelTgt)) hits[groupName][lastRelTgt] = 0;
824      while (i < budgets.Length) {
825        hits[groupName][lastRelTgt] += 1.0 / (groupCount * problemCount * budgets.Length);
826        i++;
827      }
828    }
829    #endregion
830
831    private void UpdateCaption() {
832      Caption = Content != null ? Content.OptimizerName + " RLD View" : ViewAttribute.GetViewName(GetType());
833    }
834
835    private void groupComboBox_SelectedIndexChanged(object sender, EventArgs e) {
836      UpdateRuns();
837      SetEnabledStateOfControls();
838    }
839    private void problemComboBox_SelectedIndexChanged(object sender, EventArgs e) {
840      UpdateRuns();
841      SetEnabledStateOfControls();
842    }
843    private void dataTableComboBox_SelectedIndexChanged(object sender, EventArgs e) {
844      if (dataTableComboBox.SelectedIndex >= 0)
845        GenerateDefaultBudgets((string)dataTableComboBox.SelectedItem);
846      UpdateBestKnownQualities();
847      UpdateRuns();
848      SetEnabledStateOfControls();
849    }
850
851    private void logScalingCheckBox_CheckedChanged(object sender, EventArgs e) {
852      UpdateResultsByTarget();
853      byCostDataTable.VisualProperties.XAxisLogScale = byCostDataTable.Rows.Count > 0 && budgetLogScalingCheckBox.Checked;
854    }
855
856    private void boundShadingCheckBox_CheckedChanged(object sender, EventArgs e) {
857      UpdateResultsByTarget();
858    }
859
860    #region Event handlers for target analysis
861    private bool suppressTargetsEvents;
862    private void targetsTextBox_Validating(object sender, CancelEventArgs e) {
863      if (suppressTargetsEvents) return;
864      var targetStrings = targetsTextBox.Text.Split(new[] { '%', ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
865      var targetList = new List<decimal>();
866      foreach (var ts in targetStrings) {
867        decimal t;
868        if (!decimal.TryParse(ts, out t)) {
869          errorProvider.SetError(targetsTextBox, "Not all targets can be parsed: " + ts);
870          e.Cancel = true;
871          return;
872        }
873        if (targetsAreRelative)
874          targetList.Add(t / 100);
875        else targetList.Add(t);
876      }
877      if (targetList.Count == 0) {
878        errorProvider.SetError(targetsTextBox, "Give at least one target value!");
879        e.Cancel = true;
880        return;
881      }
882      e.Cancel = false;
883      errorProvider.SetError(targetsTextBox, null);
884      targets = targetsAreRelative ? targetList.Select(x => (double)x).OrderByDescending(x => x).ToArray() : targetList.Select(x => (double)x).ToArray();
885      suppressTargetsEvents = true;
886      try {
887        if (targetsAreRelative)
888          targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%";
889        else targetsTextBox.Text = string.Join(" ; ", targets);
890      } finally { suppressTargetsEvents = false; }
891
892      UpdateResultsByTarget();
893      SetEnabledStateOfControls();
894    }
895
896    private void aggregateTargetsCheckBox_CheckedChanged(object sender, EventArgs e) {
897      SuspendRepaint();
898      try {
899        UpdateResultsByTarget();
900      } finally { ResumeRepaint(true); }
901    }
902
903    private void relativeOrAbsoluteComboBox_SelectedIndexChanged(object sender, EventArgs e) {
904      if (suppressUpdates) return;
905      var pd = (ProblemInstance)problemComboBox.SelectedItem;
906      if (!double.IsNaN(pd.BestKnownQuality)) {
907        var max = pd.IsMaximization();
908        if (targetsAreRelative) targets = GetAbsoluteTargets(max, pd.BestKnownQuality).ToArray();
909        else {
910          // Rounding to 5 digits since it's certainly appropriate for this application
911          if (pd.BestKnownQuality > 0) {
912            targets = targets.Select(x => Math.Round(max ? 1.0 - (x / pd.BestKnownQuality) : (x / pd.BestKnownQuality) - 1.0, 5)).ToArray();
913          } else if (pd.BestKnownQuality < 0) {
914            targets = targets.Select(x => Math.Round(!max ? 1.0 - (x / pd.BestKnownQuality) : (x / pd.BestKnownQuality) - 1.0, 5)).ToArray();
915          }
916        }
917      }
918      targetsAreRelative = (string)relativeOrAbsoluteComboBox.SelectedItem == "relative";
919      SuspendRepaint();
920      suppressTargetsEvents = true;
921      try {
922        if (targetsAreRelative)
923          targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%";
924        else targetsTextBox.Text = string.Join(" ; ", targets);
925
926        UpdateResultsByTarget();
927      } finally { suppressTargetsEvents = false; ResumeRepaint(true); }
928    }
929
930    private void generateTargetsButton_Click(object sender, EventArgs e) {
931      decimal max = 1, min = 0, count = 10;
932      if (targets != null) {
933        max = (decimal)targets.Max();
934        min = (decimal)targets.Min();
935        count = targets.Length;
936      } else if (Content.Count > 0 && dataTableComboBox.SelectedIndex >= 0) {
937        var table = (string)dataTableComboBox.SelectedItem;
938        max = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Max(y => y.Item2)).Max();
939        min = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Min(y => y.Item2)).Min();
940        count = 6;
941      }
942      using (var dialog = new DefineArithmeticProgressionDialog(false, min, max, (max - min) / count)) {
943        if (dialog.ShowDialog() == DialogResult.OK) {
944          if (dialog.Values.Any()) {
945            targets = dialog.Values.Select(x => (double)x).ToArray();
946            suppressTargetsEvents = true;
947            if (targetsAreRelative)
948              targetsTextBox.Text = string.Join("% ; ", targets);
949            else targetsTextBox.Text = string.Join(" ; ", targets);
950            suppressTargetsEvents = false;
951
952            UpdateResultsByTarget();
953            SetEnabledStateOfControls();
954          }
955        }
956      }
957    }
958
959    private void addTargetsAsResultButton_Click(object sender, EventArgs e) {
960      var table = (string)dataTableComboBox.SelectedItem;
961      if (string.IsNullOrEmpty(table)) return;
962
963      var targetsPerProblem = problems.ToDictionary(x => x, x => x.BestKnownQuality);
964
965      foreach (var run in Content) {
966        if (!run.Results.ContainsKey(table)) continue;
967        var resultsTable = (IndexedDataTable<double>)run.Results[table];
968        var values = resultsTable.Rows.First().Values;
969        var i = 0;
970        var j = 0;
971        var pd = new ProblemInstance(run);
972        var max = pd.IsMaximization();
973        var absTargets = GetAbsoluteTargetsWorstToBest(max, targetsPerProblem[pd]);
974        while (i < absTargets.Length && j < values.Count) {
975          var target = absTargets[i];
976          var current = values[j];
977          if (max && current.Item2 >= target || !max && current.Item2 <= target) {
978            run.Results[table + ".Target" + target] = new DoubleValue(current.Item1);
979            i++;
980          } else {
981            j++;
982          }
983        }
984      }
985    }
986
987    private void markerCheckBox_CheckedChanged(object sender, EventArgs e) {
988      SuspendRepaint();
989      try {
990        UpdateResultsByTarget();
991      } finally { ResumeRepaint(true); }
992    }
993    #endregion
994
995    #region Event handlers for cost analysis
996    private bool suppressBudgetsEvents;
997    private void budgetsTextBox_Validating(object sender, CancelEventArgs e) {
998      if (suppressBudgetsEvents) return;
999      var budgetStrings = budgetsTextBox.Text.Split(new[] { ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
1000      var budgetList = new List<double>();
1001      foreach (var ts in budgetStrings) {
1002        double b;
1003        if (!double.TryParse(ts, out b)) {
1004          errorProvider.SetError(budgetsTextBox, "Not all targets can be parsed: " + ts);
1005          e.Cancel = true;
1006          return;
1007        }
1008        budgetList.Add(b);
1009      }
1010      if (budgetList.Count == 0) {
1011        errorProvider.SetError(budgetsTextBox, "Give at least one target value!");
1012        e.Cancel = true;
1013        return;
1014      }
1015      e.Cancel = false;
1016      errorProvider.SetError(budgetsTextBox, null);
1017      budgets = budgetList.ToArray();
1018      UpdateResultsByCost();
1019      SetEnabledStateOfControls();
1020    }
1021
1022    private void aggregateBudgetsCheckBox_CheckedChanged(object sender, EventArgs e) {
1023      SuspendRepaint();
1024      try {
1025        UpdateResultsByCost();
1026      } finally { ResumeRepaint(true); }
1027    }
1028
1029    private void generateBudgetsButton_Click(object sender, EventArgs e) {
1030      decimal max = 1, min = 0, count = 10;
1031      if (budgets != null) {
1032        max = (decimal)budgets.Max();
1033        min = (decimal)budgets.Min();
1034        count = budgets.Length;
1035      } else if (Content.Count > 0 && dataTableComboBox.SelectedIndex >= 0) {
1036        var table = (string)dataTableComboBox.SelectedItem;
1037        min = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Min(y => y.Item1)).Min();
1038        max = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Max(y => y.Item1)).Max();
1039        count = 6;
1040      }
1041      using (var dialog = new DefineArithmeticProgressionDialog(false, min, max, (max - min) / count)) {
1042        if (dialog.ShowDialog() == DialogResult.OK) {
1043          if (dialog.Values.Any()) {
1044            budgets = dialog.Values.OrderBy(x => x).Select(x => (double)x).ToArray();
1045
1046            suppressBudgetsEvents = true;
1047            budgetsTextBox.Text = string.Join(" ; ", budgets);
1048            suppressBudgetsEvents = false;
1049
1050            UpdateResultsByCost();
1051            SetEnabledStateOfControls();
1052          }
1053        }
1054      }
1055    }
1056
1057    private void addBudgetsAsResultButton_Click(object sender, EventArgs e) {
1058      var table = (string)dataTableComboBox.SelectedItem;
1059      var budgetStrings = budgetsTextBox.Text.Split(new[] { ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
1060      if (budgetStrings.Length == 0) {
1061        MessageBox.Show("Define a number of budgets.");
1062        return;
1063      }
1064      var budgetList = new List<double>();
1065      foreach (var bs in budgetStrings) {
1066        double v;
1067        if (!double.TryParse(bs, out v)) {
1068          MessageBox.Show("Budgets must be a valid number: " + bs);
1069          return;
1070        }
1071        budgetList.Add(v);
1072      }
1073      budgetList.Sort();
1074
1075      foreach (var run in Content) {
1076        if (!run.Results.ContainsKey(table)) continue;
1077        var resultsTable = (IndexedDataTable<double>)run.Results[table];
1078        var values = resultsTable.Rows.First().Values;
1079        var i = 0;
1080        var j = 0;
1081        Tuple<double, double> prev = null;
1082        while (i < budgetList.Count && j < values.Count) {
1083          var current = values[j];
1084          if (current.Item1 >= budgetList[i]) {
1085            if (prev != null || current.Item1 == budgetList[i]) {
1086              var tgt = (prev == null || current.Item1 == budgetList[i]) ? current.Item2 : prev.Item2;
1087              run.Results[table + ".Cost" + budgetList[i]] = new DoubleValue(tgt);
1088            }
1089            i++;
1090          } else {
1091            j++;
1092            prev = current;
1093          }
1094        }
1095      }
1096    }
1097    #endregion
1098
1099    #region Helpers
1100    private double CalculateRelativeDifference(bool maximization, double bestKnown, double fit) {
1101      if (bestKnown == 0) {
1102        // no relative difference with respect to bestKnown possible
1103        return maximization ? -fit : fit;
1104      }
1105      var absDiff = (fit - bestKnown);
1106      var relDiff = absDiff / bestKnown;
1107      if (maximization) {
1108        return bestKnown > 0 ? -relDiff : relDiff;
1109      } else {
1110        return bestKnown > 0 ? relDiff : -relDiff;
1111      }
1112    }
1113
1114    private Dictionary<ProblemInstance, double> CalculateBestTargetPerProblemInstance(string table) {
1115      if (table == null) table = string.Empty;
1116      return (from r in Content
1117              where r.Visible
1118              let pd = new ProblemInstance(r)
1119              let target = r.Parameters.ContainsKey("BestKnownQuality")
1120                           && r.Parameters["BestKnownQuality"] is DoubleValue
1121                ? ((DoubleValue)r.Parameters["BestKnownQuality"]).Value
1122                : (r.Results.ContainsKey(table) && r.Results[table] is IndexedDataTable<double>
1123                  ? ((IndexedDataTable<double>)r.Results[table]).Rows.First().Values.Last().Item2
1124                  : double.NaN)
1125              group target by pd into g
1126              select new { Problem = g.Key, Target = g.Any(x => !double.IsNaN(x)) ? (g.Key.IsMaximization() ? g.Max() : g.Where(x => !double.IsNaN(x)).Min()) : double.NaN })
1127        .ToDictionary(x => x.Problem, x => x.Target);
1128    }
1129
1130    private void UpdateRuns() {
1131      if (InvokeRequired) {
1132        Invoke((Action)UpdateRuns);
1133        return;
1134      }
1135      SuspendRepaint();
1136      try {
1137        UpdateResultsByTarget();
1138        UpdateResultsByCost();
1139      } finally { ResumeRepaint(true); }
1140    }
1141
1142    private void UpdateBestKnownQualities() {
1143      var table = (string)dataTableComboBox.SelectedItem;
1144      if (string.IsNullOrEmpty(table)) return;
1145
1146      var targetsPerProblem = CalculateBestTargetPerProblemInstance(table);
1147      foreach (var pd in problems) {
1148        double bkq;
1149        if (targetsPerProblem.TryGetValue(pd, out bkq))
1150          pd.BestKnownQuality = bkq;
1151        else pd.BestKnownQuality = double.NaN;
1152      }
1153      //problemComboBox.ResetBindings();
1154    }
1155    #endregion
1156
1157    private void ConfigureSeries(Series series) {
1158      series.SmartLabelStyle.Enabled = true;
1159      series.SmartLabelStyle.AllowOutsidePlotArea = LabelOutsidePlotAreaStyle.No;
1160      series.SmartLabelStyle.CalloutLineAnchorCapStyle = LineAnchorCapStyle.None;
1161      series.SmartLabelStyle.CalloutLineColor = series.Color;
1162      series.SmartLabelStyle.CalloutLineWidth = 2;
1163      series.SmartLabelStyle.CalloutStyle = LabelCalloutStyle.Underlined;
1164      series.SmartLabelStyle.IsOverlappedHidden = false;
1165      series.SmartLabelStyle.MaxMovingDistance = 200;
1166      series.ToolTip = series.LegendText + " X = #VALX, Y = #VALY";
1167    }
1168
1169    private void chart_MouseDown(object sender, MouseEventArgs e) {
1170      HitTestResult result = targetChart.HitTest(e.X, e.Y);
1171      if (result.ChartElementType == ChartElementType.LegendItem) {
1172        ToggleTargetChartSeriesVisible(result.Series);
1173      }
1174    }
1175    private void chart_MouseMove(object sender, MouseEventArgs e) {
1176      HitTestResult result = targetChart.HitTest(e.X, e.Y);
1177      if (result.ChartElementType == ChartElementType.LegendItem)
1178        this.Cursor = Cursors.Hand;
1179      else
1180        this.Cursor = Cursors.Default;
1181    }
1182    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
1183      foreach (LegendItem legendItem in e.LegendItems) {
1184        var series = targetChart.Series[legendItem.SeriesName];
1185        if (series != null) {
1186          bool seriesIsInvisible = invisibleTargetSeries.Any(x => x.Name == series.Name);
1187          foreach (LegendCell cell in legendItem.Cells) {
1188            cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black;
1189          }
1190        }
1191      }
1192    }
1193
1194    private void ToggleTargetChartSeriesVisible(Series series) {
1195      var indexList = invisibleTargetSeries.FindIndex(x => x.Name == series.Name);
1196      var indexChart = targetChart.Series.IndexOf(series);
1197      if (targetChart.Series.Count == 1) targetChart.ChartAreas[0].AxisX.IsLogarithmic = false;
1198      targetChart.Series.RemoveAt(indexChart);
1199      var s = indexList >= 0 ? invisibleTargetSeries[indexList] : new Series(series.Name) {
1200        Color = series.Color,
1201        ChartType = series.ChartType,
1202        BorderWidth = series.BorderWidth,
1203        BorderDashStyle = series.BorderDashStyle
1204      };
1205      if (indexList < 0) {
1206        // hide
1207        invisibleTargetSeries.Add(series);
1208        var shadeSeries = targetChart.Series.FirstOrDefault(x => x.Name == series.Name + "-range");
1209        if (shadeSeries != null) {
1210          if (targetChart.Series.Count == 1) targetChart.ChartAreas[0].AxisX.IsLogarithmic = false;
1211          targetChart.Series.Remove(shadeSeries);
1212          invisibleTargetSeries.Add(shadeSeries);
1213          indexChart--;
1214        }
1215      } else {
1216        // show
1217        invisibleTargetSeries.RemoveAt(indexList);
1218        var shadeSeries = invisibleTargetSeries.FirstOrDefault(x => x.Name == series.Name + "-range");
1219        if (shadeSeries != null) {
1220          invisibleTargetSeries.Remove(shadeSeries);
1221          InsertOrAddSeries(indexChart, shadeSeries);
1222          indexChart++;
1223        }
1224      }
1225      InsertOrAddSeries(indexChart, s);
1226      targetChart.ChartAreas[0].AxisX.IsLogarithmic = CanDisplayLogarithmic();
1227    }
1228
1229    private bool CanDisplayLogarithmic() {
1230      return targetLogScalingCheckBox.Checked
1231        && targetChart.Series.Count > 0 // must have a series
1232        && targetChart.Series.Any(x => x.Points.Count > 0) // at least one series must have points
1233        && targetChart.Series.All(s => s.Points.All(p => p.XValue > 0)); // all points must be positive
1234    }
1235
1236    private void InsertOrAddSeries(int index, Series s) {
1237      if (targetChart.Series.Count <= index)
1238        targetChart.Series.Add(s);
1239      else targetChart.Series.Insert(index, s);
1240    }
1241
1242    private class ProblemInstance : INotifyPropertyChanged {
1243      private readonly bool matchAll;
1244      public static readonly ProblemInstance MatchAll = new ProblemInstance() {
1245        ProblemName = "All with Best-Known"
1246      };
1247
1248      private ProblemInstance() {
1249        ProblemType = string.Empty;
1250        ProblemName = string.Empty;
1251        Evaluator = string.Empty;
1252        Maximization = string.Empty;
1253        DisplayProblemType = false;
1254        DisplayProblemName = false;
1255        DisplayEvaluator = false;
1256        DisplayMaximization = false;
1257        matchAll = true;
1258        BestKnownQuality = double.NaN;
1259      }
1260
1261      public ProblemInstance(IRun run) {
1262        ProblemType = GetStringValueOrEmpty(run, "Problem Type");
1263        ProblemName = GetStringValueOrEmpty(run, "Problem Name");
1264        Evaluator = GetStringValueOrEmpty(run, "Evaluator");
1265        Maximization = GetMaximizationValueOrEmpty(run, "Maximization");
1266        DisplayProblemType = !string.IsNullOrEmpty(ProblemType);
1267        DisplayProblemName = !string.IsNullOrEmpty(ProblemName);
1268        DisplayEvaluator = !string.IsNullOrEmpty(Evaluator);
1269        DisplayMaximization = !string.IsNullOrEmpty(Maximization);
1270        matchAll = false;
1271        BestKnownQuality = GetDoubleValueOrNaN(run, "BestKnownQuality");
1272      }
1273
1274      private bool displayProblemType;
1275      public bool DisplayProblemType {
1276        get { return displayProblemType; }
1277        set {
1278          if (displayProblemType == value) return;
1279          displayProblemType = value;
1280          OnPropertyChanged("DisplayProblemType");
1281        }
1282      }
1283      private string problemType;
1284      public string ProblemType {
1285        get { return problemType; }
1286        set {
1287          if (problemType == value) return;
1288          problemType = value;
1289          OnPropertyChanged("ProblemType");
1290        }
1291      }
1292      private bool displayProblemName;
1293      public bool DisplayProblemName {
1294        get { return displayProblemName; }
1295        set {
1296          if (displayProblemName == value) return;
1297          displayProblemName = value;
1298          OnPropertyChanged("DisplayProblemName");
1299        }
1300      }
1301      private string problemName;
1302      public string ProblemName {
1303        get { return problemName; }
1304        set {
1305          if (problemName == value) return;
1306          problemName = value;
1307          OnPropertyChanged("ProblemName");
1308        }
1309      }
1310      private bool displayEvaluator;
1311      public bool DisplayEvaluator {
1312        get { return displayEvaluator; }
1313        set {
1314          if (displayEvaluator == value) return;
1315          displayEvaluator = value;
1316          OnPropertyChanged("DisplayEvaluator");
1317        }
1318      }
1319      private string evaluator;
1320      public string Evaluator {
1321        get { return evaluator; }
1322        set {
1323          if (evaluator == value) return;
1324          evaluator = value;
1325          OnPropertyChanged("Evaluator");
1326        }
1327      }
1328      private bool displayMaximization;
1329      public bool DisplayMaximization {
1330        get { return displayMaximization; }
1331        set {
1332          if (displayMaximization == value) return;
1333          displayMaximization = value;
1334          OnPropertyChanged("DisplayMaximization");
1335        }
1336      }
1337      private string maximization;
1338      public string Maximization {
1339        get { return maximization; }
1340        set {
1341          if (maximization == value) return;
1342          maximization = value;
1343          OnPropertyChanged("Maximization");
1344        }
1345      }
1346      private double bestKnownQuality;
1347      public double BestKnownQuality {
1348        get { return bestKnownQuality; }
1349        set {
1350          if (bestKnownQuality == value) return;
1351          bestKnownQuality = value;
1352          OnPropertyChanged("BestKnownQuality");
1353        }
1354      }
1355
1356      public bool IsMaximization() {
1357        return Maximization == "MAX";
1358      }
1359
1360      public bool Match(IRun run) {
1361        return matchAll ||
1362               GetStringValueOrEmpty(run, "Problem Type") == ProblemType
1363               && GetStringValueOrEmpty(run, "Problem Name") == ProblemName
1364               && GetStringValueOrEmpty(run, "Evaluator") == Evaluator
1365               && GetMaximizationValueOrEmpty(run, "Maximization") == Maximization;
1366      }
1367
1368      private double GetDoubleValueOrNaN(IRun run, string key) {
1369        IItem param;
1370        if (run.Parameters.TryGetValue(key, out param)) {
1371          var dv = param as DoubleValue;
1372          return dv != null ? dv.Value : double.NaN;
1373        }
1374        return double.NaN;
1375      }
1376
1377      private string GetStringValueOrEmpty(IRun run, string key) {
1378        IItem param;
1379        if (run.Parameters.TryGetValue(key, out param)) {
1380          var sv = param as StringValue;
1381          return sv != null ? sv.Value : string.Empty;
1382        }
1383        return string.Empty;
1384      }
1385
1386      private string GetMaximizationValueOrEmpty(IRun run, string key) {
1387        IItem param;
1388        if (run.Parameters.TryGetValue(key, out param)) {
1389          var bv = param as BoolValue;
1390          return bv != null ? (bv.Value ? "MAX" : "MIN") : string.Empty;
1391        }
1392        return string.Empty;
1393      }
1394
1395      public override bool Equals(object obj) {
1396        var other = obj as ProblemInstance;
1397        if (other == null) return false;
1398        return ProblemType == other.ProblemType
1399               && ProblemName == other.ProblemName
1400               && Evaluator == other.Evaluator
1401               && Maximization == other.Maximization;
1402      }
1403
1404      public override int GetHashCode() {
1405        return ProblemType.GetHashCode() ^ ProblemName.GetHashCode() ^ Evaluator.GetHashCode() ^ Maximization.GetHashCode();
1406      }
1407
1408      public override string ToString() {
1409        return string.Join("  --  ", new[] {
1410          (DisplayProblemType ? ProblemType : string.Empty),
1411          (DisplayProblemName ? ProblemName : string.Empty),
1412          (DisplayEvaluator ? Evaluator : string.Empty),
1413          (DisplayMaximization ? Maximization : string.Empty),
1414          !double.IsNaN(BestKnownQuality) ? BestKnownQuality.ToString(CultureInfo.CurrentCulture.NumberFormat) : string.Empty }.Where(x => !string.IsNullOrEmpty(x)));
1415      }
1416
1417      public event PropertyChangedEventHandler PropertyChanged;
1418      protected virtual void OnPropertyChanged(string propertyName = null) {
1419        var handler = PropertyChanged;
1420        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
1421      }
1422    }
1423  }
1424}
Note: See TracBrowser for help on using the repository browser.