#region License Information /* HeuristicLab * Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Globalization; using System.Linq; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using HeuristicLab.Analysis; using HeuristicLab.Collections; using HeuristicLab.Core; using HeuristicLab.Core.Views; using HeuristicLab.Data; using HeuristicLab.MainForm; using HeuristicLab.MainForm.WindowsForms; namespace HeuristicLab.Optimization.Views { [View("Run-length Distribution View")] [Content(typeof(RunCollection), false)] public partial class RunCollectionRLDView : ItemView { private List invisibleTargetSeries; private const string AllInstances = "All Instances"; private static readonly Color[] colors = new[] { Color.FromArgb(0x40, 0x6A, 0xB7), Color.FromArgb(0xB1, 0x6D, 0x01), Color.FromArgb(0x4E, 0x8A, 0x06), Color.FromArgb(0x75, 0x50, 0x7B), Color.FromArgb(0x72, 0x9F, 0xCF), Color.FromArgb(0xA4, 0x00, 0x00), Color.FromArgb(0xAD, 0x7F, 0xA8), Color.FromArgb(0x29, 0x50, 0xCF), Color.FromArgb(0x90, 0xB0, 0x60), Color.FromArgb(0xF5, 0x89, 0x30), Color.FromArgb(0x55, 0x57, 0x53), Color.FromArgb(0xEF, 0x59, 0x59), Color.FromArgb(0xED, 0xD4, 0x30), Color.FromArgb(0x63, 0xC2, 0x16), }; private static readonly ChartDashStyle[] lineStyles = new[] { ChartDashStyle.Solid, ChartDashStyle.Dash, ChartDashStyle.DashDot, ChartDashStyle.Dot }; private static readonly DataRowVisualProperties.DataRowLineStyle[] hlLineStyles = new[] { DataRowVisualProperties.DataRowLineStyle.Solid, DataRowVisualProperties.DataRowLineStyle.Dash, DataRowVisualProperties.DataRowLineStyle.DashDot, DataRowVisualProperties.DataRowLineStyle.Dot }; public new RunCollection Content { get { return (RunCollection)base.Content; } set { base.Content = value; } } private double[] targets; private double[] budgets; private bool targetsAreRelative = true; private bool showLabelsInTargetChart = true; private readonly BindingList problems; private bool suppressUpdates; private readonly IndexedDataTable byCostDataTable; public IndexedDataTable ByCostDataTable { get { return byCostDataTable; } } public RunCollectionRLDView() { InitializeComponent(); invisibleTargetSeries = new List(); targetChart.CustomizeAllChartAreas(); targetChart.ChartAreas[0].CursorX.Interval = 1; targetChart.SuppressExceptions = true; byCostDataTable = new IndexedDataTable("ECDF by Cost", "A data table containing the ECDF of function values (relative to best-known).") { VisualProperties = { YAxisTitle = "Proportion of runs", YAxisMinimumFixedValue = 0, YAxisMinimumAuto = false, YAxisMaximumFixedValue = 1, YAxisMaximumAuto = false } }; byCostViewHost.Content = byCostDataTable; suppressUpdates = true; relativeOrAbsoluteComboBox.SelectedItem = targetsAreRelative ? "relative" : "absolute"; problems = new BindingList(); problemComboBox.DataSource = new BindingSource() { DataSource = problems }; problemComboBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged; suppressUpdates = false; } #region Content events protected override void RegisterContentEvents() { base.RegisterContentEvents(); Content.ItemsAdded += Content_ItemsAdded; Content.ItemsRemoved += Content_ItemsRemoved; Content.CollectionReset += Content_CollectionReset; Content.UpdateOfRunsInProgressChanged += Content_UpdateOfRunsInProgressChanged; Content.OptimizerNameChanged += Content_AlgorithmNameChanged; } protected override void DeregisterContentEvents() { Content.ItemsAdded -= Content_ItemsAdded; Content.ItemsRemoved -= Content_ItemsRemoved; Content.CollectionReset -= Content_CollectionReset; Content.UpdateOfRunsInProgressChanged -= Content_UpdateOfRunsInProgressChanged; Content.OptimizerNameChanged -= Content_AlgorithmNameChanged; base.DeregisterContentEvents(); } private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs e) { if (suppressUpdates) return; if (InvokeRequired) { Invoke(new CollectionItemsChangedEventHandler(Content_ItemsAdded), sender, e); return; } UpdateGroupAndProblemComboBox(); UpdateDataTableComboBox(); foreach (var run in e.Items) RegisterRunEvents(run); } private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs e) { if (suppressUpdates) return; if (InvokeRequired) { Invoke(new CollectionItemsChangedEventHandler(Content_ItemsRemoved), sender, e); return; } UpdateGroupAndProblemComboBox(); UpdateDataTableComboBox(); foreach (var run in e.Items) DeregisterRunEvents(run); } private void Content_CollectionReset(object sender, CollectionItemsChangedEventArgs e) { if (suppressUpdates) return; if (InvokeRequired) { Invoke(new CollectionItemsChangedEventHandler(Content_CollectionReset), sender, e); return; } UpdateGroupAndProblemComboBox(); UpdateDataTableComboBox(); foreach (var run in e.OldItems) DeregisterRunEvents(run); } private void Content_AlgorithmNameChanged(object sender, EventArgs e) { if (InvokeRequired) Invoke(new EventHandler(Content_AlgorithmNameChanged), sender, e); else UpdateCaption(); } private void Content_UpdateOfRunsInProgressChanged(object sender, EventArgs e) { if (InvokeRequired) { Invoke(new EventHandler(Content_UpdateOfRunsInProgressChanged), sender, e); return; } suppressUpdates = Content.UpdateOfRunsInProgress; if (!suppressUpdates) { UpdateDataTableComboBox(); UpdateGroupAndProblemComboBox(); UpdateRuns(); } } private void RegisterRunEvents(IRun run) { run.PropertyChanged += run_PropertyChanged; } private void DeregisterRunEvents(IRun run) { run.PropertyChanged -= run_PropertyChanged; } private void run_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (suppressUpdates) return; if (InvokeRequired) { Invoke((Action)run_PropertyChanged, sender, e); } else { if (e.PropertyName == "Visible") UpdateRuns(); } } #endregion protected override void OnContentChanged() { base.OnContentChanged(); dataTableComboBox.Items.Clear(); groupComboBox.Items.Clear(); targetChart.ChartAreas[0].AxisX.IsLogarithmic = false; targetChart.Series.Clear(); invisibleTargetSeries.Clear(); byCostDataTable.VisualProperties.XAxisLogScale = false; byCostDataTable.Rows.Clear(); UpdateCaption(); if (Content != null) { UpdateGroupAndProblemComboBox(); UpdateDataTableComboBox(); } } private void UpdateGroupAndProblemComboBox() { var selectedGroupItem = (string)groupComboBox.SelectedItem; var groupings = Content.ParameterNames.OrderBy(x => x).ToArray(); groupComboBox.Items.Clear(); groupComboBox.Items.Add(AllInstances); groupComboBox.Items.AddRange(groupings); if (selectedGroupItem != null && groupComboBox.Items.Contains(selectedGroupItem)) { groupComboBox.SelectedItem = selectedGroupItem; } else if (groupComboBox.Items.Count > 0) { groupComboBox.SelectedItem = groupComboBox.Items[0]; } var table = (string)dataTableComboBox.SelectedItem; var problemDict = CalculateBestTargetPerProblemInstance(table); var problemTypesDifferent = problemDict.Keys.Select(x => x.ProblemType).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1; var problemNamesDifferent = problemDict.Keys.Select(x => x.ProblemName).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1; var evaluatorDifferent = problemDict.Keys.Select(x => x.Evaluator).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1; var maximizationDifferent = problemDict.Keys.Select(x => x.Maximization).Distinct().Count() > 1; var allEqual = !problemTypesDifferent && !problemNamesDifferent && !evaluatorDifferent && !maximizationDifferent; var selectedProblemItem = (ProblemInstance)problemComboBox.SelectedItem; problemComboBox.DataSource = null; problemComboBox.Items.Clear(); problems.Clear(); problems.Add(ProblemInstance.MatchAll); problemComboBox.DataSource = new BindingSource() { DataSource = problems }; problemComboBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged; if (problems[0].Equals(selectedProblemItem)) problemComboBox.SelectedItem = problems[0]; foreach (var p in problemDict.ToList()) { p.Key.BestKnownQuality = p.Value; p.Key.DisplayProblemType = problemTypesDifferent; p.Key.DisplayProblemName = problemNamesDifferent || allEqual; p.Key.DisplayEvaluator = evaluatorDifferent; p.Key.DisplayMaximization = maximizationDifferent; problems.Add(p.Key); if (p.Key.Equals(selectedProblemItem)) problemComboBox.SelectedItem = p.Key; } SetEnabledStateOfControls(); } private void UpdateDataTableComboBox() { string selectedItem = (string)dataTableComboBox.SelectedItem; dataTableComboBox.Items.Clear(); var dataTables = (from run in Content from result in run.Results where result.Value is IndexedDataTable select result.Key).Distinct().ToArray(); dataTableComboBox.Items.AddRange(dataTables); if (selectedItem != null && dataTableComboBox.Items.Contains(selectedItem)) { dataTableComboBox.SelectedItem = selectedItem; } else if (dataTableComboBox.Items.Count > 0) { dataTableComboBox.SelectedItem = dataTableComboBox.Items[0]; } } protected override void SetEnabledStateOfControls() { base.SetEnabledStateOfControls(); groupComboBox.Enabled = Content != null; problemComboBox.Enabled = Content != null && problemComboBox.Items.Count > 1; dataTableComboBox.Enabled = Content != null && dataTableComboBox.Items.Count > 1; addTargetsAsResultButton.Enabled = Content != null && targets != null && dataTableComboBox.SelectedIndex >= 0; addBudgetsAsResultButton.Enabled = Content != null && budgets != null && dataTableComboBox.SelectedIndex >= 0; generateTargetsButton.Enabled = targets != null; } private IEnumerable GroupRuns() { var table = (string)dataTableComboBox.SelectedItem; if (string.IsNullOrEmpty(table)) yield break; var selectedGroup = (string)groupComboBox.SelectedItem; if (string.IsNullOrEmpty(selectedGroup)) yield break; var selectedProblem = (ProblemInstance)problemComboBox.SelectedItem; if (selectedProblem == null) yield break; foreach (var x in (from r in Content where (selectedGroup == AllInstances || r.Parameters.ContainsKey(selectedGroup)) && selectedProblem.Match(r) && r.Results.ContainsKey(table) && r.Visible let key = selectedGroup == AllInstances ? AllInstances : r.Parameters[selectedGroup].ToString() group r by key into g select new AlgorithmInstance(g.Key, g, problems))) { yield return x; } } #region Performance analysis by (multiple) target(s) private void UpdateResultsByTarget() { // necessary to reset log scale -> empty chart cannot use log scaling targetChart.ChartAreas[0].AxisX.IsLogarithmic = false; targetChart.Series.Clear(); invisibleTargetSeries.Clear(); var table = (string)dataTableComboBox.SelectedItem; if (string.IsNullOrEmpty(table)) return; if (targets == null) GenerateDefaultTargets(); var algInstances = GroupRuns().ToList(); if (algInstances.Count == 0) return; var xAxisTitles = new HashSet(); // hits describes the number of target hits at a certain time for a certain group var hits = new Dictionary>(); // misses describes the number of target misses after a certain time for a certain group // for instance when a run ends, but has not achieved all targets, misses describes // how many targets have been left open at the point when the run ended var misses = new Dictionary>(); var totalRuns = new Dictionary(); var aggregate = aggregateTargetsCheckBox.Checked; double minEff = double.MaxValue, maxEff = double.MinValue; foreach (var alg in algInstances) { var noRuns = 0; SortedList epdfHits = null, epdfMisses = null; if (aggregate) { hits[alg.Name] = epdfHits = new SortedList(); misses[alg.Name] = epdfMisses = new SortedList(); } foreach (var problem in alg.GetProblemInstances()) { var max = problem.IsMaximization(); var absTargets = GetAbsoluteTargets(problem).ToArray(); foreach (var run in alg.GetRuns(problem)) { noRuns++; var resultsTable = (IndexedDataTable)run.Results[table]; xAxisTitles.Add(resultsTable.VisualProperties.XAxisTitle); var efforts = absTargets.Select(t => GetEffortToHitTarget(resultsTable.Rows.First().Values, t, max)).ToArray(); minEff = Math.Min(minEff, efforts.Min(x => x.Item2)); maxEff = Math.Max(maxEff, efforts.Max(x => x.Item2)); for (var idx = 0; idx < efforts.Length; idx++) { var e = efforts[idx]; if (!aggregate) { var key = alg.Name + "@" + (targetsAreRelative ? (targets[idx] * 100).ToString(CultureInfo.CurrentCulture.NumberFormat) + "%" : targets[idx].ToString(CultureInfo.CurrentCulture.NumberFormat)); if (!hits.TryGetValue(key, out epdfHits)) hits[key] = epdfHits = new SortedList(); if (!misses.TryGetValue(key, out epdfMisses)) misses[key] = epdfMisses = new SortedList(); totalRuns[key] = noRuns; }; var list = e.Item1 ? epdfHits : epdfMisses; int v; if (list.TryGetValue(e.Item2, out v)) list[e.Item2] = v + 1; else list[e.Item2] = 1; } } } if (aggregate) totalRuns[alg.Name] = noRuns; } UpdateTargetChartAxisXBounds(minEff, maxEff); DrawTargetsEcdf(hits, misses, totalRuns); if (targets.Length == 1) { if (targetsAreRelative) targetChart.ChartAreas[0].AxisY.Title = "Probability to be " + (targets[0] * 100) + "% worse than best"; else targetChart.ChartAreas[0].AxisY.Title = "Probability to reach at least a fitness of " + targets[0]; } else targetChart.ChartAreas[0].AxisY.Title = "Proportion of reached targets"; targetChart.ChartAreas[0].AxisX.Title = string.Join(" / ", xAxisTitles); targetChart.ChartAreas[0].AxisX.IsLogarithmic = CanDisplayLogarithmic(); targetChart.ChartAreas[0].CursorY.Interval = 0.05; UpdateErtTables(algInstances); } private void DrawTargetsEcdf(Dictionary> hits, Dictionary> misses, Dictionary noRuns) { var colorCount = 0; var lineStyleCount = 0; var showMarkers = markerCheckBox.Checked; foreach (var list in hits) { var row = new Series(list.Key) { ChartType = SeriesChartType.StepLine, BorderWidth = 3, Color = colors[colorCount], BorderDashStyle = lineStyles[lineStyleCount], }; var rowShade = new Series(list.Key + "-range") { IsVisibleInLegend = false, ChartType = SeriesChartType.Range, Color = Color.FromArgb(32, colors[colorCount]), YValuesPerPoint = 2 }; var ecdf = 0.0; var missedecdf = 0.0; var iter = misses[list.Key].GetEnumerator(); var moreMisses = iter.MoveNext(); var totalTargets = noRuns[list.Key]; if (aggregateTargetsCheckBox.Checked) totalTargets *= targets.Length; var movingTargets = totalTargets; var labelPrinted = false; foreach (var h in list.Value) { var prevmissedecdf = missedecdf; while (moreMisses && iter.Current.Key <= h.Key) { if (!labelPrinted && row.Points.Count > 0) { var point = row.Points.Last(); if (showLabelsInTargetChart) point.Label = row.Name; point.MarkerStyle = MarkerStyle.Cross; point.MarkerBorderWidth = 1; point.MarkerSize = 10; labelPrinted = true; rowShade.Points.Add(new DataPoint(point.XValue, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets})); } missedecdf += iter.Current.Value; movingTargets -= iter.Current.Value; if (row.Points.Count > 0 && row.Points.Last().XValue == iter.Current.Key) { row.Points.Last().SetValueY(ecdf / movingTargets); row.Points.Last().Color = Color.FromArgb(255 - (int)Math.Floor(255 * (prevmissedecdf / totalTargets)), colors[colorCount]); } else { var dp = new DataPoint(iter.Current.Key, ecdf / movingTargets) { Color = Color.FromArgb(255 - (int)Math.Floor(255 * (prevmissedecdf / totalTargets)), colors[colorCount]) }; if (showMarkers) { dp.MarkerStyle = MarkerStyle.Circle; dp.MarkerBorderWidth = 1; dp.MarkerSize = 5; } row.Points.Add(dp); prevmissedecdf = missedecdf; } if (boundShadingCheckBox.Checked) { if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == iter.Current.Key) rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets); else rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets})); } moreMisses = iter.MoveNext(); if (!labelPrinted) { var point = row.Points.Last(); if (showLabelsInTargetChart) point.Label = row.Name; point.MarkerStyle = MarkerStyle.Cross; point.MarkerBorderWidth = 1; point.MarkerSize = 10; labelPrinted = true; } } ecdf += h.Value; if (row.Points.Count > 0 && row.Points.Last().XValue == h.Key) { row.Points.Last().SetValueY(ecdf / movingTargets); row.Points.Last().Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount]); } else { var dp = new DataPoint(h.Key, ecdf / movingTargets) { Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount]) }; if (showMarkers) { dp.MarkerStyle = MarkerStyle.Circle; dp.MarkerBorderWidth = 1; dp.MarkerSize = 5; } row.Points.Add(dp); } if (missedecdf > 0 && boundShadingCheckBox.Checked) { if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == h.Key) rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets); else rowShade.Points.Add(new DataPoint(h.Key, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets})); } } while (moreMisses) { // if there are misses beyond the last hit we extend the shaded area missedecdf += iter.Current.Value; var dp = new DataPoint(iter.Current.Key, ecdf / movingTargets) { Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount]) }; if (showMarkers) { dp.MarkerStyle = MarkerStyle.Circle; dp.MarkerBorderWidth = 1; dp.MarkerSize = 5; } row.Points.Add(dp); if (boundShadingCheckBox.Checked) { rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets})); } moreMisses = iter.MoveNext(); if (!labelPrinted && row.Points.Count > 0) { var point = row.Points.Last(); if (showLabelsInTargetChart) point.Label = row.Name; point.MarkerStyle = MarkerStyle.Cross; point.MarkerBorderWidth = 1; point.MarkerSize = 10; labelPrinted = true; } } if (!labelPrinted) { var point = row.Points.Last(); if (showLabelsInTargetChart) point.Label = row.Name; point.MarkerStyle = MarkerStyle.Cross; point.MarkerBorderWidth = 1; point.MarkerSize = 10; rowShade.Points.Add(new DataPoint(point.XValue, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets})); labelPrinted = true; } ConfigureSeries(row); targetChart.Series.Add(rowShade); targetChart.Series.Add(row); colorCount = (colorCount + 1) % colors.Length; if (colorCount == 0) lineStyleCount = (lineStyleCount + 1) % lineStyles.Length; } } private void UpdateTargetChartAxisXBounds(double minEff, double maxEff) { var minZeros = (int)Math.Floor(Math.Log10(minEff)); var maxZeros = (int)Math.Floor(Math.Log10(maxEff)); var axisMin = (decimal)Math.Pow(10, minZeros); var axisMax = (decimal)Math.Pow(10, maxZeros); if (!targetLogScalingCheckBox.Checked) { var minAdd = (decimal)Math.Pow(10, minZeros - 1) * 2; var maxAdd = (decimal)Math.Pow(10, maxZeros - 1) * 2; while (axisMin + minAdd < (decimal)minEff) axisMin += minAdd; while (axisMax <= (decimal)maxEff) axisMax += maxAdd; } else axisMax = (decimal)Math.Pow(10, (int)Math.Ceiling(Math.Log10(maxEff))); targetChart.ChartAreas[0].AxisX.Minimum = (double)axisMin; targetChart.ChartAreas[0].AxisX.Maximum = (double)axisMax; } private IEnumerable GetAbsoluteTargets(ProblemInstance pInstance) { if (!targetsAreRelative) return targets; var maximization = pInstance.IsMaximization(); var bestKnown = pInstance.BestKnownQuality; if (double.IsNaN(bestKnown)) throw new ArgumentException("Problem instance does not have a defined best - known quality."); IEnumerable tmp = null; if (bestKnown > 0) { tmp = targets.Select(x => (maximization ? (1 - x) : (1 + x)) * bestKnown); } else if (bestKnown < 0) { tmp = targets.Select(x => (!maximization ? (1 - x) : (1 + x)) * bestKnown); } else { // relative to 0 is impossible tmp = targets; } return tmp; } private double[] GetAbsoluteTargetsWorstToBest(ProblemInstance pInstance) { if (double.IsNaN(pInstance.BestKnownQuality)) throw new ArgumentException("Problem instance does not have a defined best-known quality."); var absTargets = GetAbsoluteTargets(pInstance); return (pInstance.IsMaximization() ? absTargets.OrderBy(x => x) : absTargets.OrderByDescending(x => x)).ToArray(); } private void GenerateDefaultTargets() { targets = new[] { 0.1, 0.095, 0.09, 0.085, 0.08, 0.075, 0.07, 0.065, 0.06, 0.055, 0.05, 0.045, 0.04, 0.035, 0.03, 0.025, 0.02, 0.015, 0.01, 0.005, 0 }; SynchronizeTargetTextBox(); } private Tuple GetEffortToHitTarget( ObservableList> convergenceGraph, double absTarget, bool maximization) { if (convergenceGraph.Count == 0) throw new ArgumentException("Convergence graph is empty.", "convergenceGraph"); var index = convergenceGraph.BinarySearch(Tuple.Create(0.0, absTarget), new TargetComparer(maximization)); if (index >= 0) { return Tuple.Create(true, convergenceGraph[index].Item1); } else { index = ~index; if (index >= convergenceGraph.Count) return Tuple.Create(false, convergenceGraph.Last().Item1); return Tuple.Create(true, convergenceGraph[index].Item1); } } private void UpdateErtTables(List algorithmInstances) { ertTableView.Content = null; var columns = targets.Length + 1; var totalRows = algorithmInstances.Count * algorithmInstances.Max(x => x.GetNumberOfProblemInstances()) + algorithmInstances.Max(x => x.GetNumberOfProblemInstances()); var matrix = new StringMatrix(totalRows, columns); var rowNames = new List(); matrix.ColumnNames = targets.Select(x => targetsAreRelative ? (100 * x).ToString() + "%" : x.ToString()) .Concat(new[] { "#succ" }).ToList(); var rowCount = 0; var tableName = (string)dataTableComboBox.SelectedItem; if (string.IsNullOrEmpty(tableName)) return; var problems = algorithmInstances.SelectMany(x => x.GetProblemInstances()).Distinct().ToList(); foreach (var problem in problems) { var max = problem.IsMaximization(); rowNames.Add(problem.ToString()); var absTargets = GetAbsoluteTargetsWorstToBest(problem); if (targetsAreRelative) { // print out the absolute target values for (var i = 0; i < absTargets.Length; i++) { matrix[rowCount, i] = absTargets[i].ToString("##,0.0", CultureInfo.CurrentCulture.NumberFormat); } } rowCount++; foreach (var alg in algorithmInstances) { rowNames.Add(alg.Name); var runs = alg.GetRuns(problem).ToList(); if (runs.Count == 0) { matrix[rowCount, columns - 1] = "N/A"; rowCount++; continue; } var result = default(ErtCalculationResult); for (var i = 0; i < absTargets.Length; i++) { result = ExpectedRuntimeHelper.CalculateErt(runs, tableName, absTargets[i], max); matrix[rowCount, i] = result.ToString(); } matrix[rowCount, columns - 1] = targets.Length > 0 ? result.SuccessfulRuns + "/" + result.TotalRuns : "-"; rowCount++; } } matrix.RowNames = rowNames; ertTableView.Content = matrix; ertTableView.DataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells); } #endregion #region Performance analysis by (multiple) budget(s) private void UpdateResultsByCost() { // necessary to reset log scale -> empty chart cannot use log scaling byCostDataTable.VisualProperties.XAxisLogScale = false; byCostDataTable.Rows.Clear(); var table = (string)dataTableComboBox.SelectedItem; if (string.IsNullOrEmpty(table)) return; if (budgets == null) GenerateDefaultBudgets(table); var algInstances = GroupRuns().ToList(); if (algInstances.Count == 0) return; var colorCount = 0; var lineStyleCount = 0; foreach (var alg in algInstances) { var hits = new Dictionary>(); foreach (var problem in alg.GetProblemInstances()) { foreach (var run in alg.GetRuns(problem)) { var resultsTable = (IndexedDataTable)run.Results[table]; CalculateHitsForEachBudget(hits, resultsTable.Rows.First(), problem, alg.Name); } } foreach (var list in hits) { var row = new IndexedDataRow(list.Key) { VisualProperties = { ChartType = DataRowVisualProperties.DataRowChartType.StepLine, LineWidth = 2, Color = colors[colorCount], LineStyle = hlLineStyles[lineStyleCount], StartIndexZero = false } }; var total = 0.0; var count = list.Value.Count; foreach (var h in list.Value) { total += h.Value; row.Values.Add(Tuple.Create(h.Key, total / (double)count)); } byCostDataTable.Rows.Add(row); } colorCount = (colorCount + 1) % colors.Length; if (colorCount == 0) lineStyleCount = (lineStyleCount + 1) % lineStyles.Length; } byCostDataTable.VisualProperties.XAxisTitle = "Targets to Best-Known Ratio"; byCostDataTable.VisualProperties.XAxisLogScale = byCostDataTable.Rows.Count > 0 && budgetLogScalingCheckBox.Checked; } private void GenerateDefaultBudgets(string table) { var runs = Content; var min = runs.Select(x => ((IndexedDataTable)x.Results[table]).Rows.First().Values.Select(y => y.Item1).Min()).Min(); var max = runs.Select(x => ((IndexedDataTable)x.Results[table]).Rows.First().Values.Select(y => y.Item1).Max()).Max(); var points = 3; budgets = Enumerable.Range(1, points).Select(x => min + (x / (double)points) * (max - min)).ToArray(); suppressBudgetsEvents = true; budgetsTextBox.Text = string.Join(" ; ", budgets); suppressBudgetsEvents = false; } private void CalculateHitsForEachBudget(Dictionary> hits, IndexedDataRow row, ProblemInstance problem, string groupName) { var max = problem.IsMaximization(); var prevIndex = 0; foreach (var b in budgets) { var key = groupName + "-" + b; var index = row.Values.BinarySearch(prevIndex, row.Values.Count - prevIndex, Tuple.Create(b, 0.0), new CostComparer()); if (index < 0) { index = ~index; if (index >= row.Values.Count) break; // the run wasn't long enough to use up budget b (or any subsequent larger one) } if (!hits.ContainsKey(key)) hits.Add(key, new SortedList()); var v = row.Values[index]; var relTgt = CalculateRelativeDifference(max, problem.BestKnownQuality, v.Item2) + 1; if (hits[key].ContainsKey(relTgt)) hits[key][relTgt]++; else hits[key][relTgt] = 1.0; prevIndex = index; } } #endregion private void UpdateCaption() { Caption = Content != null ? Content.OptimizerName + " RLD View" : ViewAttribute.GetViewName(GetType()); } private void SynchronizeTargetTextBox() { if (InvokeRequired) Invoke((Action)SynchronizeTargetTextBox); else { suppressTargetsEvents = true; try { if (targetsAreRelative) targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%"; else targetsTextBox.Text = string.Join(" ; ", targets); } finally { suppressTargetsEvents = false; } } } private void groupComboBox_SelectedIndexChanged(object sender, EventArgs e) { UpdateRuns(); SetEnabledStateOfControls(); } private void problemComboBox_SelectedIndexChanged(object sender, EventArgs e) { UpdateRuns(); SetEnabledStateOfControls(); } private void dataTableComboBox_SelectedIndexChanged(object sender, EventArgs e) { if (dataTableComboBox.SelectedIndex >= 0) GenerateDefaultBudgets((string)dataTableComboBox.SelectedItem); UpdateBestKnownQualities(); UpdateRuns(); SetEnabledStateOfControls(); } private void logScalingCheckBox_CheckedChanged(object sender, EventArgs e) { UpdateResultsByTarget(); byCostDataTable.VisualProperties.XAxisLogScale = byCostDataTable.Rows.Count > 0 && budgetLogScalingCheckBox.Checked; } private void boundShadingCheckBox_CheckedChanged(object sender, EventArgs e) { UpdateResultsByTarget(); } #region Event handlers for target analysis private bool suppressTargetsEvents; private void targetsTextBox_Validating(object sender, CancelEventArgs e) { if (suppressTargetsEvents) return; var targetStrings = targetsTextBox.Text.Split(new[] { '%', ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries); var targetList = new List(); foreach (var ts in targetStrings) { decimal t; if (!decimal.TryParse(ts, out t)) { errorProvider.SetError(targetsTextBox, "Not all targets can be parsed: " + ts); e.Cancel = true; return; } if (targetsAreRelative) targetList.Add(t / 100); else targetList.Add(t); } if (targetList.Count == 0) { errorProvider.SetError(targetsTextBox, "Give at least one target value!"); e.Cancel = true; return; } e.Cancel = false; errorProvider.SetError(targetsTextBox, null); targets = targetsAreRelative ? targetList.Select(x => (double)x).OrderByDescending(x => x).ToArray() : targetList.Select(x => (double)x).ToArray(); SynchronizeTargetTextBox(); UpdateResultsByTarget(); SetEnabledStateOfControls(); } private void aggregateTargetsCheckBox_CheckedChanged(object sender, EventArgs e) { SuspendRepaint(); try { UpdateResultsByTarget(); } finally { ResumeRepaint(true); } } private void relativeOrAbsoluteComboBox_SelectedIndexChanged(object sender, EventArgs e) { if (suppressUpdates) return; var pd = (ProblemInstance)problemComboBox.SelectedItem; if (!double.IsNaN(pd.BestKnownQuality)) { var max = pd.IsMaximization(); if (targetsAreRelative) targets = GetAbsoluteTargets(pd).ToArray(); else { // Rounding to 5 digits since it's certainly appropriate for this application if (pd.BestKnownQuality > 0) { targets = targets.Select(x => Math.Round(max ? 1.0 - (x / pd.BestKnownQuality) : (x / pd.BestKnownQuality) - 1.0, 5)).ToArray(); } else if (pd.BestKnownQuality < 0) { targets = targets.Select(x => Math.Round(!max ? 1.0 - (x / pd.BestKnownQuality) : (x / pd.BestKnownQuality) - 1.0, 5)).ToArray(); } } } targetsAreRelative = (string)relativeOrAbsoluteComboBox.SelectedItem == "relative"; SynchronizeTargetTextBox(); try { SuspendRepaint(); UpdateResultsByTarget(); } finally { ResumeRepaint(true); } } private void generateTargetsButton_Click(object sender, EventArgs e) { if (targets == null) return; decimal max = 10, min = 0, count = 10; max = (decimal)targets.Max(); min = (decimal)targets.Min(); count = targets.Length - 1; if (targetsAreRelative) { max *= 100; min *= 100; } using (var dialog = new DefineArithmeticProgressionDialog(false, min, max, (max - min) / count)) { if (dialog.ShowDialog() == DialogResult.OK) { if (dialog.Values.Any()) { targets = targetsAreRelative ? dialog.Values.OrderByDescending(x => x).Select(x => (double)x / 100.0).ToArray() : dialog.Values.Select(x => (double)x).ToArray(); SynchronizeTargetTextBox(); UpdateResultsByTarget(); SetEnabledStateOfControls(); } } } } private void addTargetsAsResultButton_Click(object sender, EventArgs e) { var table = (string)dataTableComboBox.SelectedItem; if (string.IsNullOrEmpty(table)) return; foreach (var run in Content) { if (!run.Results.ContainsKey(table)) continue; var resultsTable = (IndexedDataTable)run.Results[table]; var values = resultsTable.Rows.First().Values; var pd = new ProblemInstance(run); pd = problems.Single(x => x.Equals(pd)); var max = pd.IsMaximization(); var absTargets = GetAbsoluteTargetsWorstToBest(pd); var prevIndex = 0; for (var i = 0; i < absTargets.Length; i++) { var absTarget = absTargets[i]; var index = values.BinarySearch(prevIndex, values.Count - prevIndex, Tuple.Create(0.0, absTarget), new TargetComparer(max)); if (index < 0) { index = ~index; if (index >= values.Count) break; // the target (and subsequent ones) wasn't achieved } var target = targetsAreRelative ? (targets[i] * 100) : absTarget; run.Results[table + (targetsAreRelative ? ".RelTarget " : ".AbsTarget ") + target + (targetsAreRelative ? "%" : string.Empty)] = new DoubleValue(values[index].Item1); prevIndex = index; } } } private void markerCheckBox_CheckedChanged(object sender, EventArgs e) { SuspendRepaint(); try { UpdateResultsByTarget(); } finally { ResumeRepaint(true); } } private void showLabelsCheckBox_CheckedChanged(object sender, EventArgs e) { showLabelsInTargetChart = showLabelsCheckBox.Checked; SuspendRepaint(); try { UpdateResultsByTarget(); } finally { ResumeRepaint(true); } } #endregion #region Event handlers for cost analysis private bool suppressBudgetsEvents; private void budgetsTextBox_Validating(object sender, CancelEventArgs e) { if (suppressBudgetsEvents) return; var budgetStrings = budgetsTextBox.Text.Split(new[] { ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries); var budgetList = new List(); foreach (var ts in budgetStrings) { double b; if (!double.TryParse(ts, out b)) { errorProvider.SetError(budgetsTextBox, "Not all budgets can be parsed: " + ts); e.Cancel = true; return; } budgetList.Add(b); } if (budgetList.Count == 0) { errorProvider.SetError(budgetsTextBox, "Give at least one budget value!"); e.Cancel = true; return; } e.Cancel = false; errorProvider.SetError(budgetsTextBox, null); budgets = budgetList.OrderBy(x => x).ToArray(); try { suppressBudgetsEvents = true; budgetsTextBox.Text = string.Join(" ; ", budgets); } finally { suppressBudgetsEvents = false; } UpdateResultsByCost(); SetEnabledStateOfControls(); } private void generateBudgetsButton_Click(object sender, EventArgs e) { decimal max = 1, min = 0, count = 10; if (budgets != null) { max = (decimal)budgets.Max(); min = (decimal)budgets.Min(); count = budgets.Length; } else if (Content.Count > 0 && dataTableComboBox.SelectedIndex >= 0) { var table = (string)dataTableComboBox.SelectedItem; min = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable)x.Results[table]).Rows.First().Values.Min(y => y.Item1)).Min(); max = (decimal)Content.Where(x => x.Results.ContainsKey(table)).Select(x => ((IndexedDataTable)x.Results[table]).Rows.First().Values.Max(y => y.Item1)).Max(); count = 3; } using (var dialog = new DefineArithmeticProgressionDialog(false, min, max, (max - min) / count)) { if (dialog.ShowDialog() == DialogResult.OK) { if (dialog.Values.Any()) { budgets = dialog.Values.OrderBy(x => x).Select(x => (double)x).ToArray(); try { suppressBudgetsEvents = true; budgetsTextBox.Text = string.Join(" ; ", budgets); } finally { suppressBudgetsEvents = false; } UpdateResultsByCost(); SetEnabledStateOfControls(); } } } } private void addBudgetsAsResultButton_Click(object sender, EventArgs e) { var table = (string)dataTableComboBox.SelectedItem; foreach (var run in Content) { if (!run.Results.ContainsKey(table)) continue; var resultsTable = (IndexedDataTable)run.Results[table]; var values = resultsTable.Rows.First().Values; var pd = new ProblemInstance(run); pd = problems.Single(x => x.Equals(pd)); var prevIndex = 0; foreach (var b in budgets) { var index = values.BinarySearch(prevIndex, values.Count - prevIndex, Tuple.Create(b, 0.0), new CostComparer()); if (index < 0) { index = ~index; if (index >= values.Count) break; // the run wasn't long enough to use up budget b (or any subsequent larger one) } var v = values[index]; var tgt = targetsAreRelative ? CalculateRelativeDifference(pd.IsMaximization(), pd.BestKnownQuality, v.Item2) : v.Item2; run.Results[table + (targetsAreRelative ? ".CostForRelTarget " : ".CostForAbsTarget ") + b] = new DoubleValue(tgt); prevIndex = index; } } } #endregion #region Helpers private double CalculateRelativeDifference(bool maximization, double bestKnown, double fit) { if (bestKnown == 0) { // no relative difference with respect to bestKnown possible return maximization ? -fit : fit; } var absDiff = (fit - bestKnown); var relDiff = absDiff / bestKnown; if (maximization) { return bestKnown > 0 ? -relDiff : relDiff; } else { return bestKnown > 0 ? relDiff : -relDiff; } } private Dictionary CalculateBestTargetPerProblemInstance(string table) { if (table == null) table = string.Empty; return (from r in Content where r.Visible let pd = new ProblemInstance(r) let target = r.Parameters.ContainsKey("BestKnownQuality") && r.Parameters["BestKnownQuality"] is DoubleValue ? ((DoubleValue)r.Parameters["BestKnownQuality"]).Value : (r.Results.ContainsKey(table) && r.Results[table] is IndexedDataTable ? ((IndexedDataTable)r.Results[table]).Rows.First().Values.Last().Item2 : double.NaN) group target by pd into g 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 }) .ToDictionary(x => x.Problem, x => x.Target); } private void UpdateRuns() { if (InvokeRequired) { Invoke((Action)UpdateRuns); return; } SuspendRepaint(); try { UpdateResultsByTarget(); UpdateResultsByCost(); } finally { ResumeRepaint(true); } } private void UpdateBestKnownQualities() { var table = (string)dataTableComboBox.SelectedItem; if (string.IsNullOrEmpty(table)) return; var targetsPerProblem = CalculateBestTargetPerProblemInstance(table); foreach (var pd in problems) { double bkq; if (targetsPerProblem.TryGetValue(pd, out bkq)) pd.BestKnownQuality = bkq; else pd.BestKnownQuality = double.NaN; } } #endregion private void ConfigureSeries(Series series) { series.SmartLabelStyle.Enabled = showLabelsInTargetChart; series.SmartLabelStyle.AllowOutsidePlotArea = LabelOutsidePlotAreaStyle.No; series.SmartLabelStyle.CalloutLineAnchorCapStyle = LineAnchorCapStyle.None; series.SmartLabelStyle.CalloutLineColor = series.Color; series.SmartLabelStyle.CalloutLineWidth = 2; series.SmartLabelStyle.CalloutStyle = LabelCalloutStyle.Underlined; series.SmartLabelStyle.IsOverlappedHidden = false; series.SmartLabelStyle.MaxMovingDistance = 200; series.ToolTip = series.LegendText + " X = #VALX, Y = #VALY"; } private void chart_MouseDown(object sender, MouseEventArgs e) { HitTestResult result = targetChart.HitTest(e.X, e.Y); if (result.ChartElementType == ChartElementType.LegendItem) { ToggleTargetChartSeriesVisible(result.Series); } } private void chart_MouseMove(object sender, MouseEventArgs e) { HitTestResult result = targetChart.HitTest(e.X, e.Y); if (result.ChartElementType == ChartElementType.LegendItem) this.Cursor = Cursors.Hand; else this.Cursor = Cursors.Default; } private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) { foreach (LegendItem legendItem in e.LegendItems) { var series = targetChart.Series[legendItem.SeriesName]; if (series != null) { bool seriesIsInvisible = invisibleTargetSeries.Any(x => x.Name == series.Name); foreach (LegendCell cell in legendItem.Cells) { cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black; } } } } private void ToggleTargetChartSeriesVisible(Series series) { var indexList = invisibleTargetSeries.FindIndex(x => x.Name == series.Name); var indexChart = targetChart.Series.IndexOf(series); if (targetChart.Series.Count == 1) targetChart.ChartAreas[0].AxisX.IsLogarithmic = false; targetChart.Series.RemoveAt(indexChart); var s = indexList >= 0 ? invisibleTargetSeries[indexList] : new Series(series.Name) { Color = series.Color, ChartType = series.ChartType, BorderWidth = series.BorderWidth, BorderDashStyle = series.BorderDashStyle }; if (indexList < 0) { // hide invisibleTargetSeries.Add(series); var shadeSeries = targetChart.Series.FirstOrDefault(x => x.Name == series.Name + "-range"); if (shadeSeries != null) { if (targetChart.Series.Count == 1) targetChart.ChartAreas[0].AxisX.IsLogarithmic = false; targetChart.Series.Remove(shadeSeries); invisibleTargetSeries.Add(shadeSeries); indexChart--; } } else { // show invisibleTargetSeries.RemoveAt(indexList); var shadeSeries = invisibleTargetSeries.FirstOrDefault(x => x.Name == series.Name + "-range"); if (shadeSeries != null) { invisibleTargetSeries.Remove(shadeSeries); InsertOrAddSeries(indexChart, shadeSeries); indexChart++; } } InsertOrAddSeries(indexChart, s); targetChart.ChartAreas[0].AxisX.IsLogarithmic = CanDisplayLogarithmic(); } private bool CanDisplayLogarithmic() { return targetLogScalingCheckBox.Checked && targetChart.Series.Count > 0 // must have a series && targetChart.Series.Any(x => x.Points.Count > 0) // at least one series must have points && targetChart.Series.All(s => s.Points.All(p => p.XValue > 0)); // all points must be positive } private void InsertOrAddSeries(int index, Series s) { if (targetChart.Series.Count <= index) targetChart.Series.Add(s); else targetChart.Series.Insert(index, s); } private class AlgorithmInstance : INotifyPropertyChanged { private string name; public string Name { get { return name; } set { if (name == value) return; name = value; OnPropertyChanged("Name"); } } private Dictionary> performanceData; public int GetNumberOfProblemInstances() { return performanceData.Count; } public IEnumerable GetProblemInstances() { return performanceData.Keys; } public int GetNumberOfRuns(ProblemInstance p) { if (p == ProblemInstance.MatchAll) return performanceData.Select(x => x.Value.Count).Sum(); List runs; if (performanceData.TryGetValue(p, out runs)) return runs.Count; return 0; } public IEnumerable GetRuns(ProblemInstance p) { if (p == ProblemInstance.MatchAll) return performanceData.SelectMany(x => x.Value); List runs; if (performanceData.TryGetValue(p, out runs)) return runs; return Enumerable.Empty(); } public AlgorithmInstance(string name, IEnumerable runs, IEnumerable problems) { this.name = name; var pDict = problems.ToDictionary(r => r, _ => new List()); foreach (var y in runs) { var pd = new ProblemInstance(y); List l; if (pDict.TryGetValue(pd, out l)) l.Add(y); } performanceData = pDict.Where(x => x.Value.Count > 0).ToDictionary(x => x.Key, x => x.Value); } public override bool Equals(object obj) { var other = obj as AlgorithmInstance; if (other == null) return false; return name == other.name; } public override int GetHashCode() { return name.GetHashCode(); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } } private class ProblemInstance : INotifyPropertyChanged { private readonly bool matchAll; public static readonly ProblemInstance MatchAll = new ProblemInstance() { ProblemName = "All with Best-Known" }; private ProblemInstance() { ProblemType = string.Empty; ProblemName = string.Empty; Evaluator = string.Empty; Maximization = string.Empty; DisplayProblemType = false; DisplayProblemName = false; DisplayEvaluator = false; DisplayMaximization = false; matchAll = true; BestKnownQuality = double.NaN; } public ProblemInstance(IRun run) { ProblemType = GetStringValueOrEmpty(run, "Problem Type"); ProblemName = GetStringValueOrEmpty(run, "Problem Name"); Evaluator = GetStringValueOrEmpty(run, "Evaluator"); Maximization = GetMaximizationValueOrEmpty(run, "Maximization"); DisplayProblemType = !string.IsNullOrEmpty(ProblemType); DisplayProblemName = !string.IsNullOrEmpty(ProblemName); DisplayEvaluator = !string.IsNullOrEmpty(Evaluator); DisplayMaximization = !string.IsNullOrEmpty(Maximization); matchAll = false; BestKnownQuality = GetDoubleValueOrNaN(run, "BestKnownQuality"); } private bool displayProblemType; public bool DisplayProblemType { get { return displayProblemType; } set { if (displayProblemType == value) return; displayProblemType = value; OnPropertyChanged("DisplayProblemType"); } } private string problemType; public string ProblemType { get { return problemType; } set { if (problemType == value) return; problemType = value; OnPropertyChanged("ProblemType"); } } private bool displayProblemName; public bool DisplayProblemName { get { return displayProblemName; } set { if (displayProblemName == value) return; displayProblemName = value; OnPropertyChanged("DisplayProblemName"); } } private string problemName; public string ProblemName { get { return problemName; } set { if (problemName == value) return; problemName = value; OnPropertyChanged("ProblemName"); } } private bool displayEvaluator; public bool DisplayEvaluator { get { return displayEvaluator; } set { if (displayEvaluator == value) return; displayEvaluator = value; OnPropertyChanged("DisplayEvaluator"); } } private string evaluator; public string Evaluator { get { return evaluator; } set { if (evaluator == value) return; evaluator = value; OnPropertyChanged("Evaluator"); } } private bool displayMaximization; public bool DisplayMaximization { get { return displayMaximization; } set { if (displayMaximization == value) return; displayMaximization = value; OnPropertyChanged("DisplayMaximization"); } } private string maximization; public string Maximization { get { return maximization; } set { if (maximization == value) return; maximization = value; OnPropertyChanged("Maximization"); } } private double bestKnownQuality; public double BestKnownQuality { get { return bestKnownQuality; } set { if (bestKnownQuality == value) return; bestKnownQuality = value; OnPropertyChanged("BestKnownQuality"); } } public bool IsMaximization() { return Maximization == "MAX"; } public bool Match(IRun run) { return matchAll || GetStringValueOrEmpty(run, "Problem Type") == ProblemType && GetStringValueOrEmpty(run, "Problem Name") == ProblemName && GetStringValueOrEmpty(run, "Evaluator") == Evaluator && GetMaximizationValueOrEmpty(run, "Maximization") == Maximization; } private double GetDoubleValueOrNaN(IRun run, string key) { IItem param; if (run.Parameters.TryGetValue(key, out param)) { var dv = param as DoubleValue; return dv != null ? dv.Value : double.NaN; } return double.NaN; } private string GetStringValueOrEmpty(IRun run, string key) { IItem param; if (run.Parameters.TryGetValue(key, out param)) { var sv = param as StringValue; return sv != null ? sv.Value : string.Empty; } return string.Empty; } private string GetMaximizationValueOrEmpty(IRun run, string key) { IItem param; if (run.Parameters.TryGetValue(key, out param)) { var bv = param as BoolValue; return bv != null ? (bv.Value ? "MAX" : "MIN") : string.Empty; } return string.Empty; } public override bool Equals(object obj) { var other = obj as ProblemInstance; if (other == null) return false; return ProblemType == other.ProblemType && ProblemName == other.ProblemName && Evaluator == other.Evaluator && Maximization == other.Maximization; } public override int GetHashCode() { return ProblemType.GetHashCode() ^ ProblemName.GetHashCode() ^ Evaluator.GetHashCode() ^ Maximization.GetHashCode(); } public override string ToString() { return string.Join(" -- ", new[] { (DisplayProblemType ? ProblemType : string.Empty), (DisplayProblemName ? ProblemName : string.Empty), (DisplayEvaluator ? Evaluator : string.Empty), (DisplayMaximization ? Maximization : string.Empty), !double.IsNaN(BestKnownQuality) ? BestKnownQuality.ToString(CultureInfo.CurrentCulture.NumberFormat) : string.Empty }.Where(x => !string.IsNullOrEmpty(x))); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } } private class CostComparer : Comparer> { public override int Compare(Tuple x, Tuple y) { return x.Item1.CompareTo(y.Item1); } } private class TargetComparer : Comparer> { public bool Maximization { get; private set; } public TargetComparer(bool maximization) { Maximization = maximization; } public override int Compare(Tuple x, Tuple y) { return Maximization ? x.Item2.CompareTo(y.Item2) : y.Item2.CompareTo(x.Item2); } } } }