Index: /branches/1614_GeneralizedQAP/HeuristicLab.Analysis.Views/3.3/ScatterPlotDataRowVisualPropertiesControl.resx
===================================================================
--- /branches/1614_GeneralizedQAP/HeuristicLab.Analysis.Views/3.3/ScatterPlotDataRowVisualPropertiesControl.resx	(revision 15720)
+++ /branches/1614_GeneralizedQAP/HeuristicLab.Analysis.Views/3.3/ScatterPlotDataRowVisualPropertiesControl.resx	(revision 15720)
@@ -0,0 +1,123 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="colorDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>
Index: /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/BestNScopesSolutionAnalyzer.cs
===================================================================
--- /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/BestNScopesSolutionAnalyzer.cs	(revision 15720)
+++ /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/BestNScopesSolutionAnalyzer.cs	(revision 15720)
@@ -0,0 +1,174 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using HeuristicLab.Common;
+using HeuristicLab.Core;
+using HeuristicLab.Data;
+using HeuristicLab.Operators;
+using HeuristicLab.Optimization;
+using HeuristicLab.Optimization.Operators;
+using HeuristicLab.Parameters;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace HeuristicLab.Analysis {
+  /// <summary>
+  /// An operator that extracts (clones) the scope containing the best quality.
+  /// </summary>
+  [Item("BestNScopesSolutionAnalyzer", "An operator that maintains at most N scopes containing good quality solutions.")]
+  [StorableClass]
+  public class BestNScopesSolutionAnalyzer : SingleSuccessorOperator, IAnalyzer, ISingleObjectiveOperator {
+
+    public virtual bool EnabledByDefault {
+      get { return true; }
+    }
+    public LookupParameter<BoolValue> MaximizationParameter {
+      get { return (LookupParameter<BoolValue>)Parameters["Maximization"]; }
+    }
+    public ScopeTreeLookupParameter<DoubleValue> QualityParameter {
+      get { return (ScopeTreeLookupParameter<DoubleValue>)Parameters["Quality"]; }
+    }
+    public IFixedValueParameter<StringValue> BestSolutionsResultNameParameter {
+      get { return (IFixedValueParameter<StringValue>)Parameters["BestSolutions ResultName"]; }
+    }
+    public IValueLookupParameter<ResultCollection> ResultsParameter {
+      get { return (IValueLookupParameter<ResultCollection>)Parameters["Results"]; }
+    }
+    public IValueParameter<ISolutionSimilarityCalculator> SimilarityCalculatorParameter {
+      get { return (IValueParameter<ISolutionSimilarityCalculator>)Parameters["SimilarityCalculator"]; }
+    }
+    private IFixedValueParameter<IntValue> NParameter {
+      get { return (IFixedValueParameter<IntValue>)Parameters["N"]; }
+    }
+    private IFixedValueParameter<DoubleValue> MaximumSimilarityParameter {
+      get { return (IFixedValueParameter<DoubleValue>)Parameters["MaximumSimilarity"]; }
+    }
+
+    public string BestSolutionsResultName {
+      get { return BestSolutionsResultNameParameter.Value.Value; }
+      set { BestSolutionsResultNameParameter.Value.Value = value; }
+    }
+
+    public int N {
+      get { return NParameter.Value.Value; }
+      set { NParameter.Value.Value = value; }
+    }
+
+    public double MaximumSimilarity {
+      get { return MaximumSimilarityParameter.Value.Value; }
+      set { MaximumSimilarityParameter.Value.Value = value; }
+    }
+
+    #region Storing & Cloning
+    [StorableConstructor]
+    protected BestNScopesSolutionAnalyzer(bool deserializing) : base(deserializing) { }
+    protected BestNScopesSolutionAnalyzer(BestNScopesSolutionAnalyzer original, Cloner cloner) : base(original, cloner) { }
+    public override IDeepCloneable Clone(Cloner cloner) {
+      return new BestNScopesSolutionAnalyzer(this, cloner);
+    }
+    #endregion
+    public BestNScopesSolutionAnalyzer()
+      : base() {
+      Parameters.Add(new LookupParameter<BoolValue>("Maximization", "True if the problem is a maximization problem."));
+      Parameters.Add(new ScopeTreeLookupParameter<DoubleValue>("Quality", "The qualities of the solutions."));
+      Parameters.Add(new FixedValueParameter<StringValue>("BestSolutions ResultName", "The name of the result for storing the best solution.", new StringValue("Best Solutions")));
+      Parameters.Add(new ValueLookupParameter<ResultCollection>("Results", "The result collection where the solution should be stored."));
+      Parameters.Add(new ValueParameter<ISolutionSimilarityCalculator>("SimilarityCalculator", "The solution similarity calculator that is used to compare two solution scopes.", new QualitySimilarityCalculator()));
+      Parameters.Add(new FixedValueParameter<IntValue>("N", "The N best solutions that are kept.", new IntValue(10)));
+      Parameters.Add(new FixedValueParameter<DoubleValue>("MaximumSimilarity", "The maximum similarity between two solutions in order to be remembered in the list.", new DoubleValue(0.8)));
+    }
+
+    public override IOperation Apply() {
+      var results = ResultsParameter.ActualValue;
+      if (results.ContainsKey(BestSolutionsResultName) && !typeof(ScopeList).IsAssignableFrom(results[BestSolutionsResultName].DataType)) {
+        throw new InvalidOperationException(string.Format("Could not add best solutions result, because there is already a result with the name \"{0}\" present in the result collection.", BestSolutionsResultName));
+      }
+      ScopeList bestScopes = null;
+      if (results.ContainsKey(BestSolutionsResultName)) {
+        bestScopes = (ScopeList)results[BestSolutionsResultName].Value;
+      } else {
+        bestScopes = new ScopeList();
+        results.Add(new Result(BestSolutionsResultName, bestScopes));
+      }
+
+      var max = MaximizationParameter.ActualValue.Value;
+      var simCalc = SimilarityCalculatorParameter.Value;
+      var maxSim = MaximumSimilarity;
+      var qualityName = QualityParameter.TranslatedName;
+      var depth = QualityParameter.Depth;
+
+      IEnumerable<IScope> scopes = new [] { ExecutionContext.Scope };
+      for (var j = 0; j < depth; j++)
+        scopes = scopes.SelectMany(x => x.SubScopes);
+
+      scopes = max ? scopes.OrderByDescending(x => ((DoubleValue)x.Variables[qualityName].Value).Value)
+                   : scopes.OrderBy(x => ((DoubleValue)x.Variables[qualityName].Value).Value);
+      
+      var newList = new ScopeList(bestScopes.Take(N));
+      var cloner = new Cloner();
+      // avoid cloning the results collection that the solution is put in
+      cloner.RegisterClonedObject(results, new ResultCollection());
+
+      foreach (var s in scopes) {
+        bool isDiverse = true, isBetterThanWorst = false, isIdentical = false;
+        var worstIdx = -1;
+        IScope worst = null;
+        var idx = 0;
+        foreach (var a in newList) {
+          if (IsBetter(s, a, qualityName, max)) {
+            isBetterThanWorst = true;
+            if (worst == null || IsBetter(worst, a, qualityName, max)) {
+              worst = a;
+              worstIdx = idx;
+            }
+          }
+          var similarity = simCalc.CalculateSolutionSimilarity(s, a);
+          if (similarity.IsAlmost(1.0)) isIdentical = true;
+          if (similarity > maxSim) isDiverse = false;
+          idx++;
+
+          if (isIdentical || (newList.Count >= N && !isDiverse)) break;
+        }
+        // to accept a new solution it needs to be
+        // A) not identical
+        // B) must be less similar than a certain value
+        // C) must be better than at least one solution already accepted
+        // The exception to B) and C) is when the list is not full yet
+        if (isIdentical || (newList.Count >= N && !(isDiverse && isBetterThanWorst))) continue;
+        // avoid cloning subscopes of the solution
+        cloner.RegisterClonedObject(s.SubScopes, new ScopeList());
+        newList.Add(cloner.Clone(s));
+        if (newList.Count > N) newList.RemoveAt(worstIdx);
+      }
+
+      bestScopes.Replace(newList);
+
+      return base.Apply();
+    }
+
+    private static bool IsBetter(IScope a, IScope b, string qualityName, bool max) {
+      return max ? ((DoubleValue)a.Variables[qualityName].Value).Value > ((DoubleValue)b.Variables[qualityName].Value).Value
+                 : ((DoubleValue)a.Variables[qualityName].Value).Value < ((DoubleValue)b.Variables[qualityName].Value).Value;
+    }
+  }
+}
Index: /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/Clustering/CkMeans1D.cs
===================================================================
--- /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/Clustering/CkMeans1D.cs	(revision 15720)
+++ /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/Clustering/CkMeans1D.cs	(revision 15720)
@@ -0,0 +1,128 @@
+﻿#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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using HeuristicLab.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace HeuristicLab.Analysis {
+  /// <summary>
+  /// Implements the Ckmeans.1d.dp method. It is described in the paper:
+  /// Haizhou Wang and Mingzhou Song. 2011.
+  /// Ckmeans.1d.dp: Optimal k-means Clustering in One Dimension by Dynamic Programming
+  /// The R Journal Vol. 3/2, pp. 29-33.
+  /// available at https://journal.r-project.org/archive/2011-2/RJournal_2011-2_Wang+Song.pdf
+  /// </summary>
+  public class CkMeans1D {
+    /// <summary>
+    /// Clusters the 1-dimensional data given in <paramref name="estimations"/>.
+    /// </summary>
+    /// <param name="estimations">The 1-dimensional data that should be clustered.</param>
+    /// <param name="k">The maximum number of clusters.</param>
+    /// <param name="clusterValues">A vector of the same length as estimations that assigns to each point a cluster id.</param>
+    /// <returns>A sorted list of cluster centroids and corresponding cluster ids.</returns>
+    public static SortedList<double, int> Cluster(double[] estimations, int k, out int[] clusterValues) {
+      int nPoints = estimations.Length;
+      var distinct = estimations.Distinct().OrderBy(x => x).ToArray();
+      var max = distinct.Max();
+      if (distinct.Length <= k) {
+        var dict = distinct.Select((v, i) => new { Index = i, Value = v }).ToDictionary(x => x.Value, y => y.Index);
+        for (int i = distinct.Length; i < k; i++)
+          dict.Add(max + i - distinct.Length + 1, i);
+
+        clusterValues = new int[nPoints];
+        for (int i = 0; i < nPoints; i++)
+          if (!dict.ContainsKey(estimations[i])) clusterValues[i] = 0;
+          else clusterValues[i] = dict[estimations[i]];
+
+        return new SortedList<double, int>(dict);
+      }
+
+      var n = distinct.Length;
+      var D = new double[n, k];
+      var B = new int[n, k];
+
+      for (int m = 0; m < k; m++) {
+        for (int j = m; j <= n - k + m; j++) {
+          if (m == 0)
+            D[j, m] = SumOfSquaredDistances(distinct, 0, j + 1);
+          else {
+            var minD = double.MaxValue;
+            var minI = 0;
+            for (int i = 1; i <= j; i++) {
+              var d = D[i - 1, m - 1] + SumOfSquaredDistances(distinct, i, j + 1);
+              if (d < minD) {
+                minD = d;
+                minI = i;
+              }
+            }
+            D[j, m] = minD;
+            B[j, m] = minI;
+          }
+        }
+      }
+
+      var centers = new SortedList<double, int>();
+      var upper = B[n - 1, k - 1];
+      var c = Mean(distinct, upper, n);
+      centers.Add(c, k - 1);
+      for (int i = k - 2; i >= 0; i--) {
+        var lower = B[upper - 1, i];
+        var c2 = Mean(distinct, lower, upper);
+        centers.Add(c2, i);
+        upper = lower;
+      }
+
+      clusterValues = new int[nPoints];
+      for (int i = 0; i < estimations.Length; i++) {
+        clusterValues[i] = centers.MinItems(x => Math.Abs(estimations[i] - x.Key)).First().Value;
+      }
+
+      return centers;
+    }
+
+    private static double SumOfSquaredDistances(double[] x, int start, int end) {
+      if (start == end) throw new InvalidOperationException();
+      if (start + 1 == end) return 0.0;
+      double mean = 0.0;
+      for (int i = start; i < end; i++) {
+        mean += x[i];
+      }
+      mean /= (end - start);
+      var sum = 0.0;
+      for (int i = start; i < end; i++) {
+        sum += (x[i] - mean) * (x[i] - mean);
+      }
+      return sum;
+    }
+
+    private static double Mean(double[] x, int start, int end) {
+      if (start == end) throw new InvalidOperationException();
+      double mean = 0.0;
+      for (int i = start; i < end; i++) {
+        mean += x[i];
+      }
+      mean /= (end - start);
+      return mean;
+    }
+  }
+}
Index: /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/Clustering/ClusterHelper.cs
===================================================================
--- /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/Clustering/ClusterHelper.cs	(revision 15720)
+++ /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/Clustering/ClusterHelper.cs	(revision 15720)
@@ -0,0 +1,97 @@
+﻿#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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace HeuristicLab.Analysis {
+  public class ClusteringHelper<T> {
+    private readonly int k;
+
+    private List<KeyValuePair<T, double>> instances;
+    private List<KeyValuePair<T, double>> excluded;
+    private int[] clusterValues;
+    private int[] rankedMap;
+
+    private ClusteringHelper(int K) {
+      this.k = K;
+    }
+
+    /// <summary>
+    /// Helps in clustering data which is available as key-value pairs.
+    /// It is possible to specify an exclude function to omit certain points from clustering.
+    /// These points will be assigned cluster with id k. All other points will be assigned a
+    /// cluster id in the range [0;k-1].
+    /// </summary>
+    /// <param name="k">The maximum number of clusters that should be created.</param>
+    /// <param name="values">The data which links a certain item with a double value that is to be clustered.</param>
+    /// <param name="excludeFunc">The function that allows excluding certing data points which will receive cluster id k.</param>
+    /// <returns>A reference to the helper class to allow fluent calls.</returns>
+    public static ClusteringHelper<T> Cluster(int k, IEnumerable<KeyValuePair<T, double>> values, Func<KeyValuePair<T, double>, bool> excludeFunc = null) {
+      if (excludeFunc == null) excludeFunc = _ => false;
+      var helper = new ClusteringHelper<T>(k);
+      helper.Initialize(values, excludeFunc);
+      if (helper.instances.Count == 0) {
+        helper.clusterValues = new int[0];
+        helper.rankedMap = new int[k + 1];
+      } else {
+        var centroids = CkMeans1D.Cluster(helper.instances.Select(x => x.Value).ToArray(), k, out helper.clusterValues);
+        helper.rankedMap = new int[k + 1];
+        var rank = 0;
+        foreach (var c in centroids) helper.rankedMap[c.Value] = rank++;
+      }
+      // excluded are always ranked last
+      helper.rankedMap[k] = k;
+      return helper;
+    }
+
+    /// <summary>
+    /// Returns the clustered data by 
+    /// </summary>
+    /// <returns></returns>
+    public IEnumerable<KeyValuePair<T, Tuple<double, int>>> GetByInstance() {
+      return GetClustered().Select(x => new KeyValuePair<T, Tuple<double, int>>(x.Item1.Key, Tuple.Create(x.Item1.Value, rankedMap[x.Item2])));
+    }
+
+    public IEnumerable<KeyValuePair<int, List<KeyValuePair<T, double>>>> GetByCluster() {
+      return GetClustered().GroupBy(x => x.Item2)
+        .Select(x => new KeyValuePair<int, List<KeyValuePair<T, double>>>(rankedMap[x.Key], x.Select(y => y.Item1).ToList()));
+    }
+
+    private void Initialize(IEnumerable<KeyValuePair<T, double>> values, Func<KeyValuePair<T, double>, bool> excludeFunc) {
+      instances = new List<KeyValuePair<T, double>>();
+      excluded = new List<KeyValuePair<T, double>>();
+      foreach (var v in values) {
+        if (!excludeFunc(v)) instances.Add(v);
+        else excluded.Add(v);
+      }
+    }
+
+    private IEnumerable<Tuple<KeyValuePair<T, double>, int>> GetClustered() {
+      for (var i = 0; i < clusterValues.Length; i++) {
+        yield return Tuple.Create(instances[i], clusterValues[i]);
+      }
+      foreach (var ex in excluded)
+        yield return Tuple.Create(ex, k);
+    }
+  }
+}
Index: /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/HeuristicLab.Analysis-3.3.csproj
===================================================================
--- /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/HeuristicLab.Analysis-3.3.csproj	(revision 15719)
+++ /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/HeuristicLab.Analysis-3.3.csproj	(revision 15720)
@@ -170,6 +170,11 @@
     <Compile Include="AlleleFrequencyAnalysis\AlleleFrequencyCollection.cs" />
     <Compile Include="AlleleFrequencyAnalysis\AlleleFrequencyCollectionHistory.cs" />
+    <Compile Include="BestNScopesSolutionAnalyzer.cs" />
     <Compile Include="BestScopeSolutionAnalyzer.cs" />
     <Compile Include="DataVisualization\GanttData.cs" />
+    <Compile Include="Clustering\CkMeans1D.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Clustering\ClusterHelper.cs" />
     <Compile Include="DataVisualization\IndexedDataRow.cs" />
     <Compile Include="DataVisualization\IndexedDataTable.cs" />
@@ -190,4 +195,5 @@
     <Compile Include="MultiObjective\RankBasedParetoFrontAnalyzer.cs" />
     <Compile Include="MultiObjective\ParetoFrontAnalyzer.cs" />
+    <Compile Include="Optimizers\IteratedAlgorithm.cs" />
     <Compile Include="Plugin.cs" />
     <Compile Include="PopulationSimilarityAnalysis\PopulationDiversityAnalyzer.cs" />
@@ -206,4 +212,6 @@
     <Compile Include="QualityAnalysis\QualityPerEvaluationsAnalyzer.cs" />
     <Compile Include="QualityAnalysis\ScaledQualityDifferenceAnalyzer.cs" />
+    <Compile Include="SelfOrganizingMaps\RelationalSOM.cs" />
+    <Compile Include="SelfOrganizingMaps\SOM.cs" />
     <Compile Include="Statistics\BonferroniHolm.cs" />
     <Compile Include="Statistics\EnumerableStatisticsExtension.cs" />
Index: /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/Optimizers/IteratedAlgorithm.cs
===================================================================
--- /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/Optimizers/IteratedAlgorithm.cs	(revision 15720)
+++ /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/Optimizers/IteratedAlgorithm.cs	(revision 15720)
@@ -0,0 +1,746 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using HeuristicLab.Collections;
+using HeuristicLab.Common;
+using HeuristicLab.Core;
+using HeuristicLab.Data;
+using HeuristicLab.Optimization;
+using HeuristicLab.Parameters;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace HeuristicLab.Analysis {
+  /// <summary>
+  /// A run in which an algorithm is executed for a certain maximum time only.
+  /// </summary>
+  [Item("Iterated Algorithm", "An algorithm that repeats an algorithm until either a certain target value is reached or a maximum budget is exceeded.")]
+  [Creatable(CreatableAttribute.Categories.TestingAndAnalysis, Priority = 116)]
+  [StorableClass]
+  public sealed class IteratedAlgorithm : Algorithm, IStorableContent {
+    private const string ExecutionTimeResultName = "Execution Time";
+    private const string BestQualityResultName = "BestQuality";
+    private const string RandomRestartsResultName = "RandomRestarts";
+    private const string EvaluatedSolutionsResultName = "EvaluatedSolutions";
+    private const string EvaluatedMovesResultName = "EvaluatedMoves";
+    private const string QualityPerClockResultName = "QualityPerClock";
+    private const string QualityPerEvaluationsResultName = "QualityPerEvaluations";
+    private const string EvaluationsResultName = "Evaluations";
+
+    public string Filename { get; set; }
+
+    public override Type ProblemType { get { return typeof(ISingleObjectiveHeuristicOptimizationProblem); } }
+
+    public IValueParameter<TimeSpanValue> MaximumExecutionTimeParameter {
+      get { return (IValueParameter<TimeSpanValue>)Parameters["MaximumExecutionTime"]; }
+    }
+
+    public IValueParameter<IntValue> MaximumEvaluationsParameter {
+      get { return (IValueParameter<IntValue>)Parameters["MaximumEvaluations"]; }
+    }
+
+    public IValueParameter<DoubleValue> TargetQualityParameter {
+      get { return (IValueParameter<DoubleValue>)Parameters["TargetQuality"]; }
+    }
+
+    private IFixedValueParameter<DoubleValue> MoveCostPerSolutionParameter {
+      get { return (IFixedValueParameter<DoubleValue>)Parameters["MoveCostPerSolution"]; }
+    }
+
+    private IFixedValueParameter<BoolValue> StoreSolutionInRunParameter {
+      get { return (IFixedValueParameter<BoolValue>)Parameters["StoreSolutionInRun"]; }
+    }
+
+    private IValueParameter<IAlgorithm> AlgorithmParameter {
+      get { return (IValueParameter<IAlgorithm>)Parameters["Algorithm"]; }
+    }
+
+    private IFixedValueParameter<MultiAnalyzer> AnalyzerParameter {
+      get { return (IFixedValueParameter<MultiAnalyzer>)Parameters["Analyzer"]; }
+    }
+
+    [Storable]
+    private ResultCollection results;
+    public override ResultCollection Results {
+      get { return results; }
+    }
+
+    [Storable]
+    private QualityPerClockAnalyzer perClockAnalyzer;
+    public QualityPerClockAnalyzer PerClockAnalyzer {
+      get { return perClockAnalyzer; }
+    }
+    [Storable]
+    private QualityPerEvaluationsAnalyzer perEvaluationsAnalyzer;
+    public QualityPerEvaluationsAnalyzer PerEvaluationsAnalyzer {
+      get { return perEvaluationsAnalyzer; }
+    }
+    [Storable]
+    private BestScopeSolutionAnalyzer bestSolutionAnalyzer;
+    public BestScopeSolutionAnalyzer BestSolutionAnalyzer {
+      get { return bestSolutionAnalyzer; }
+    }
+
+    public double MoveCostPerSolution {
+      get { return MoveCostPerSolutionParameter.Value.Value; }
+      set { MoveCostPerSolutionParameter.Value.Value = value; }
+    }
+
+    public bool StoreSolutionInRun {
+      get { return StoreSolutionInRunParameter.Value.Value; }
+      set { StoreSolutionInRunParameter.Value.Value = value; }
+    }
+
+    // algorithm will be set in AfterDeserialization to the value of the parameter
+    private IAlgorithm algorithm;
+    public IAlgorithm Algorithm {
+      get { return algorithm; }
+      set {
+        if (algorithm == value) return;
+        if (algorithm != null) {
+          DeregisterAlgorithmEvents();
+          RemoveAlgorithmAnalyzers();
+        }
+        algorithm = value;
+        if (algorithm != null) {
+          if (algorithm.ExecutionState != ExecutionState.Prepared)
+            algorithm.Prepare(true);
+          if (Problem != null)
+            algorithm.Problem = Problem;
+          RegisterAlgorithmEvents();
+          AddAlgorithmAnalyzers();
+          HandleAlgorithmMaximumEvaluationsChanged();
+          HandleAlgorithmTargetQualityChanged();
+        }
+        if (AlgorithmParameter.Value != algorithm)
+          AlgorithmParameter.Value = algorithm;
+        Prepare();
+      }
+    }
+
+    public MultiAnalyzer Analyzer {
+      get { return AnalyzerParameter.Value; }
+    }
+
+    private bool Maximization {
+      get { return Problem != null && ((IValueParameter<BoolValue>)((ISingleObjectiveHeuristicOptimizationProblem)Problem).MaximizationParameter).Value.Value; }
+    }
+
+    private bool IsFinished {
+      get {
+        var executionTime = Results.ContainsKey(ExecutionTimeResultName) ? ((TimeSpanValue)Results[ExecutionTimeResultName].Value).Value : TimeSpan.Zero;
+        var evaluations = Results.ContainsKey(EvaluationsResultName) ? ((DoubleValue)Results[EvaluationsResultName].Value).Value : 0;
+        var bestQuality = Results.ContainsKey(BestQualityResultName) ? ((DoubleValue)Results[BestQualityResultName].Value).Value
+                                                                     : (Maximization ? double.MinValue : double.MaxValue);
+        var targetValue = TargetQualityParameter.Value != null ? TargetQualityParameter.Value.Value
+                                                             : Maximization ? double.MaxValue : double.MinValue;
+
+        var timeHit = MaximumExecutionTimeParameter.Value != null && executionTime >= MaximumExecutionTimeParameter.Value.Value;
+        var evalHit = MaximumEvaluationsParameter.Value != null && evaluations >= MaximumEvaluationsParameter.Value.Value;
+        var targetHit = Maximization && bestQuality >= targetValue || !Maximization && bestQuality <= targetValue;
+
+        return forceStop || timeHit || evalHit || targetHit;
+      }
+    }
+
+    [StorableConstructor]
+    private IteratedAlgorithm(bool deserializing) : base(deserializing) { }
+    private IteratedAlgorithm(IteratedAlgorithm original, Cloner cloner)
+      : base(original, cloner) {
+      results = cloner.Clone(original.Results);
+      perClockAnalyzer = cloner.Clone(original.perClockAnalyzer);
+      perEvaluationsAnalyzer = cloner.Clone(original.perEvaluationsAnalyzer);
+      bestSolutionAnalyzer = cloner.Clone(original.bestSolutionAnalyzer);
+      algorithm = cloner.Clone(original.algorithm);
+      RegisterEventHandlers();
+    }
+    public IteratedAlgorithm()
+      : base() {
+      results = new ResultCollection();
+      Parameters.Add(new FixedValueParameter<MultiAnalyzer>("Analyzer", "Analyzers that should be called in addition to the default algorithm analyzers.", new MultiAnalyzer()));
+      Parameters.Add(new OptionalValueParameter<TimeSpanValue>("MaximumExecutionTime", "The maximum wall-clock time that the algorithm should run."));
+      Parameters.Add(new OptionalValueParameter<IntValue>("MaximumEvaluations", "The maximum number of function evaluations that the algorithm should run.", new IntValue(100000000)));
+      Parameters.Add(new OptionalValueParameter<DoubleValue>("TargetQuality", "The target quality that the algorithm should run for."));
+      Parameters.Add(new FixedValueParameter<DoubleValue>("MoveCostPerSolution", "The amount of solution evaluation equivalents of a single move. Use 1 for a black-box scenario.", new DoubleValue(1)));
+      Parameters.Add(new FixedValueParameter<BoolValue>("StoreSolutionInRun", "Whether the solution data types should be kept in the run."));
+      Parameters.Add(new ValueParameter<IAlgorithm>("Algorithm", "The algorithm to iterate.") { GetsCollected = false }); // due to storage efficiency, by default we don't want to store the algorithm instance in the run
+
+      perClockAnalyzer = new QualityPerClockAnalyzer();
+      perEvaluationsAnalyzer = new QualityPerEvaluationsAnalyzer();
+      bestSolutionAnalyzer = new BestScopeSolutionAnalyzer();
+      Analyzer.Operators.Add(perClockAnalyzer, true);
+      Analyzer.Operators.Add(perEvaluationsAnalyzer, true);
+      Analyzer.Operators.Add(bestSolutionAnalyzer, StoreSolutionInRun);
+
+      RegisterEventHandlers();
+    }
+
+    public override IDeepCloneable Clone(Cloner cloner) {
+      if (ExecutionState == ExecutionState.Started) throw new InvalidOperationException(string.Format("Clone not allowed in execution state \"{0}\".", ExecutionState));
+      return new IteratedAlgorithm(this, cloner);
+    }
+
+    [StorableHook(HookType.AfterDeserialization)]
+    private void AfterDeserialization() {
+      algorithm = AlgorithmParameter.Value;
+      // BackwardsCompatibility3.3
+      #region Backwards compatible code, remove with 3.4
+      if (Parameters.ContainsKey("TargetValue")) {
+        var target = ((OptionalValueParameter<DoubleValue>)Parameters["TargetValue"]).Value;
+        Parameters.Remove("TargetValue");
+        Parameters.Add(new OptionalValueParameter<DoubleValue>("TargetQuality", "The target quality that the algorithm should run for.", target));
+      }
+      if (!Parameters.ContainsKey("Analyzer")) {
+        Parameters.Add(new FixedValueParameter<MultiAnalyzer>("Analyzer", "Analyzers that should be called in addition to the default algorithm analyzers.", new MultiAnalyzer()));
+        Analyzer.Operators.Add(perClockAnalyzer, true);
+        Analyzer.Operators.Add(perEvaluationsAnalyzer, true);
+        if (Algorithm != null && Algorithm.Parameters.ContainsKey("Analyzer")) {
+          var analyzerParam = Algorithm.Parameters["Analyzer"] as IValueParameter<MultiAnalyzer>;
+          if (analyzerParam != null) {
+            analyzerParam.Value.Operators.Remove(perClockAnalyzer);
+            analyzerParam.Value.Operators.Remove(perEvaluationsAnalyzer);
+          } else {
+            var analyzerParam2 = Algorithm.Parameters["Analyzer"] as IValueParameter<IMultiAnalyzer>;
+            if (analyzerParam2 != null) {
+              analyzerParam2.Value.Operators.Remove(perClockAnalyzer);
+              analyzerParam2.Value.Operators.Remove(perEvaluationsAnalyzer);
+            }
+          }
+          AddAlgorithmAnalyzers();
+        }
+      }
+      if (bestSolutionAnalyzer == null) {
+        bestSolutionAnalyzer = new BestScopeSolutionAnalyzer();
+        Analyzer.Operators.Add(bestSolutionAnalyzer, StoreSolutionInRun);
+      }
+      #endregion
+      RegisterEventHandlers();
+    }
+
+    #region Register Event Handlers
+    protected override void RegisterProblemEvents() {
+      var bkParam = ((ISingleObjectiveHeuristicOptimizationProblem)Problem).BestKnownQualityParameter as IValueParameter<DoubleValue>;
+      if (bkParam != null) {
+        bkParam.ValueChanged += Problem_BestKnownQualityParameter_ValueChanged;
+      }
+      base.RegisterProblemEvents();
+    }
+    protected override void DeregisterProblemEvents() {
+      base.DeregisterProblemEvents();
+      var bkParam = ((ISingleObjectiveHeuristicOptimizationProblem)Problem).BestKnownQualityParameter as IValueParameter<DoubleValue>;
+      if (bkParam != null) {
+        bkParam.ValueChanged -= Problem_BestKnownQualityParameter_ValueChanged;
+      }
+    }
+    private void RegisterAlgorithmEvents() {
+      Algorithm.ExceptionOccurred += Algorithm_ExceptionOccurred;
+      Algorithm.Paused += Algorithm_Paused;
+      Algorithm.Stopped += Algorithm_Stopped;
+      Algorithm.ProblemChanged += Algorithm_ProblemChanged;
+      #region MaximumEvaluations
+      IParameter param;
+      if (Algorithm.Parameters.TryGetValue("MaximumEvaluations", out param)) {
+        var intFixedValueParam = (param as IFixedValueParameter<IntValue>);
+        if (intFixedValueParam != null) {
+          intFixedValueParam.Value.ValueChanged += AlgorithmMaximumEvaluationsOnValueChanged;
+        } else {
+          var intValueParam = (param as IValueParameter<IntValue>);
+          if (intValueParam != null) {
+            intValueParam.ValueChanged += AlgorithmMaximumEvaluationsParameterOnChanged;
+            if (intValueParam.Value != null) intValueParam.Value.ValueChanged += AlgorithmMaximumEvaluationsOnValueChanged;
+          }
+        }
+      }
+      #endregion
+      #region TargetQuality
+      if (Algorithm.Parameters.TryGetValue("TargetQuality", out param)) {
+        var intValueParam = (param as IValueParameter<IntValue>);
+        if (intValueParam != null) {
+          intValueParam.ValueChanged += AlgorithmTargetQualityParameterOnChanged;
+          if (intValueParam.Value != null) intValueParam.Value.ValueChanged += AlgorithmTargetQualityOnValueChanged;
+        }
+      }
+      #endregion
+    }
+
+    private void DeregisterAlgorithmEvents() {
+      Algorithm.ExceptionOccurred -= Algorithm_ExceptionOccurred;
+      Algorithm.Paused -= Algorithm_Paused;
+      Algorithm.Stopped -= Algorithm_Stopped;
+      Algorithm.ProblemChanged -= Algorithm_ProblemChanged;
+      #region MaximumEvaluations
+      IParameter param;
+      if (Algorithm.Parameters.TryGetValue("MaximumEvaluations", out param)) {
+        var intFixedValueParam = (param as IFixedValueParameter<IntValue>);
+        if (intFixedValueParam != null) {
+          intFixedValueParam.Value.ValueChanged -= AlgorithmMaximumEvaluationsOnValueChanged;
+        } else {
+          var intValueParam = (param as IValueParameter<IntValue>);
+          if (intValueParam != null) {
+            intValueParam.ValueChanged -= AlgorithmMaximumEvaluationsParameterOnChanged;
+            if (intValueParam.Value != null) intValueParam.Value.ValueChanged -= AlgorithmMaximumEvaluationsOnValueChanged;
+          }
+        }
+      }
+      #endregion
+      #region TargetQuality
+      if (Algorithm.Parameters.TryGetValue("TargetQuality", out param)) {
+        var intValueParam = (param as IValueParameter<IntValue>);
+        if (intValueParam != null) {
+          intValueParam.ValueChanged -= AlgorithmTargetQualityParameterOnChanged;
+          if (intValueParam.Value != null) intValueParam.Value.ValueChanged -= AlgorithmTargetQualityOnValueChanged;
+        }
+      }
+      #endregion
+    }
+    #endregion
+
+    private void RegisterEventHandlers() {
+      if (Algorithm != null) RegisterAlgorithmEvents();
+      if (Problem != null) RegisterProblemEvents();
+      AlgorithmParameter.ValueChanged += AlgorithmParameterOnValueChanged;
+      StoreSolutionInRunParameter.Value.ValueChanged += StoreSolutionInRunOnValueChanged;
+      Analyzer.Operators.CollectionReset += AnalyzerOperatorsChanged;
+      Analyzer.Operators.ItemsAdded += AnalyzerOperatorsChanged;
+      Analyzer.Operators.ItemsRemoved += AnalyzerOperatorsChanged;
+      Analyzer.Operators.ItemsReplaced += AnalyzerOperatorsChanged;
+      MaximumEvaluationsParameter.Value.ValueChanged += MaximumEvaluationsParameterOnValueChanged;
+      TargetQualityParameter.ValueChanged += TargetQualityParameterChanged;
+      if (TargetQualityParameter.Value != null) TargetQualityParameter.Value.ValueChanged += TargetQualityParameterOnValueChanged;
+    }
+
+    private bool suppressAnalyzerOperatorEvents = false;
+
+    private void AnalyzerOperatorsChanged(object sender, CollectionItemsChangedEventArgs<IndexedItem<IAnalyzer>> e) {
+      if (suppressAnalyzerOperatorEvents) return;
+      if (!Analyzer.Operators.Contains(perClockAnalyzer)) {
+        suppressAnalyzerOperatorEvents = true;
+        try { Analyzer.Operators.Add(perClockAnalyzer, false); } finally { suppressAnalyzerOperatorEvents = false; }
+      }
+      if (!Analyzer.Operators.Contains(perEvaluationsAnalyzer)) {
+        suppressAnalyzerOperatorEvents = true;
+        try { Analyzer.Operators.Add(perEvaluationsAnalyzer, false); } finally { suppressAnalyzerOperatorEvents = false; }
+      }
+      if (!Analyzer.Operators.Contains(bestSolutionAnalyzer)) {
+        suppressAnalyzerOperatorEvents = true;
+        try { Analyzer.Operators.Add(bestSolutionAnalyzer, false); } finally { suppressAnalyzerOperatorEvents = false; }
+      }
+    }
+
+    private void StoreSolutionInRunOnValueChanged(object sender, EventArgs eventArgs) {
+      Analyzer.Operators.SetItemCheckedState(bestSolutionAnalyzer, StoreSolutionInRun);
+    }
+
+    private void AlgorithmParameterOnValueChanged(object sender, EventArgs eventArgs) {
+      Algorithm = AlgorithmParameter.Value;
+    }
+
+    #region Prepare, Start, Pause, Stop
+    public override void Prepare() {
+      if (Problem == null || Algorithm == null) return;
+
+      Algorithm.Prepare(true);
+
+      results.Clear();
+      OnPrepared();
+    }
+
+    public override void Start() {
+      base.Start();
+      OnStarted();
+      Task.Factory.StartNew(Run, null).ContinueWith(t => {
+        try {
+          t.Wait();
+        } catch (AggregateException ex) {
+          try {
+            ex.Flatten().Handle(x => x is OperationCanceledException);
+          } catch (AggregateException remaining) {
+            if (remaining.InnerExceptions.Count == 1) OnExceptionOccurred(remaining.InnerExceptions[0]);
+            else OnExceptionOccurred(remaining);
+          }
+        }
+        if (Algorithm.ExecutionState == ExecutionState.Paused) OnPaused();
+        else OnStopped();
+        forceStop = false;
+      });
+    }
+
+    public override void Pause() {
+      base.Pause();
+      Algorithm.Pause();
+    }
+
+    private bool forceStop = false;
+    public override void Stop() {
+      base.Stop();
+      if (ExecutionState == ExecutionState.Started) {
+        forceStop = true;
+        Algorithm.Stop();
+      } else if (ExecutionState == ExecutionState.Paused) {
+        RoundupResults();
+        Algorithm.Prepare(true);
+        OnStopped();
+      }
+    }
+    #endregion
+
+    private DateTime lastUpdateTime;
+    private void Run(object state) {
+      lastUpdateTime = DateTime.UtcNow;
+      System.Timers.Timer timer = new System.Timers.Timer(250);
+      timer.AutoReset = true;
+      timer.Elapsed += timer_Elapsed;
+      timer.Start();
+      try {
+        Run();
+      } finally {
+        timer.Elapsed -= timer_Elapsed;
+        timer.Stop();
+        ExecutionTime += DateTime.UtcNow - lastUpdateTime;
+      }
+    }
+
+    private readonly AutoResetEvent algorithmWaitHandle = new AutoResetEvent(false);
+    private void Run() {
+      if (!Results.ContainsKey(ExecutionTimeResultName)) Results.Add(new Result(ExecutionTimeResultName, new TimeSpanValue(TimeSpan.Zero)));
+      if (!Results.ContainsKey(EvaluatedSolutionsResultName)) Results.Add(new Result(EvaluatedSolutionsResultName, new DoubleValue(0)));
+      if (!Results.ContainsKey(EvaluatedMovesResultName)) Results.Add(new Result(EvaluatedMovesResultName, new DoubleValue(0)));
+      if (!Results.ContainsKey(EvaluationsResultName)) Results.Add(new Result(EvaluationsResultName, new DoubleValue(0)));
+      if (!Results.ContainsKey(BestQualityResultName)) Results.Add(new Result(BestQualityResultName, new DoubleValue(double.NaN)));
+
+      do {
+        if (!Results.ContainsKey(RandomRestartsResultName)) Results.Add(new Result(RandomRestartsResultName, new IntValue(0)));
+        else if (Algorithm.ExecutionState == ExecutionState.Prepared) ((IntValue)Results[RandomRestartsResultName].Value).Value++;
+
+        Algorithm.Start();
+        algorithmWaitHandle.WaitOne();
+
+        if (Algorithm.ExecutionState == ExecutionState.Paused) return;
+
+        RoundupResults();
+
+        Algorithm.Prepare(true);
+      } while (!IsFinished);
+    }
+
+    private void RoundupResults() {
+      if (Algorithm == null) return;
+      var execTime = ((TimeSpanValue)Results[ExecutionTimeResultName].Value);
+      var solEvals = ((DoubleValue)Results[EvaluatedSolutionsResultName].Value);
+      var movEvals = ((DoubleValue)Results[EvaluatedMovesResultName].Value);
+      var restarts = ((IntValue)Results[RandomRestartsResultName].Value);
+      var evaluations = ((DoubleValue)Results[EvaluationsResultName].Value);
+      var bestQuality = ((DoubleValue)Results[BestQualityResultName].Value);
+      var improvement = false;
+
+      IResult result;
+      if (Algorithm.Results.TryGetValue(perEvaluationsAnalyzer.EvaluatedSolutionsParameter.ActualName, out result)) {
+        var evals = ((IntValue)result.Value).Value;
+        evaluations.Value += evals;
+        solEvals.Value += evals;
+      }
+      if (Algorithm.Results.TryGetValue(perEvaluationsAnalyzer.EvaluatedMovesParameter.ActualName, out result)) {
+        var evals = ((IntValue)result.Value).Value;
+        evaluations.Value += MoveCostPerSolution * evals;
+        movEvals.Value += evals;
+      }
+      if (Algorithm.Results.TryGetValue(perEvaluationsAnalyzer.BestQualityParameter.ActualName, out result)) {
+        var newBestQuality = ((DoubleValue)result.Value).Value;
+        if (double.IsNaN(bestQuality.Value)
+            || Maximization && newBestQuality > bestQuality.Value
+            || !Maximization && newBestQuality < bestQuality.Value) {
+          bestQuality.Value = newBestQuality;
+          improvement = true;
+        }
+      }
+      if (Algorithm.Results.TryGetValue(perClockAnalyzer.QualityPerClockParameter.ActualName, out result)) UpdateQualityPerClockResult((IndexedDataTable<double>)result.Value, restarts.Value);
+      if (Algorithm.Results.TryGetValue(perEvaluationsAnalyzer.QualityPerEvaluationsParameter.ActualName, out result)) UpdateQualityPerEvaluationsResult((IndexedDataTable<double>)result.Value, restarts.Value);
+      if (StoreSolutionInRun) {
+        foreach (var r in Algorithm.Results) {
+          if (r.Name == bestSolutionAnalyzer.BestSolutionResultName || r.Name.ToLower().EndsWith("solution") && improvement) {
+            if (!Results.TryGetValue(r.Name, out result))
+              Results.Add(new Result(r.Name, (IItem)r.Value.Clone()));
+            else result.Value = (IItem)r.Value.Clone();
+          }
+        }
+      }
+
+      execTime.Value = ExecutionTime;
+    }
+
+    private void UpdateQualityPerClockResult(IndexedDataTable<double> perClock, int restarts) {
+      IndexedDataTable<double> dt;
+      if (!Results.ContainsKey(QualityPerClockResultName)) {
+        dt = (IndexedDataTable<double>)perClock.Clone();
+        if (!dt.Rows.ContainsKey("Restarts"))
+          dt.Rows.Add(new IndexedDataRow<double>("Restarts") {
+            VisualProperties = {
+              ChartType = DataRowVisualProperties.DataRowChartType.StepLine,
+              SecondYAxis = true
+            }
+          });
+        foreach (var v in dt.Rows.First().Values)
+          dt.Rows["Restarts"].Values.Add(Tuple.Create(v.Item1, 0.0));
+        Results.Add(new Result(QualityPerClockResultName, dt));
+      } else {
+        dt = (IndexedDataTable<double>)Results[QualityPerClockResultName].Value;
+        var qualityValues = dt.Rows.First().Values;
+        var restartValues = dt.Rows["Restarts"].Values;
+        var best = qualityValues.Last().Item2;
+        var execTime = qualityValues.Last().Item1;
+        var improvement = false;
+        foreach (var tupl in perClock.Rows.First().Values) {
+          if (Maximization && tupl.Item2 > best || !Maximization && tupl.Item2 < best) {
+            if (!improvement) {
+              // the last entry always holds the same value, but with highest execution time
+              qualityValues.RemoveAt(qualityValues.Count - 1);
+              restartValues.RemoveAt(restartValues.Count - 1);
+              improvement = true;
+            }
+            qualityValues.Add(Tuple.Create(execTime + tupl.Item1, tupl.Item2));
+            restartValues.Add(Tuple.Create(execTime + tupl.Item1, (double)restarts));
+            best = tupl.Item2;
+          }
+        }
+        if (!improvement) {
+          qualityValues.RemoveAt(qualityValues.Count - 1);
+          restartValues.RemoveAt(restartValues.Count - 1);
+        }
+        var totalExecTime = execTime + perClock.Rows.First().Values.Last().Item1;
+        qualityValues.Add(Tuple.Create(totalExecTime, best));
+        restartValues.Add(Tuple.Create(totalExecTime, (double)restarts));
+      }
+    }
+
+    private void UpdateQualityPerEvaluationsResult(IndexedDataTable<double> perEvaluations, int restarts) {
+      IndexedDataTable<double> dt;
+      if (!Results.ContainsKey(QualityPerEvaluationsResultName)) {
+        dt = (IndexedDataTable<double>)perEvaluations.Clone();
+        if (!dt.Rows.ContainsKey("Restarts"))
+          dt.Rows.Add(new IndexedDataRow<double>("Restarts") {
+            VisualProperties = {
+              ChartType = DataRowVisualProperties.DataRowChartType.StepLine,
+              SecondYAxis = true
+            }
+          });
+        foreach (var v in dt.Rows.First().Values)
+          dt.Rows["Restarts"].Values.Add(Tuple.Create(v.Item1, 0.0));
+        Results.Add(new Result(QualityPerEvaluationsResultName, dt));
+      } else {
+        dt = (IndexedDataTable<double>)Results[QualityPerEvaluationsResultName].Value;
+        var qualityValues = dt.Rows.First().Values;
+        var restartValues = dt.Rows["Restarts"].Values;
+        var best = qualityValues.Last().Item2;
+        var evaluations = qualityValues.Last().Item1;
+        var improvement = false;
+        foreach (var tupl in perEvaluations.Rows.First().Values) {
+          if (Maximization && tupl.Item2 > best || !Maximization && tupl.Item2 < best) {
+            if (!improvement) {
+              // the last entry always holds the same value, but with highest evaluations
+              qualityValues.RemoveAt(qualityValues.Count - 1);
+              restartValues.RemoveAt(restartValues.Count - 1);
+              improvement = true;
+            }
+            qualityValues.Add(Tuple.Create(evaluations + tupl.Item1, tupl.Item2));
+            restartValues.Add(Tuple.Create(evaluations + tupl.Item1, (double)restarts));
+            best = tupl.Item2;
+          }
+        }
+        if (!improvement) {
+          // add the best quality again as value with highest evaluations
+          qualityValues.RemoveAt(qualityValues.Count - 1);
+          restartValues.RemoveAt(restartValues.Count - 1);
+        }
+        var totalEvaluations = evaluations + perEvaluations.Rows.First().Values.Last().Item1;
+        qualityValues.Add(Tuple.Create(totalEvaluations, best));
+        restartValues.Add(Tuple.Create(totalEvaluations, (double)restarts));
+      }
+    }
+
+    private void UpdateTargetValueFromBestKnownQuality() {
+      var bkParam = ((ISingleObjectiveHeuristicOptimizationProblem)Problem).BestKnownQualityParameter as IValueParameter<DoubleValue>;
+      if (bkParam != null && bkParam.Value != null)
+        TargetQualityParameter.Value = new DoubleValue(bkParam.Value.Value);
+      else if (bkParam != null && bkParam.Value == null)
+        TargetQualityParameter.Value = null;
+    }
+
+    private void AddAlgorithmAnalyzers() {
+      if (Algorithm == null) return;
+      if (!Algorithm.Parameters.ContainsKey("Analyzer")) return;
+      var analyzerParam = Algorithm.Parameters["Analyzer"] as IValueParameter<MultiAnalyzer>;
+      if (analyzerParam != null) {
+        if (analyzerParam.Value.Operators.Contains(Analyzer)) return;
+        analyzerParam.Value.Operators.Add(Analyzer, true);
+      } else {
+        var analyzerParam2 = Algorithm.Parameters["Analyzer"] as IValueParameter<IMultiAnalyzer>;
+        if (analyzerParam2 == null) return;
+        if (analyzerParam2.Value.Operators.Contains(Analyzer)) return;
+        analyzerParam2.Value.Operators.Add(Analyzer, true);
+      }
+    }
+
+    private void RemoveAlgorithmAnalyzers() {
+      if (Algorithm == null) return;
+      if (!Algorithm.Parameters.ContainsKey("Analyzer")) return;
+      var analyzerParam = Algorithm.Parameters["Analyzer"] as IValueParameter<MultiAnalyzer>;
+      if (analyzerParam != null) {
+        analyzerParam.Value.Operators.Remove(Analyzer);
+      } else {
+        var analyzerParam2 = Algorithm.Parameters["Analyzer"] as IValueParameter<IMultiAnalyzer>;
+        if (analyzerParam2 != null) {
+          analyzerParam2.Value.Operators.Remove(Analyzer);
+        }
+      }
+    }
+
+    #region Event Handlers
+    private void Algorithm_ExceptionOccurred(object sender, EventArgs<Exception> e) {
+      OnExceptionOccurred(e.Value);
+    }
+    private void Algorithm_Paused(object sender, EventArgs e) {
+      if (ExecutionState == ExecutionState.Paused) return;
+      algorithmWaitHandle.Set();
+    }
+    private void Algorithm_Stopped(object sender, EventArgs e) {
+      if (ExecutionState == ExecutionState.Paused) return;
+      algorithmWaitHandle.Set();
+    }
+
+    private void Algorithm_ProblemChanged(object sender, EventArgs e) {
+      if (Algorithm.Problem != Problem) Problem = (ISingleObjectiveHeuristicOptimizationProblem)Algorithm.Problem;
+      AddAlgorithmAnalyzers();
+    }
+
+    private void AlgorithmTargetQualityParameterOnChanged(object sender, EventArgs e) {
+      var doubleValueParam = (sender as IValueParameter<DoubleValue>);
+      if (doubleValueParam.Value != null) doubleValueParam.Value.ValueChanged += AlgorithmTargetQualityOnValueChanged;
+      HandleAlgorithmTargetQualityChanged();
+    }
+
+    private void AlgorithmTargetQualityOnValueChanged(object sender, EventArgs e) {
+      HandleAlgorithmTargetQualityChanged();
+    }
+
+    private void AlgorithmMaximumEvaluationsParameterOnChanged(object sender, EventArgs eventArgs) {
+      var intValueParam = (sender as IValueParameter<IntValue>);
+      if (intValueParam.Value == null) {
+        intValueParam.Value = new IntValue();
+        return;
+      }
+      intValueParam.Value.ValueChanged += AlgorithmMaximumEvaluationsOnValueChanged;
+      HandleAlgorithmMaximumEvaluationsChanged();
+    }
+
+    private void AlgorithmMaximumEvaluationsOnValueChanged(object sender, EventArgs eventArgs) {
+      var intValue = (IntValue)sender;
+      HandleAlgorithmMaximumEvaluationsChanged();
+    }
+
+    private void Problem_BestKnownQualityParameter_ValueChanged(object sender, EventArgs e) {
+      var param = sender as IValueParameter<DoubleValue>;
+      if (param != null) {
+        if (param.Value != null) param.Value.ValueChanged += Problem_BestKnownQualityParameter_Value_ValueChanged;
+        UpdateTargetValueFromBestKnownQuality();
+      }
+    }
+
+    private void Problem_BestKnownQualityParameter_Value_ValueChanged(object sender, EventArgs e) {
+      UpdateTargetValueFromBestKnownQuality();
+    }
+
+    protected override void Problem_Reset(object sender, EventArgs eventArgs) {
+      if (Algorithm != null) AddAlgorithmAnalyzers();
+    }
+
+    protected override void Problem_OperatorsChanged(object sender, EventArgs eventArgs) {
+      if (Algorithm != null) AddAlgorithmAnalyzers();
+    }
+
+    protected override void OnProblemChanged() {
+      base.OnProblemChanged();
+      if (Algorithm != null) Algorithm.Problem = Problem;
+      if (Problem != null) UpdateTargetValueFromBestKnownQuality();
+    }
+
+    private void MaximumEvaluationsParameterOnValueChanged(object sender, EventArgs eventArgs) {
+      SynchronizeMaximumEvaluationsParameter();
+    }
+
+    private void TargetQualityParameterChanged(object sender, EventArgs e) {
+      if (TargetQualityParameter.Value != null) TargetQualityParameter.Value.ValueChanged += TargetQualityParameterOnValueChanged;
+      HandleAlgorithmTargetQualityChanged();
+    }
+
+    private void TargetQualityParameterOnValueChanged(object sender, EventArgs eventArgs) {
+      HandleAlgorithmTargetQualityChanged();
+    }
+
+    private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
+      System.Timers.Timer timer = (System.Timers.Timer)sender;
+      timer.Enabled = false;
+      DateTime now = DateTime.UtcNow;
+      ExecutionTime += now - lastUpdateTime;
+      lastUpdateTime = now;
+      timer.Enabled = true;
+    }
+    #endregion
+
+    private void HandleAlgorithmTargetQualityChanged() {
+      if (Algorithm == null) return;
+      IParameter param;
+      if (!Algorithm.Parameters.TryGetValue("TargetQuality", out param)) return;
+      var doubleValueParam = (param as IValueParameter<DoubleValue>);
+      if (doubleValueParam == null) return;
+      if (doubleValueParam.Value == null && TargetQualityParameter.Value != null) {
+        doubleValueParam.Value = new DoubleValue(TargetQualityParameter.Value.Value);
+      } else if (doubleValueParam.Value != null && TargetQualityParameter.Value == null) {
+        doubleValueParam.Value = null;
+      } else if (doubleValueParam.Value != null && TargetQualityParameter.Value != null) {
+        doubleValueParam.Value.Value = TargetQualityParameter.Value.Value;
+      }
+    }
+
+    private void HandleAlgorithmMaximumEvaluationsChanged() {
+      if (Algorithm == null) return;
+      IParameter param;
+      if (!Algorithm.Parameters.TryGetValue("MaximumEvaluations", out param)) return;
+      var intValueParam = (param as IValueParameter<IntValue>);
+      if (intValueParam == null) return;
+      var value = intValueParam.Value;
+      if (value.Value > MaximumEvaluationsParameter.Value.Value)
+        value.Value = MaximumEvaluationsParameter.Value.Value;
+    }
+
+    private void SynchronizeMaximumEvaluationsParameter() {
+      if (Algorithm == null) return;
+      IParameter param;
+      if (!Algorithm.Parameters.TryGetValue("MaximumEvaluations", out param)) return;
+      var intValueParam = (param as IValueParameter<IntValue>);
+      if (intValueParam == null) return;
+      if (intValueParam.Value == null) intValueParam.Value = new IntValue(MaximumEvaluationsParameter.Value.Value);
+      else intValueParam.Value.Value = MaximumEvaluationsParameter.Value.Value;
+    }
+  }
+}
Index: /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/QualityAnalysis/QualityDistributionAnalyzer.cs
===================================================================
--- /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/QualityAnalysis/QualityDistributionAnalyzer.cs	(revision 15719)
+++ /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/QualityAnalysis/QualityDistributionAnalyzer.cs	(revision 15720)
@@ -20,5 +20,4 @@
 #endregion
 
-using System.Linq;
 using HeuristicLab.Common;
 using HeuristicLab.Core;
@@ -28,4 +27,6 @@
 using HeuristicLab.Parameters;
 using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+using System.Collections.Generic;
+using System.Linq;
 
 namespace HeuristicLab.Analysis.QualityAnalysis {
@@ -33,4 +34,5 @@
   [StorableClass]
   public class QualityDistributionAnalyzer : SingleSuccessorOperator, IAnalyzer, IIterationBasedOperator, ISingleObjectiveOperator {
+    private const string TableDescription = "Shows the quality distributions in the current population.";
 
     #region Parameter properties
@@ -93,40 +95,48 @@
     }
 
+    public static DataTable PrepareTable(string qualityName = "Quality") {
+      var result = new DataTable("Population Quality Distribution", TableDescription);
+      result.VisualProperties.XAxisTitle = qualityName;
+      result.VisualProperties.YAxisTitle = "Frequency";
+
+      var row = new DataRow("QualityDistribution");
+      row.VisualProperties.ChartType = DataRowVisualProperties.DataRowChartType.Histogram;
+      result.Rows.Add(row);
+
+      return result;
+    }
+
+    public static void UpdateTable(DataTable table, IEnumerable<double> qualities) {
+      var row = table.Rows["QualityDistribution"];
+      row.Values.Replace(qualities);
+    }
+
     public override IOperation Apply() {
       DataTable qualityDistribution = null;
-      ResultCollection results = ResultsParameter.ActualValue;
-      string description = "Shows the quality distributions in the current population.";
+      var results = ResultsParameter.ActualValue;
       if (results.ContainsKey(HistogramName)) {
-        qualityDistribution = results[HistogramName].Value as DataTable;
+        qualityDistribution = (DataTable)results[HistogramName].Value;
       } else {
-        qualityDistribution = new DataTable("Population Quality Distribution", description);
-        qualityDistribution.VisualProperties.XAxisTitle = QualityParameter.ActualName;
-        qualityDistribution.VisualProperties.YAxisTitle = "Frequency";
-        results.Add(new Result(HistogramName, description, qualityDistribution));
+        qualityDistribution = PrepareTable(QualityParameter.ActualName);
+        results.Add(new Result(HistogramName, TableDescription, qualityDistribution));
       }
-      DataRow row;
-      if (!qualityDistribution.Rows.TryGetValue("QualityDistribution", out row)) {
-        row = new DataRow("QualityDistribution");
-        row.VisualProperties.ChartType = DataRowVisualProperties.DataRowChartType.Histogram;
-        qualityDistribution.Rows.Add(row);
-      }
-      var qualities = QualityParameter.ActualValue;
-      row.Values.Replace(qualities.Select(x => x.Value));
+
+      UpdateTable(qualityDistribution, QualityParameter.ActualValue.Select(x => x.Value));
 
       if (StoreHistory) {
-        string historyResultName = HistogramName + " History";
+        var historyResultName = HistogramName + " History";
         DataTableHistory qdHistory = null;
         if (results.ContainsKey(historyResultName)) {
-          qdHistory = results[historyResultName].Value as DataTableHistory;
+          qdHistory = (DataTableHistory)results[historyResultName].Value;
         } else {
           qdHistory = new DataTableHistory();
           results.Add(new Result(historyResultName, qdHistory));
         }
-        DataTable table = (DataTable)qualityDistribution.Clone();
-        IntValue iteration = IterationsParameter.ActualValue;
+        var table = (DataTable)qualityDistribution.Clone();
+        var iteration = IterationsParameter.ActualValue;
         if (iteration != null) {
-          string iterationName = IterationsParameter.ActualName;
+          var iterationName = IterationsParameter.ActualName;
           if (iterationName.EndsWith("s")) iterationName = iterationName.Remove(iterationName.Length - 1);
-          string appendix = " at " + iterationName + " " + iteration.Value.ToString();
+          var appendix = " at " + iterationName + " " + iteration.Value.ToString();
           table.Name += appendix;
           table.Rows["QualityDistribution"].VisualProperties.DisplayName += appendix;
Index: /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/SelfOrganizingMaps/RelationalSOM.cs
===================================================================
--- /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/SelfOrganizingMaps/RelationalSOM.cs	(revision 15720)
+++ /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/SelfOrganizingMaps/RelationalSOM.cs	(revision 15720)
@@ -0,0 +1,127 @@
+﻿#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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using HeuristicLab.Core;
+using HeuristicLab.Data;
+using HeuristicLab.Random;
+using System;
+using System.Linq;
+
+namespace HeuristicLab.Analysis.SelfOrganizingMaps {
+  public static class RelationalSOM {
+    /// <summary>
+    /// This is the online algorithm described in
+    /// Olteanu, M. and Villa-Vialaneix, N. 2015.
+    /// On-line relational and multiple relational SOM.
+    /// Neurocomputing 147, pp. 15-30.
+    /// </summary>
+    /// <param name="dissimilarities">The full NxN matrix containing all dissimilarities between N points.</param>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="somSize">The length of a side of the SOM grid (there are somSize * somSize neurons).</param>
+    /// <param name="iterations">The amount of iterations to perform in learning.</param>
+    /// <param name="learningRate">The initial learning rate.</param>
+    /// <param name="learningRadius">The initial learning radius.</param>
+    /// <param name="jittering">If the final coordinates should be jittered slightly within the grid.</param>
+    /// <returns>A matrix of coordinates having N rows and 2 columns.</returns>
+    public static DoubleMatrix Map(DoubleMatrix dissimilarities, IRandom random = null, int somSize = 5, int iterations = 100, double learningRate = double.NaN, double learningRadius = 5.0, bool jittering = true) {
+      if (random == null) random = new MersenneTwister();
+      var K = somSize * somSize;
+      var N = dissimilarities.Rows;
+      if (double.IsNaN(learningRate)) learningRate = 1.0 / Math.Sqrt(2.0 * N);
+      var fixedLearningRate = learningRate / 10.0;
+      var varLearningRate = 9.0 * fixedLearningRate;
+      Func<int, int> getX = (neuron) => neuron % somSize;
+      Func<int, int> getY = (neuron) => neuron / somSize;
+      Func<int, int, int, int, double> neighborhood = (maxIter, iter, k, bmu) => {
+        var sigma = learningRadius * ((maxIter - iter) / (double)maxIter) + 0.0001;
+        var xK = getX(k);
+        var yK = getY(k);
+        var xW = getX(bmu);
+        var yW = getY(bmu);
+        var d = (xK - xW) * (xK - xW) + (yK - yW) * (yK - yW);
+        return Math.Exp(-d / (2.0 * sigma * sigma));
+      };
+      var alphas = Enumerable.Range(0, K).Select(k => Enumerable.Range(0, N).Select(_ => random.NextDouble()).ToArray()).ToArray();
+      // normalize s.t. sum(alphas[k]) = 1
+      for (var k = 0; k < K; k++) {
+        var sum = alphas[k].Sum();
+        for (var i = 0; i < alphas[k].Length; i++) alphas[k][i] /= sum;
+      }
+      var oldAlphas = alphas.Select(x => (double[])x.Clone()).ToArray();
+
+      for (var iter = 0; iter < iterations; iter++) {
+        learningRate = varLearningRate * ((iterations - iter) / (double)iterations) + fixedLearningRate;
+        var pointShuffle = Enumerable.Range(0, N).Shuffle(random).ToArray();
+        for (var p = 0; p < N; p++) {
+          var i = pointShuffle[p];
+          var bmu = GetBestMatchingUnit(dissimilarities, alphas, i);
+
+          for (var k = 0; k < K; k++) {
+            for (var j = 0; j < N; j++) {
+              alphas[k][j] = oldAlphas[k][j] + learningRate * neighborhood(iterations, iter, k, bmu) * ((i == j ? 1.0 : 0.0) - oldAlphas[k][j]);
+            }
+          }
+        }
+        for (var k = 0; k < K; k++) {
+          for (var j = 0; j < N; j++) {
+            oldAlphas[k][j] = alphas[k][j];
+          }
+        }
+      }
+
+      var result = new DoubleMatrix(N, 2);
+      for (var i = 0; i < N; i++) {
+        var bmu = GetBestMatchingUnit(dissimilarities, alphas, i);
+        if (!jittering) {
+          result[i, 0] = getX(bmu);
+          result[i, 1] = getY(bmu);
+        } else {
+          result[i, 0] = getX(bmu) + random.NextDouble() * 0.8;
+          result[i, 1] = getY(bmu) + random.NextDouble() * 0.8;
+        }
+      }
+      return result;
+    }
+
+    private static int GetBestMatchingUnit(DoubleMatrix D, double[][] alphas, int i) {
+      var bmu = -1;
+      var minV = double.MaxValue;
+      for (var k = 0; k < alphas.Length; k++) {
+        var Daki = 0.0;
+        var akDak = 0.0;
+        for (var r = 0; r < D.Rows; r++) {
+          var Dakr = 0.0;
+          for (var s = 0; s < D.Rows; s++) {
+            Dakr += D[r, s] * alphas[k][s];
+          }
+          if (r == i) Daki = Dakr;
+          akDak += alphas[k][r] * Dakr;
+        }
+        var v = Daki - 0.5 * akDak;
+        if (v < minV) {
+          bmu = k;
+          minV = v;
+        }
+      }
+      return bmu;
+    }
+  }
+}
Index: /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/SelfOrganizingMaps/SOM.cs
===================================================================
--- /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/SelfOrganizingMaps/SOM.cs	(revision 15720)
+++ /branches/1614_GeneralizedQAP/HeuristicLab.Analysis/3.3/SelfOrganizingMaps/SOM.cs	(revision 15720)
@@ -0,0 +1,132 @@
+﻿#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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using HeuristicLab.Common;
+using HeuristicLab.Core;
+using HeuristicLab.Data;
+using HeuristicLab.Random;
+using System;
+using System.Linq;
+
+namespace HeuristicLab.Analysis.SelfOrganizingMaps {
+  public static class SOM {
+    /// <summary>
+    /// Performs a self-organizing map to a range of feature column-vectors organized in a matrix.
+    /// </summary>
+    /// <param name="features">The feature matrix containing the column-vectors.</param>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="somSize">The length of a side of the SOM grid (there are somSize * somSize neurons).</param>
+    /// <param name="iterations">The amount of iterations to perform in learning.</param>
+    /// <param name="learningRate">The initial learning rate.</param>
+    /// <param name="learningRadius">The initial learning radius.</param>
+    /// <param name="jittering">If the final coordinates should be jittered slightly within the grid.</param>
+    /// <returns>A matrix of coordinates having N rows and 2 columns.</returns>
+    public static DoubleMatrix Map(DoubleMatrix features, IRandom random = null, int somSize = 5, int iterations = 100, double learningRate = double.NaN, double learningRadius = 5.0, bool jittering = true) {
+      if (random == null) random = new MersenneTwister();
+      var K = somSize * somSize;
+      var N = features.Columns;
+      var L = features.Rows;
+
+      if (double.IsNaN(learningRate)) learningRate = 1.0 / Math.Sqrt(2.0 * L);
+      var fixedLearningRate = learningRate / 10.0;
+      var varLearningRate = 9.0 * fixedLearningRate;
+      Func<int, int> getX = (neuron) => neuron % somSize;
+      Func<int, int> getY = (neuron) => neuron / somSize;
+      Func<int, int, int, int, double> neighborhood = (maxIter, iter, k, bmu) => {
+        var sigma = learningRadius * ((maxIter - iter) / (double)maxIter) + 0.0001;
+        var xK = getX(k);
+        var yK = getY(k);
+        var xW = getX(bmu);
+        var yW = getY(bmu);
+        var d = (xK - xW) * (xK - xW) + (yK - yW) * (yK - yW);
+        return Math.Exp(-d / (2.0 * sigma * sigma));
+      };
+      var scaledFeatures = Scale(features);
+      var weights = Enumerable.Range(0, K).Select(k => Enumerable.Range(0, L).Select(_ => random.NextDouble()).ToArray()).ToArray();
+      // normalize s.t. sum(alphas[k]) = 1
+      for (var k = 0; k < K; k++) {
+        var sum = weights[k].Sum();
+        for (var i = 0; i < weights[k].Length; i++) weights[k][i] /= sum;
+      }
+      var oldWeights = weights.Select(x => (double[])x.Clone()).ToArray();
+
+      for (var iter = 0; iter < iterations; iter++) {
+        learningRate = varLearningRate * ((iterations - iter) / (double)iterations) + fixedLearningRate;
+        var pointShuffle = Enumerable.Range(0, N).Shuffle(random).ToArray();
+        for (var p = 0; p < N; p++) {
+          var i = pointShuffle[p];
+          var bmu = GetBestMatchingUnit(scaledFeatures, weights, i);
+
+          for (var k = 0; k < K; k++) {
+            for (var j = 0; j < L; j++) {
+              weights[k][j] = oldWeights[k][j] + learningRate * neighborhood(iterations, iter, k, bmu) * (scaledFeatures[j, i] - oldWeights[k][j]);
+            }
+          }
+        }
+        for (var k = 0; k < K; k++) {
+          for (var j = 0; j < L; j++) {
+            oldWeights[k][j] = weights[k][j];
+          }
+        }
+      }
+
+      var result = new DoubleMatrix(N, 2);
+      for (var i = 0; i < N; i++) {
+        var bmu = GetBestMatchingUnit(scaledFeatures, weights, i);
+        if (!jittering) {
+          result[i, 0] = getX(bmu);
+          result[i, 1] = getY(bmu);
+        } else {
+          result[i, 0] = getX(bmu) + random.NextDouble() * 0.8;
+          result[i, 1] = getY(bmu) + random.NextDouble() * 0.8;
+        }
+      }
+      return result;
+    }
+
+    private static int GetBestMatchingUnit(DoubleMatrix D, double[][] weights, int i) {
+      var bmu = -1;
+      var minV = double.MaxValue;
+      for (var k = 0; k < weights.Length; k++) {
+        var d = 0.0;
+        for (var r = 0; r < weights[k].Length; r++) {
+          d += (D[r, i] - weights[k][r]) * (D[r, i] - weights[k][r]);
+        }
+        if (d < minV) {
+          minV = d;
+          bmu = k;
+        }
+      }
+      return bmu;
+    }
+
+    private static DoubleMatrix Scale(DoubleMatrix features) {
+      var scaledFeatures = new DoubleMatrix(features.Rows, features.Columns);
+      for (var i = 0; i < features.Rows; i++) {
+        var rowSum = 0.0;
+        for (var j = 0; j < features.Columns; j++) rowSum += features[i, j];
+        if (rowSum.IsAlmost(0.0)) rowSum = 1.0;
+        for (var j = 0; j < features.Columns; j++) scaledFeatures[i, j] = features[i, j] / rowSum;
+      }
+      return scaledFeatures;
+    }
+  }
+}
