#region License Information /* HeuristicLab * Copyright (C) 2002-2016 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.Drawing; using System.Linq; using HeuristicLab.Analysis; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Optimization; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using HeuristicLab.Problems.DataAnalysis; namespace HeuristicLab.GoalSeeking { internal enum GoalSeekingOptimizerAction { None, Prepare, Start, Stop, Pause } [StorableClass] [Item("GoalSeeking Optimizer", "A wrapper for the GoalSeekingProblem class to facilitate adding models and problem data.")] [Creatable("Algorithms")] public class GoalSeekingOptimizer : NamedItem, IOptimizer, IStorableContent { [Storable] private IGoalSeekingProblem problem; public IGoalSeekingProblem Problem { get { return problem; } set { if (value == null || value == problem) return; problem = value; // the problem takes precedence over the algorithm. if the algorithm is unsuitable we remove it. if (optimizer != null && !optimizer.ProblemType.IsInstanceOfType(problem)) { optimizer = null; OnAlgorithmChanged(); } if (optimizer != null) optimizer.Problem = problem; } } [Storable] private IAlgorithm optimizer; public IAlgorithm Optimizer { get { return optimizer; } set { if (value == null || value == optimizer) return; // make sure the algorithm is suitable for the problem. if (problem != null && !value.ProblemType.IsInstanceOfType(problem)) throw new InvalidOperationException("The algorithm is incompatible with the problem."); optimizer = value; if (problem != null) optimizer.Problem = problem; RegisterOptimizerEvents(); OnAlgorithmChanged(); } } [Storable] private IRegressionProblemData problemData; public IRegressionProblemData ProblemData { get { return problemData; } set { if (value == null || value == problemData) return; if (problem == null) throw new InvalidOperationException("Cannot set problem data. Please configure a problem first."); problemData = value; problem.Configure(problemData, problemData.TrainingIndices.First()); OnProblemDataChanged(); } } [Storable] private ResultCollection results; public ResultCollection Results { get { return results; } private set { if (value != null) results = value; OnResultsChanged(); } } private GoalSeekingOptimizerAction gsoAction; public GoalSeekingOptimizer() { Name = "Goal Seeking Optimizer"; } [StorableConstructor] protected GoalSeekingOptimizer(bool deserializing) : base(deserializing) { } [StorableHook(HookType.AfterDeserialization)] private void AfterDeserialization() { if (optimizer != null) RegisterOptimizerEvents(); } protected GoalSeekingOptimizer(GoalSeekingOptimizer original, Cloner cloner) : base(original, cloner) { this.Optimizer = (IAlgorithm)original.Optimizer.Clone(cloner); this.Problem = (IGoalSeekingProblem)original.Problem.Clone(cloner); this.ProblemData = (IRegressionProblemData)original.ProblemData.Clone(cloner); this.Results = (ResultCollection)original.Results.Clone(); } public override IDeepCloneable Clone(Cloner cloner) { return new GoalSeekingOptimizer(this, cloner); } public EventHandler ProblemChanged; private void OnProblemChanged() { var changed = ProblemChanged; if (changed != null) changed(this, EventArgs.Empty); } public EventHandler AlgorithmChanged; private void OnAlgorithmChanged() { var changed = AlgorithmChanged; if (changed != null) changed(this, EventArgs.Empty); } public EventHandler ProblemDataChanged; private void OnProblemDataChanged() { var changed = ProblemDataChanged; if (changed != null) changed(this, EventArgs.Empty); } public EventHandler ResultsChanged; private void OnResultsChanged() { var changed = ResultsChanged; if (changed != null) changed(this, EventArgs.Empty); } private int calculatedRows; private int currentRow; public void Start() { if ((ExecutionState != ExecutionState.Prepared) && (ExecutionState != ExecutionState.Paused)) throw new InvalidOperationException(string.Format("Start not allowed in execution state \"{0}\".", ExecutionState)); if (optimizer == null) return; gsoAction = GoalSeekingOptimizerAction.Start; if (Optimizer.ExecutionState == ExecutionState.Stopped) { Optimizer.Prepare(clearRuns: true); calculatedRows = 0; currentRow = problemData.TrainingIndices.Skip(calculatedRows).First(); problem.Configure(problemData, currentRow); optimizer.Prepare(); } optimizer.Start(); } public void Pause() { if (ExecutionState != ExecutionState.Started) throw new InvalidOperationException(string.Format("Pause not allowed in execution state \"{0}\".", ExecutionState)); if (optimizer == null) return; gsoAction = GoalSeekingOptimizerAction.Pause; if (optimizer.ExecutionState != ExecutionState.Started) return; // a race-condition may occur when the algorithm has changed the state by itself in the meantime try { optimizer.Pause(); } catch (InvalidOperationException) { } } public void Stop() { if ((ExecutionState != ExecutionState.Started) && (ExecutionState != ExecutionState.Paused)) throw new InvalidOperationException(string.Format("Stop not allowed in execution state \"{0}\".", ExecutionState)); if (optimizer == null) return; gsoAction = GoalSeekingOptimizerAction.Stop; if (optimizer.ExecutionState != ExecutionState.Started && optimizer.ExecutionState != ExecutionState.Paused) { OnStopped(); return; } // a race-condition may occur when the algorithm has changed the state by itself in the meantime try { optimizer.Stop(); } catch (InvalidOperationException) { } } public void Prepare() { Prepare(false); } public void Prepare(bool clearRuns) { if ((ExecutionState != ExecutionState.Prepared) && (ExecutionState != ExecutionState.Paused) && (ExecutionState != ExecutionState.Stopped)) throw new InvalidOperationException(string.Format("Prepare not allowed in execution state \"{0}\".", ExecutionState)); if (Results != null) Results.Clear(); if (optimizer != null) { ExecutionTime = TimeSpan.Zero; if (clearRuns) optimizer.Runs.Clear(); gsoAction = GoalSeekingOptimizerAction.Prepare; // a race-condition may occur when the algorithm has changed the state by itself in the meantime try { optimizer.Prepare(clearRuns); } catch (InvalidOperationException) { } } else { ExecutionState = ExecutionState.Stopped; } } [Storable] private TimeSpan executionTime; public TimeSpan ExecutionTime { get { if ((Optimizer != null) && (Optimizer.ExecutionState != ExecutionState.Stopped)) return executionTime + Optimizer.ExecutionTime; else return executionTime; } private set { executionTime = value; OnExecutionTimeChanged(); } } public RunCollection Runs { get { if (optimizer == null) return new RunCollection(); // return an empty run collection return Optimizer.Runs; } } public IEnumerable NestedOptimizers { get; private set; } public string Filename { get; set; } public new static Image StaticItemImage { get { return HeuristicLab.Common.Resources.VSImageLibrary.Event; } } public override Image ItemImage { get { if (ExecutionState == ExecutionState.Prepared) return HeuristicLab.Common.Resources.VSImageLibrary.BatchRunPrepared; else if (ExecutionState == ExecutionState.Started) return HeuristicLab.Common.Resources.VSImageLibrary.BatchRunStarted; else if (ExecutionState == ExecutionState.Paused) return HeuristicLab.Common.Resources.VSImageLibrary.BatchRunPaused; else if (ExecutionState == ExecutionState.Stopped) return HeuristicLab.Common.Resources.VSImageLibrary.BatchRunStopped; else return base.ItemImage; } } #region Events protected override void OnNameChanged() { base.OnNameChanged(); } #region event firing public ExecutionState ExecutionState { get; private set; } public event EventHandler ExecutionStateChanged; private void OnExecutionStateChanged() { EventHandler handler = ExecutionStateChanged; if (handler != null) handler(this, EventArgs.Empty); } public event EventHandler ExecutionTimeChanged; private void OnExecutionTimeChanged() { EventHandler handler = ExecutionTimeChanged; if (handler != null) handler(this, EventArgs.Empty); } public event EventHandler Prepared; private void OnPrepared() { gsoAction = GoalSeekingOptimizerAction.None; ExecutionState = ExecutionState.Prepared; EventHandler handler = Prepared; if (handler != null) handler(this, EventArgs.Empty); } public event EventHandler Started; private void OnStarted() { ExecutionState = ExecutionState.Started; EventHandler handler = Started; if (handler != null) handler(this, EventArgs.Empty); } public event EventHandler Paused; private void OnPaused() { gsoAction = GoalSeekingOptimizerAction.None; ExecutionState = ExecutionState.Paused; EventHandler handler = Paused; if (handler != null) handler(this, EventArgs.Empty); } public event EventHandler Stopped; private void OnStopped() { gsoAction = GoalSeekingOptimizerAction.None; ExecutionState = ExecutionState.Stopped; EventHandler handler = Stopped; if (handler != null) handler(this, EventArgs.Empty); } public event EventHandler> ExceptionOccurred; private void OnExceptionOccurred(Exception exception) { EventHandler> handler = ExceptionOccurred; if (handler != null) handler(this, new EventArgs(exception)); } public event EventHandler OptimizerChanged; private void OnOptimizerChanged() { EventHandler handler = OptimizerChanged; if (handler != null) handler(this, EventArgs.Empty); } public event EventHandler RepetitionsChanged; private void OnRepetitionsChanged() { EventHandler handler = RepetitionsChanged; if (handler != null) handler(this, EventArgs.Empty); } public event EventHandler RepetetionsCounterChanged; private void OnRepetitionsCounterChanged() { EventHandler handler = RepetetionsCounterChanged; if (handler != null) handler(this, EventArgs.Empty); } #endregion #region optimizer event handlers private void RegisterOptimizerEvents() { optimizer.ExceptionOccurred += Optimizer_ExceptionOccurred; optimizer.ExecutionTimeChanged += Optimizer_ExecutionTimeChanged; optimizer.Paused += Optimizer_Paused; optimizer.Prepared += Optimizer_Prepared; optimizer.Started += Optimizer_Started; optimizer.Stopped += Optimizer_Stopped; } private void DeregisterOptimizerEvents() { optimizer.ExceptionOccurred -= Optimizer_ExceptionOccurred; optimizer.ExecutionTimeChanged -= Optimizer_ExecutionTimeChanged; optimizer.Paused -= Optimizer_Paused; optimizer.Prepared -= Optimizer_Prepared; optimizer.Started -= Optimizer_Started; optimizer.Stopped -= Optimizer_Stopped; } private void Optimizer_ExceptionOccurred(object sender, EventArgs e) { OnExceptionOccurred(e.Value); } private void Optimizer_ExecutionTimeChanged(object sender, EventArgs e) { OnExecutionTimeChanged(); } private void Optimizer_Paused(object sender, EventArgs e) { if (ExecutionState == ExecutionState.Started) { OnPaused(); } } private void Optimizer_Prepared(object sender, EventArgs e) { if (gsoAction == GoalSeekingOptimizerAction.Prepare || ExecutionState == ExecutionState.Stopped) { calculatedRows = 0; ExecutionTime = TimeSpan.Zero; OnPrepared(); } } private void Optimizer_Started(object sender, EventArgs e) { if (ExecutionState != ExecutionState.Started) { OnStarted(); } } private void Optimizer_Stopped(object sender, EventArgs e) { calculatedRows++; ExecutionTime += optimizer.ExecutionTime; var remainingRows = problemData.TrainingIndices.Skip(calculatedRows); // extract results from the optimizer GetResults(); if (gsoAction == GoalSeekingOptimizerAction.Stop) OnStopped(); else if (!remainingRows.Any()) { OnStopped(); } else switch (gsoAction) { case GoalSeekingOptimizerAction.Pause: OnPaused(); break; case GoalSeekingOptimizerAction.Start: currentRow = remainingRows.First(); problem.Configure(problemData, currentRow); optimizer.Prepare(); optimizer.Start(); break; default: if (ExecutionState == ExecutionState.Started) { OnPaused(); } else OnStopped(); break; } } #endregion #endregion #region aggregate results private void GetResults() { if (Problem is MultiObjectiveGoalSeekingProblem) { var m = (DoubleMatrix)optimizer.Results["Pareto Front Solutions"].Value; AggregateMultiObjectiveResults(m); } else if (Problem is SingleObjectiveGoalSeekingProblem) { var m = (DoubleMatrix)optimizer.Results["Best Solution"].Value; AggregateSingleObjectiveResults(m); } } private void AggregateSingleObjectiveResults(DoubleMatrix solutions) { var indices = solutions.ColumnNames.Select((x, i) => Tuple.Create(x, i)).ToDictionary(x => x.Item1, x => x.Item2); var goals = Problem.Goals.Where(x => x.Active).ToList(); var inputs = Problem.Inputs.Where(x => x.Active).ToList(); if (Results == null) Results = new ResultCollection(); // goals if (!Results.ContainsKey("Goals")) Results.Add(new Result("Goals", new ResultCollection())); var goalResults = (ResultCollection)Results["Goals"].Value; foreach (var goal in goals) { var estimatedGoalName = goal.Name + " (estimated)"; if (!goalResults.ContainsKey(goal.Name)) { goalResults.Add(new Result(goal.Name, new ScatterPlot(goal.Name, ""))); } var plot = (ScatterPlot)goalResults[goal.Name].Value; if (!plot.Rows.ContainsKey(goal.Name)) { plot.Rows.Add(new ScatterPlotDataRow { Name = goal.Name }); plot.Rows.Add(new ScatterPlotDataRow { Name = estimatedGoalName }); } plot.Rows[goal.Name].Points.Add(new Point2D(currentRow, solutions[0, indices[goal.Name]])); plot.Rows[estimatedGoalName].Points.Add(new Point2D(currentRow, solutions[0, indices[estimatedGoalName]])); } // inputs if (!Results.ContainsKey("Inputs")) Results.Add(new Result("Inputs", new ResultCollection())); var inputResults = (ResultCollection)Results["Inputs"].Value; foreach (var input in inputs) { var estimatedInputName = input.Name + " (estimated)"; if (!inputResults.ContainsKey(input.Name)) { inputResults.Add(new Result(input.Name, new ScatterPlot(input.Name, ""))); } var plot = (ScatterPlot)inputResults[input.Name].Value; if (!plot.Rows.ContainsKey(input.Name)) { plot.Rows.Add(new ScatterPlotDataRow { Name = input.Name }); plot.Rows.Add(new ScatterPlotDataRow { Name = estimatedInputName }); } plot.Rows[input.Name].Points.Add(new Point2D(currentRow, solutions[0, indices[input.Name]])); plot.Rows[estimatedInputName].Points.Add(new Point2D(currentRow, solutions[0, indices[estimatedInputName]])); } // aggregate results as a matrix UpdateResultsMatrix(solutions); } private void AggregateMultiObjectiveResults(DoubleMatrix solutions) { int maxSolutionsCount = Math.Min(solutions.Rows, 10); var indices = solutions.ColumnNames.Select((x, i) => Tuple.Create(x, i)).ToDictionary(x => x.Item1, x => x.Item2); var goals = Problem.Goals.Where(x => x.Active).ToList(); var inputs = Problem.Inputs.Where(x => x.Active).ToList(); if (Results == null) Results = new ResultCollection(); // goals if (!Results.ContainsKey("Goals")) Results.Add(new Result("Goals", new ResultCollection())); var goalResults = (ResultCollection)Results["Goals"].Value; foreach (var goal in goals) { var estimatedGoalName = goal.Name + " (estimated)"; if (!goalResults.ContainsKey(goal.Name)) { goalResults.Add(new Result(goal.Name, new ScatterPlot(goal.Name, ""))); } var plot = (ScatterPlot)goalResults[goal.Name].Value; if (!plot.Rows.ContainsKey(goal.Name)) { plot.Rows.Add(new ScatterPlotDataRow { Name = goal.Name, VisualProperties = { PointSize = 5 } }); } plot.Rows[goal.Name].Points.Add(new Point2D(currentRow, solutions[0, indices[goal.Name]])); for (int i = 0; i < maxSolutionsCount; ++i) { if (!plot.Rows.ContainsKey(estimatedGoalName + i)) plot.Rows.Add(new ScatterPlotDataRow { Name = estimatedGoalName + i, VisualProperties = { PointSize = 5 } }); plot.Rows[estimatedGoalName + i].Points.Add(new Point2D(currentRow, solutions[i, indices[estimatedGoalName]])); } } // inputs if (!Results.ContainsKey("Inputs")) Results.Add(new Result("Inputs", new ResultCollection())); var inputResults = (ResultCollection)Results["Inputs"].Value; foreach (var input in inputs) { var estimatedInputName = input.Name + " (estimated)"; if (!inputResults.ContainsKey(input.Name)) { inputResults.Add(new Result(input.Name, new ScatterPlot(input.Name, ""))); } var plot = (ScatterPlot)inputResults[input.Name].Value; if (!plot.Rows.ContainsKey(input.Name)) { plot.Rows.Add(new ScatterPlotDataRow { Name = input.Name, VisualProperties = { PointSize = 5 } }); } plot.Rows[input.Name].Points.Add(new Point2D(currentRow, solutions[0, indices[input.Name]])); // it stays the same for all the rows for (int i = 0; i < maxSolutionsCount; ++i) { if (!plot.Rows.ContainsKey(estimatedInputName + i)) plot.Rows.Add(new ScatterPlotDataRow { Name = estimatedInputName + i, VisualProperties = { PointSize = 5 } }); plot.Rows[estimatedInputName + i].Points.Add(new Point2D(currentRow, solutions[i, indices[estimatedInputName]])); } } // also add a matrix containing the whole solution data including the row UpdateResultsMatrix(solutions); } private void UpdateResultsMatrix(DoubleMatrix solutions) { // also add a matrix containing the whole solution data including the row var rows = problemData.TrainingIndices.Take(calculatedRows).ToList(); var m = new DoubleMatrix(calculatedRows, solutions.Columns + 1) { RowNames = rows.Select(x => x.ToString()), ColumnNames = new[] { "Row" }.Concat(solutions.ColumnNames) }; DoubleMatrix old = null; if (Results.ContainsKey("Solutions")) old = (DoubleMatrix)Results["Solutions"].Value; else Results.Add(new Result("Solutions", m)); if (old != null && calculatedRows > 0) { for (int i = 0; i < old.Rows; ++i) { for (int j = 0; j < old.Columns; ++j) { m[i, j] = old[i, j]; } } } m[rows.Count - 1, 0] = currentRow; for (int i = 1; i < m.Columns; ++i) { m[rows.Count - 1, i] = solutions[0, i - 1]; } Results["Solutions"].Value = m; } #endregion } }