#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 HEAL.Attic;
using HeuristicLab.Analysis;
using HeuristicLab.Common;
using HeuristicLab.Core;
using HeuristicLab.Data;
using HeuristicLab.Optimization;
using HeuristicLab.Problems.DataAnalysis;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace HeuristicLab.GoalSeeking {
internal enum GoalSeekingOptimizerAction { None, Prepare, Start, Stop, Pause }
[Item("GoalSeeking Optimizer", "A wrapper for the GoalSeekingProblem class to facilitate adding models and problem data.")]
[Creatable("Algorithms")]
[StorableType("92552DF1-7FC7-4DC9-A021-8661E878A72B")]
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 optimizerAction;
public GoalSeekingOptimizer() {
Name = "Goal Seeking Optimizer";
}
[StorableConstructor]
protected GoalSeekingOptimizer(StorableConstructorFlag _) : base(_) { }
[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;
private void PrepareAndConfigure() {
Optimizer.Prepare(clearRuns: true);
calculatedRows = 0;
currentRow = problemData.TrainingIndices.First();
problem.Configure(problemData, currentRow);
}
public void Start() {
Start(CancellationToken.None);
}
public void Start(CancellationToken cancellationToken) {
if ((ExecutionState != ExecutionState.Prepared) && (ExecutionState != ExecutionState.Paused))
throw new InvalidOperationException(string.Format("Start not allowed in execution state \"{0}\".", ExecutionState));
if (optimizer == null) return;
optimizerAction = GoalSeekingOptimizerAction.Start;
if (Optimizer.ExecutionState == ExecutionState.Stopped) {
PrepareAndConfigure();
}
Optimizer.Start(cancellationToken);
}
public async Task StartAsync() {
await StartAsync(CancellationToken.None);
}
public async Task StartAsync(CancellationToken cancellationToken) {
await AsyncHelper.DoAsync(Start, cancellationToken);
}
public void Pause() {
if (ExecutionState != ExecutionState.Started)
throw new InvalidOperationException(string.Format("Pause not allowed in execution state \"{0}\".", ExecutionState));
if (optimizer == null) return;
optimizerAction = 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;
optimizerAction = 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();
optimizerAction = 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() {
optimizerAction = 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() {
optimizerAction = GoalSeekingOptimizerAction.None;
ExecutionState = ExecutionState.Paused;
EventHandler handler = Paused;
if (handler != null) handler(this, EventArgs.Empty);
}
public event EventHandler Stopped;
private void OnStopped() {
optimizerAction = 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 RepetitionsCounterChanged;
private void OnRepetitionsCounterChanged() {
EventHandler handler = RepetitionsCounterChanged;
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 (optimizerAction == 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 (optimizerAction == GoalSeekingOptimizerAction.Stop)
OnStopped();
else if (!remainingRows.Any()) {
OnStopped();
} else switch (optimizerAction) {
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
}
}