#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.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using HEAL.Attic;
using HeuristicLab.Algorithms.DataAnalysis;
using HeuristicLab.Analysis;
using HeuristicLab.Analysis.SelfOrganizingMaps;
using HeuristicLab.Collections;
using HeuristicLab.Common;
using HeuristicLab.Common.Resources;
using HeuristicLab.Core;
using HeuristicLab.Data;
using HeuristicLab.MainForm;
using HeuristicLab.Optimization;
using HeuristicLab.Persistence.Default.Xml;
using HeuristicLab.Problems.DataAnalysis;
using HeuristicLab.Random;
using Algorithm = HeuristicLab.Clients.OKB.Administration.Algorithm;
using Problem = HeuristicLab.Clients.OKB.Administration.Problem;
using RunCreationClient = HeuristicLab.Clients.OKB.RunCreation.RunCreationClient;
using SingleObjectiveOKBProblem = HeuristicLab.Clients.OKB.RunCreation.SingleObjectiveOKBProblem;
using SingleObjectiveOKBSolution = HeuristicLab.Clients.OKB.RunCreation.SingleObjectiveOKBSolution;
namespace HeuristicLab.OptimizationExpertSystem.Common {
[Item("Knowledge Center", "Currently in experimental phase, an expert system that makes algorithm suggestions based on fitness landscape analysis features and an optimization knowledge base.")]
[Creatable(CreatableAttribute.Categories.TestingAndAnalysis, Priority = 119)]
public sealed class KnowledgeCenter : IContent {
private bool SuppressEvents { get; set; }
public string Filename { get; set; }
public static Image StaticItemImage {
get { return VSImageLibrary.Library; }
}
private readonly IntValue maximumEvaluations;
public IntValue MaximumEvaluations {
get { return maximumEvaluations; }
}
private readonly DoubleValue minimumTarget;
public DoubleValue MinimumTarget {
get { return minimumTarget; }
}
private readonly RunCollection instanceRuns;
public RunCollection InstanceRuns {
get { return instanceRuns; }
}
private readonly RunCollection seededRuns;
public RunCollection SeededRuns {
get { return seededRuns; }
}
private readonly RunCollection knowledgeBase;
public RunCollection KnowledgeBase {
get { return knowledgeBase; }
}
private readonly SingleObjectiveOKBProblem problem;
public SingleObjectiveOKBProblem Problem {
get { return problem; }
}
private readonly ItemList algorithmInstances;
private readonly ReadOnlyItemList readonlyAlgorithmInstances;
public ReadOnlyItemList AlgorithmInstances {
get { return readonlyAlgorithmInstances; }
}
private readonly RunCollection problemInstances;
public RunCollection ProblemInstances {
get { return problemInstances; }
}
private IRecommendationModel recommendationModel;
public IRecommendationModel RecommendationModel {
get { return recommendationModel; }
set {
if (recommendationModel == value) return;
recommendationModel = value;
OnRecommenderModelChanged();
}
}
private readonly CheckedItemList solutionSeedingPool;
public CheckedItemList SolutionSeedingPool {
get { return solutionSeedingPool; }
}
private readonly EnumValue seedingStrategy;
public EnumValue SeedingStrategy {
get { return seedingStrategy; }
}
private BidirectionalLookup algorithmId2RunMapping;
private BidirectionalDictionary algorithmId2AlgorithmInstanceMapping;
private BidirectionalDictionary problemId2ProblemInstanceMapping;
public bool Maximization {
get { return Problem != null && Problem.ProblemId >= 0 && ((IValueParameter)Problem.MaximizationParameter).Value.Value; }
}
public KnowledgeCenter() {
maximumEvaluations = new IntValue(10000);
minimumTarget = new DoubleValue(0.05);
instanceRuns = new RunCollection();
seededRuns = new RunCollection();
knowledgeBase = new RunCollection();
algorithmInstances = new ItemList();
readonlyAlgorithmInstances = algorithmInstances.AsReadOnly();
problemInstances = new RunCollection();
recommendationModel = FixedRankModel.GetEmpty();
problem = new SingleObjectiveOKBProblem();
algorithmId2RunMapping = new BidirectionalLookup();
algorithmId2AlgorithmInstanceMapping = new BidirectionalDictionary();
problemId2ProblemInstanceMapping = new BidirectionalDictionary();
solutionSeedingPool = new CheckedItemList();
seedingStrategy = new EnumValue(SeedingStrategyTypes.NoSeeding);
RegisterEventHandlers();
}
private void ProblemOnProblemChanged(object sender, EventArgs eventArgs) {
// TODO: Potentially, knowledge base has to be re-downloaded
}
private void RegisterEventHandlers() {
maximumEvaluations.ValueChanged += MaximumEvaluationsOnValueChanged;
minimumTarget.ValueChanged += MinimumTargetOnValueChanged;
problem.ProblemChanged += ProblemOnProblemChanged;
problem.Solutions.ItemsAdded += ProblemSolutionsChanged;
problem.Solutions.ItemsReplaced += ProblemSolutionsChanged;
problem.Solutions.ItemsRemoved += ProblemSolutionsChanged;
problem.Solutions.CollectionReset += ProblemSolutionsChanged;
instanceRuns.CollectionReset += InformationChanged;
instanceRuns.ItemsAdded += InformationChanged;
instanceRuns.ItemsRemoved += InformationChanged;
instanceRuns.Reset += InformationChanged;
instanceRuns.UpdateOfRunsInProgressChanged += InformationChanged;
knowledgeBase.CollectionReset += InformationChanged;
knowledgeBase.ItemsAdded += InformationChanged;
knowledgeBase.ItemsRemoved += InformationChanged;
}
private void MaximumEvaluationsOnValueChanged(object sender, EventArgs eventArgs) {
}
private void MinimumTargetOnValueChanged(object sender, EventArgs e) {
}
private void ProblemSolutionsChanged(object sender, EventArgs e) {
foreach (var sol in Problem.Solutions.Select(x => x.Solution).OfType()) {
if (!SolutionSeedingPool.Contains(sol))
SolutionSeedingPool.Add(sol, false);
}
}
private void InformationChanged(object sender, EventArgs e) {
var runCollection = sender as RunCollection;
if (runCollection != null && runCollection.UpdateOfRunsInProgress) return;
}
public bool IsCurrentInstance(IRun run) {
if (!problemId2ProblemInstanceMapping.ContainsSecond(run)) return false;
return problemId2ProblemInstanceMapping.GetBySecond(run) == Problem.ProblemId;
}
public void UpdateInstanceProjection(string[] characteristics) {
if (characteristics.Length == 0) return;
var instances = GetProblemCharacteristics(characteristics);
var key2Idx = new BidirectionalDictionary();
foreach (var kvp in instances.Select((k, i) => new { Index = i, Key = k.Key }))
key2Idx.Add(kvp.Key, kvp.Index);
Func euclid = (a, b) => Math.Sqrt(a.Zip(b, (x, y) => (x - y)).Sum(x => x * x));
Func euclidDArray = (a, b) => Math.Sqrt(a.Zip(b, (x, y) => (x - y)).Sum(x => x * x));
#region MDS
var num = instances.Count;
var matrix = new DoubleMatrix(num, num);
for (var i = 0; i < num - 1; i++) {
for (var j = i + 1; j < num; j++) {
matrix[i, j] = matrix[j, i] = euclid(instances[key2Idx.GetBySecond(i)], instances[key2Idx.GetBySecond(j)]);
}
}
var coords = MultidimensionalScaling.KruskalShepard(matrix);
#endregion
#region PCA
double[,] v = null;
var ds = new double[instances.Count, characteristics.Length];
if (characteristics.Length > 1) {
foreach (var instance in instances) {
var arr = instance.Value;
for (var feature = 0; feature < arr.Length; feature++)
ds[key2Idx.GetByFirst(instance.Key), feature] = arr[feature];
}
int info;
double[] s2;
alglib.pcabuildbasis(ds, ds.GetLength(0), ds.GetLength(1), out info, out s2, out v);
}
#endregion
#region SOM
var features = new DoubleMatrix(characteristics.Length, instances.Count);
foreach (var instance in instances) {
var arr = instance.Value;
for (var feature = 0; feature < arr.Length; feature++)
features[feature, key2Idx.GetByFirst(instance.Key)] = arr[feature];
}
var somCoords = SOM.Map(features, new MersenneTwister(42), somSize: 10, learningRadius: 20, iterations: 200, jittering: true);
#endregion
#region TSNE
var tsneFeatures = new DoubleArray[instances.Count];
foreach (var instance in instances) {
tsneFeatures[key2Idx.GetByFirst(instance.Key)] = new DoubleArray(instance.Value);
}
var tsneCoords = TSNEStatic.Run(tsneFeatures, new EuclideanDistance(), new FastRandom(42),
newDimensions: 2, perplexity: Math.Min((instances.Count - 1) / 4, 50), theta: 0,
stopLyingIter: 0, momSwitchIter: 0, momentum: 0, finalMomentum: 0, eta: 10);
#endregion
ProblemInstances.UpdateOfRunsInProgress = true;
try {
foreach (var instance in ProblemInstances) {
IItem item;
if (v != null) {
double x = 0, y = 0;
for (var feature = 0; feature < ds.GetLength(1); feature++) {
x += ds[key2Idx.GetByFirst(instance), feature] * v[feature, 0];
y += ds[key2Idx.GetByFirst(instance), feature] * v[feature, 1];
}
if (instance.Results.TryGetValue("Projection.PCA.X", out item)) {
((DoubleValue)item).Value = x;
} else instance.Results.Add("Projection.PCA.X", new DoubleValue(x));
if (instance.Results.TryGetValue("Projection.PCA.Y", out item)) {
((DoubleValue)item).Value = y;
} else instance.Results.Add("Projection.PCA.Y", new DoubleValue(y));
} else {
instance.Results.Remove("Projection.PCA.X");
instance.Results.Remove("Projection.PCA.Y");
}
if (instance.Results.TryGetValue("Projection.MDS.X", out item)) {
((DoubleValue)item).Value = coords[key2Idx.GetByFirst(instance), 0];
} else instance.Results.Add("Projection.MDS.X", new DoubleValue(coords[key2Idx.GetByFirst(instance), 0]));
if (instance.Results.TryGetValue("Projection.MDS.Y", out item)) {
((DoubleValue)item).Value = coords[key2Idx.GetByFirst(instance), 1];
} else instance.Results.Add("Projection.MDS.Y", new DoubleValue(coords[key2Idx.GetByFirst(instance), 1]));
if (instance.Results.TryGetValue("Projection.SOM.X", out item)) {
((DoubleValue)item).Value = somCoords[key2Idx.GetByFirst(instance), 0];
} else instance.Results.Add("Projection.SOM.X", new DoubleValue(somCoords[key2Idx.GetByFirst(instance), 0]));
if (instance.Results.TryGetValue("Projection.SOM.Y", out item)) {
((DoubleValue)item).Value = somCoords[key2Idx.GetByFirst(instance), 1];
} else instance.Results.Add("Projection.SOM.Y", new DoubleValue(somCoords[key2Idx.GetByFirst(instance), 1]));
if (instance.Results.TryGetValue("Projection.TSNE.X", out item)) {
((DoubleValue)item).Value = tsneCoords[key2Idx.GetByFirst(instance), 0];
} else instance.Results.Add("Projection.TSNE.X", new DoubleValue(tsneCoords[key2Idx.GetByFirst(instance), 0]));
if (instance.Results.TryGetValue("Projection.TSNE.Y", out item)) {
((DoubleValue)item).Value = tsneCoords[key2Idx.GetByFirst(instance), 1];
} else instance.Results.Add("Projection.TSNE.Y", new DoubleValue(tsneCoords[key2Idx.GetByFirst(instance), 1]));
}
} finally { ProblemInstances.UpdateOfRunsInProgress = false; }
}
private static readonly HashSet InterestingValueNames = new HashSet() {
"QualityPerEvaluations", "QualityPerClock", "Problem Name", "Problem Type", "Algorithm Name", "Algorithm Type", "Maximization", "BestKnownQuality"
};
public Task StartAlgorithmAsync(int index) {
return StartAlgorithmAsync(index, CancellationToken.None);
}
public Task StartAlgorithmAsync(int index, CancellationToken cancellation) {
var selectedInstance = algorithmInstances[index];
var algorithmClone = (IAlgorithm)selectedInstance.Clone();
var problemClone = Problem.CloneProblem() as ISingleObjectiveHeuristicOptimizationProblem;
if (problemClone == null) throw new InvalidOperationException("Problem is not of type " + typeof(ISingleObjectiveHeuristicOptimizationProblem).FullName);
// TODO: It is assumed the problem instance by default is configured using no preexisting solution creator
var seedingStrategyLocal = SeedingStrategy.Value;
if (seedingStrategyLocal != SeedingStrategyTypes.NoSeeding) {
if (!SolutionSeedingPool.CheckedItems.Any()) throw new InvalidOperationException("There are no solutions selected for seeding.");
// TODO: It would be necessary to specify the solution creator somewhere (property and GUI)
var seedingCreator = problemClone.Operators.OfType().FirstOrDefault();
if (seedingCreator == null) throw new InvalidOperationException("The problem does not contain a solution creator that allows seeding.");
seedingCreator.PreexistingSolutionsParameter.Value.Replace(SolutionSeedingPool.CheckedItems.Select(x => x.Value));
seedingCreator.SampleFromPreexistingParameter.Value.Value = seedingStrategyLocal == SeedingStrategyTypes.SeedBySampling;
// TODO: WHY!? WHY??!?
((dynamic)problemClone.SolutionCreatorParameter).Value = (dynamic)seedingCreator;
}
algorithmClone.Problem = problemClone;
algorithmClone.Prepare(true);
IParameter stopParam;
var monitorStop = true;
if (algorithmClone.Parameters.TryGetValue("MaximumEvaluations", out stopParam)) {
var maxEvalParam = stopParam as IValueParameter;
if (maxEvalParam != null) {
maxEvalParam.Value.Value = MaximumEvaluations.Value;
monitorStop = false;
}
}
// TODO: The following can be simplified when we have async implementation patterns for our algorithms:
// TODO: The closures can be removed and replaced with private member methods
var waitHandle = new AutoResetEvent(false);
#region EventHandler closures
EventHandler exeStateChanged = (sender, e) => {
if (algorithmClone.ExecutionState == ExecutionState.Stopped) {
lock (Problem.Solutions) {
foreach (var solution in algorithmClone.Results.Where(x => x.Name.ToLower().Contains("solution")).Select(x => x.Value).OfType()) {
Problem.Solutions.Add(new SingleObjectiveOKBSolution(Problem.ProblemId) {
Quality = solution.Variables.ContainsKey(Problem.Problem.Evaluator.QualityParameter.ActualName) ? ((DoubleValue)solution.Variables[Problem.Problem.Evaluator.QualityParameter.ActualName].Value).Value : double.NaN,
Solution = (IItem)solution.Clone()
});
}
}
if (seedingStrategyLocal == SeedingStrategyTypes.NoSeeding) {
lock (InstanceRuns) {
InstanceRuns.Add(algorithmClone.Runs.Last());
}
} else {
lock (SeededRuns) {
SeededRuns.Add(algorithmClone.Runs.Last());
}
}
waitHandle.Set();
}
};
EventHandler> exceptionOccurred = (sender, e) => {
waitHandle.Set();
};
EventHandler timeChanged = (sender, e) => {
IResult evalSolResult;
if (!algorithmClone.Results.TryGetValue("EvaluatedSolutions", out evalSolResult) || !(evalSolResult.Value is Data.IntValue)) return;
var evalSols = ((Data.IntValue)evalSolResult.Value).Value;
if (evalSols >= MaximumEvaluations.Value && algorithmClone.ExecutionState == ExecutionState.Started)
algorithmClone.Stop();
};
#endregion
algorithmClone.ExecutionStateChanged += exeStateChanged;
algorithmClone.ExceptionOccurred += exceptionOccurred;
if (monitorStop) algorithmClone.ExecutionTimeChanged += timeChanged;
return Task.Factory.StartNew(() => {
algorithmClone.Start();
OnAlgorithmInstanceStarted(algorithmClone);
var cancelRequested = false;
while (!waitHandle.WaitOne(200)) {
if (cancellation.IsCancellationRequested) {
cancelRequested = true;
break;
}
}
if (cancelRequested) {
try { algorithmClone.Stop(); } catch { } // ignore race condition if it is stopped in the meantime
waitHandle.WaitOne();
}
waitHandle.Dispose();
return algorithmClone.Results;
}, TaskCreationOptions.LongRunning);
}
public ResultCollection StartAlgorithm(int index, CancellationToken cancellation) {
var task = StartAlgorithmAsync(index, cancellation);
task.Wait(cancellation);
return task.Result;
}
public Task UpdateKnowledgeBaseAsync(IProgress progress = null) {
if (progress == null) progress = new Progress();
progress.Start("Updating Knowledge Base from OKB");
OnDownloadStarted(progress);
return Task.Factory.StartNew(() => { DoUpdateKnowledgeBase(progress); }, TaskCreationOptions.LongRunning);
}
public void UpdateKnowledgeBase(IProgress progress = null) {
UpdateKnowledgeBaseAsync(progress).Wait();
}
private void DoUpdateKnowledgeBase(IProgress progress) {
var queryClient = Clients.OKB.Query.QueryClient.Instance;
var adminClient = Clients.OKB.Administration.AdministrationClient.Instance;
try {
progress.Message = "Connecting to OKB...";
progress.ProgressValue = 0;
// FIXME: How to tell if refresh is necessary?
var refreshTasks = new[] {
Task.Factory.StartNew(() => queryClient.Refresh()),
Task.Factory.StartNew(() => adminClient.Refresh())
};
Task.WaitAll(refreshTasks);
var probInstance = adminClient.Problems.SingleOrDefault(x => x.Id == Problem.ProblemId);
if (probInstance == null) throw new InvalidOperationException("The chosen problem instance cannot be found in the OKB.");
var probClassId = probInstance.ProblemClassId;
var problemClassFilter = (Clients.OKB.Query.StringComparisonAvailableValuesFilter)queryClient.Filters.Single(x => x.Label == "Problem Class Name");
problemClassFilter.Value = adminClient.ProblemClasses.Single(x => x.Id == probClassId).Name;
problemId2ProblemInstanceMapping.Clear();
progress.Message = "Downloading algorithm and problem instances...";
progress.ProgressValue = 0;
int[] p = { 0 };
ProblemInstances.UpdateOfRunsInProgress = true;
ProblemInstances.Clear();
algorithmId2AlgorithmInstanceMapping.Clear();
algorithmId2RunMapping.Clear();
algorithmInstances.Clear();
var characteristics = new HashSet();
var totalProblems = adminClient.Problems.Count(x => x.ProblemClassId == probClassId);
var totalAlgorithms = adminClient.Algorithms.Count;
var problems = adminClient.Problems.Where(x => x.ProblemClassId == probClassId);
var algorithms = adminClient.Algorithms;
var combined = problems.Cast