Index: branches/PerformanceComparison/HeuristicLab.OptimizationExpertSystem/3.3/Views/UnderstandingSolutionsView.cs
===================================================================
--- branches/PerformanceComparison/HeuristicLab.OptimizationExpertSystem/3.3/Views/UnderstandingSolutionsView.cs	(revision 13722)
+++ branches/PerformanceComparison/HeuristicLab.OptimizationExpertSystem/3.3/Views/UnderstandingSolutionsView.cs	(revision 13743)
@@ -28,4 +28,5 @@
 using HeuristicLab.Optimization;
 using HeuristicLab.OptimizationExpertSystem.Common;
+using HeuristicLab.Random;
 using System;
 using System.Collections.Generic;
@@ -42,4 +43,5 @@
     public UnderstandingSolutionsView() {
       InitializeComponent();
+      solutionNetworkProjectionComboBox.SelectedIndex = 0;
     }
 
@@ -66,5 +68,5 @@
     protected override void OnSolutionSeedingPoolChanged() {
       base.OnSolutionSeedingPoolChanged();
-      UpdateSolutionNetworkAnalysis(similarityComboBox.SelectedItem as ISolutionSimilarityCalculator);
+      UpdateSolutionNetworkAnalysis(similarityComboBox.SelectedItem as ISolutionSimilarityCalculator, (string)solutionNetworkProjectionComboBox.SelectedItem);
     }
 
@@ -107,5 +109,5 @@
         UpdateSolutionFdcAnalysis(calculator, fdcBetweenBestCheckBox.Checked);
         UpdateSolutionLengthScaleAnalysis(calculator);
-        UpdateSolutionNetworkAnalysis(calculator);
+        UpdateSolutionNetworkAnalysis(calculator, (string)solutionNetworkProjectionComboBox.SelectedItem);
       } else {
         solutionsDiversityViewHost.Content = null;
@@ -221,5 +223,5 @@
     }
 
-    private void UpdateSolutionNetworkAnalysis(ISolutionSimilarityCalculator calculator) {
+    private void UpdateSolutionNetworkAnalysis(ISolutionSimilarityCalculator calculator, string projection) {
       var series = solutionsNetworkChart.Series["SolutionSeries"];
       var seedingSeries = solutionsNetworkChart.Series["SeedingSolutionSeries"];
@@ -235,5 +237,8 @@
           }
         }
-        var coords = MultidimensionalScaling.KruskalShepard(dissimilarities);
+        DoubleMatrix coords = null;
+        if (projection == "SOM")
+          coords = Som(dissimilarities, new MersenneTwister(42), jittering: true);
+        else coords = MultidimensionalScaling.KruskalShepard(dissimilarities);
         for (var i = 0; i < coords.Rows; i++) {
           var quality = GetQuality(solutionScopes[i], calculator.QualityVariableName) ?? double.NaN;
@@ -266,5 +271,5 @@
       UpdateSolutionFdcAnalysis(calculator, fdcBetweenBestCheckBox.Checked);
       UpdateSolutionLengthScaleAnalysis(calculator);
-      UpdateSolutionNetworkAnalysis(calculator);
+      UpdateSolutionNetworkAnalysis(calculator, (string)solutionNetworkProjectionComboBox.SelectedItem);
     }
 
@@ -279,5 +284,5 @@
       UpdateSolutionFdcAnalysis(calculator, fdcBetweenBestCheckBox.Checked);
       UpdateSolutionLengthScaleAnalysis(calculator);
-      UpdateSolutionNetworkAnalysis(calculator);
+      UpdateSolutionNetworkAnalysis(calculator, (string)solutionNetworkProjectionComboBox.SelectedItem);
     }
 
@@ -285,4 +290,14 @@
       if (InvokeRequired) { Invoke((Action<object, EventArgs>)FdcBetweenBestCheckBoxOnCheckedChanged, sender, e); return; }
       UpdateSolutionFdcAnalysis((ISolutionSimilarityCalculator)similarityComboBox.SelectedItem, fdcBetweenBestCheckBox.Checked);
+    }
+
+    private void SolutionNetworkProjectionComboBoxOnSelectedIndexChanged(object sender, EventArgs e) {
+      if (InvokeRequired) { Invoke((Action<object, EventArgs>)SolutionNetworkProjectionComboBoxOnSelectedIndexChanged, sender, e); return; }
+      var calculator = (ISolutionSimilarityCalculator)similarityComboBox.SelectedItem;
+      if (calculator != null) {
+        calculator.SolutionVariableName = (string)solutionNameComboBox.SelectedItem;
+        calculator.QualityVariableName = Content.Problem.Problem.Evaluator.QualityParameter.ActualName;
+      }
+      UpdateSolutionNetworkAnalysis(calculator, (string)solutionNetworkProjectionComboBox.SelectedItem);
     }
 
@@ -318,4 +333,108 @@
       return dval.Value;
     }
+
+    #region SOM projection
+    /// <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>
+    private DoubleMatrix Som(DoubleMatrix dissimilarities, IRandom random, int somSize = 5, int iterations = 100, double learningRate = double.NaN, double learningRadius = 5.0, bool jittering = true) {
+      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, double> learningRateT = (maxIter, iter) => {
+        return varLearningRate * ((maxIter - iter) / (double)maxIter) + 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 = 1.0 * ((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++) {
+        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] + learningRateT(iterations, iter) * 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 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;
+    }
+    #endregion
+
+    private DoubleMatrix Mds(DoubleMatrix dissimilarities) {
+      return MultidimensionalScaling.KruskalShepard(dissimilarities);
+    }
     #endregion
   }
