Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/HeuristicLab.SimSharp-3.3.2.csproj
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/HeuristicLab.SimSharp-3.3.2.csproj	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/HeuristicLab.SimSharp-3.3.2.csproj	(revision 18027)
@@ -0,0 +1,124 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{4C958A30-DB8A-47C7-9D70-6042C44C2502}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>HeuristicLab.SimSharp</RootNamespace>
+    <AssemblyName>HeuristicLab.SimSharp-3.3.2</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>7.3</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>7.3</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <LangVersion>7.3</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <LangVersion>7.3</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>HeuristicLab.snk</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="HeuristicLab.snk" />
+    <None Include="Plugin.cs.frame" />
+    <None Include="Properties\AssemblyInfo.cs.frame" />
+    <Compile Include="Plugin.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\..\HeuristicLab.PluginInfrastructure\3.3\HeuristicLab.PluginInfrastructure-3.3.csproj">
+      <Project>{94186a6a-5176-4402-ae83-886557b53cca}</Project>
+      <Name>HeuristicLab.PluginInfrastructure-3.3</Name>
+      <Private>False</Private>
+    </ProjectReference>
+    <ProjectReference Include="..\SimSharp-3.3.2\SimSharp-3.3.2.csproj">
+      <Project>{4656ea72-9fdc-4d57-9491-d67089c260d0}</Project>
+      <Name>SimSharp-3.3.2</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="SimSharp-3.3.2 License.txt">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+    <PropertyGroup>
+    <PreBuildEvent Condition=" '$(OS)' == 'Windows_NT' ">
+      set Path=%25Path%25;$(ProjectDir);$(SolutionDir)
+      set ProjectDir=$(ProjectDir)
+      set SolutionDir=$(SolutionDir)
+      set Outdir=$(Outdir)
+
+      call PreBuildEvent.cmd
+    </PreBuildEvent>
+    <PreBuildEvent Condition=" '$(OS)' != 'Windows_NT' ">
+      export ProjectDir=$(ProjectDir)
+      export SolutionDir=$(SolutionDir)
+
+      $SolutionDir/PreBuildEvent.sh
+    </PreBuildEvent>
+  </PropertyGroup>
+</Project>
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/Plugin.cs.frame
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/Plugin.cs.frame	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/Plugin.cs.frame	(revision 18027)
@@ -0,0 +1,31 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 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.PluginInfrastructure;
+
+namespace HeuristicLab.SimSharp {
+  [Plugin("HeuristicLab.SimSharp", "Transport plugin for the simulation framework Sim#", "3.3.2.$WCREV$")]
+  [PluginFile("HeuristicLab.SimSharp-3.3.2.dll", PluginFileType.Assembly)]
+  [PluginFile("SimSharp-3.3.2.dll", PluginFileType.Assembly)]
+  [PluginFile("SimSharp-3.3.2 License.txt", PluginFileType.License)]
+  public class HeuristicLabSimSharpPlugin : PluginBase {
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/Properties/AssemblyInfo.cs.frame
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/Properties/AssemblyInfo.cs.frame	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/Properties/AssemblyInfo.cs.frame	(revision 18027)
@@ -0,0 +1,35 @@
+﻿using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("HeuristicLab.SimSharp")]
+[assembly: AssemblyDescription("Transport plugin for the simulation framework Sim#")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("HEAL")]
+[assembly: AssemblyProduct("HeuristicLab")]
+[assembly: AssemblyCopyright("(c) HEAL")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("4c958a30-db8a-47c7-9d70-6042c44c2502")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("3.3.16.0")]
+[assembly: AssemblyFileVersion("3.3.16.$WCREV$")]
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/SimSharp-3.3.2 License.txt
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/SimSharp-3.3.2 License.txt	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/HeuristicLab.SimSharp-3.3.2/SimSharp-3.3.2 License.txt	(revision 18027)
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Andreas Beham
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/IMonitor.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/IMonitor.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/IMonitor.cs	(revision 18027)
@@ -0,0 +1,46 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace SimSharp {
+  public interface IMonitor {
+    bool Active { get; set; }
+    string Name { get; }
+
+    string Summarize();
+
+
+    event EventHandler Updated;
+  }
+
+  public interface INumericMonitor : IMonitor {
+    bool Collect { get; }
+
+    double Min { get; }
+    double Max { get; }
+    double Sum { get; }
+    double Mean { get; }
+    double StdDev { get; }
+    double Last { get; }
+
+    double GetMedian();
+    double GetPercentile(double p);
+  }
+
+  public interface ISampleMonitor : INumericMonitor {
+    void Add(double value);
+    IEnumerable<double> Samples { get; }
+  }
+
+  public interface ITimeSeriesMonitor : INumericMonitor {
+    void Increase(double value);
+    void Decrease(double value);
+    void UpdateTo(double value);
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/Report.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/Report.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/Report.cs	(revision 18027)
@@ -0,0 +1,381 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace SimSharp {
+  public sealed class Report {
+    [Flags]
+    public enum Measures { Min = 1, Max = 2, Sum = 4, Mean = 8, StdDev = 16, Last = 32, All = 63 }
+    private enum UpdateType { Auto = 0, Manual = 1, Periodic = 2, Summary = 3 }
+
+    private Simulation environment;
+    private List<Key> keys;
+    private UpdateType updateType;
+    private TimeSpan periodicUpdateInterval;
+    private bool withHeaders;
+    private string separator;
+    private bool useDoubleTime;
+
+    private DateTime lastUpdate;
+    private double[] lastFigures;
+    private bool firstUpdate;
+    private bool headerWritten;
+
+    /// <summary>
+    /// Gets or sets the output target.
+    /// </summary>
+    /// <remarks>
+    /// This is not thread-safe and must be set only when the simulation is not running.
+    /// </remarks>
+    public TextWriter Output { get; set; }
+
+    private Report() {
+      keys = new List<Key>();
+      updateType = UpdateType.Auto;
+      periodicUpdateInterval = TimeSpan.Zero;
+      environment = null;
+      Output = Console.Out;
+      separator = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator;
+      useDoubleTime = false;
+      withHeaders = true;
+    }
+
+    private void Initialize() {
+      environment.RunStarted += SimulationOnRunStarted;
+      environment.RunFinished += SimulationOnRunFinished;
+      if (updateType == UpdateType.Auto) {
+        foreach (var k in keys) k.Statistics.Updated += StatisticsOnUpdated;
+      }
+    }
+
+    private void SimulationOnRunStarted(object sender, EventArgs e) {
+      var cols = keys.Sum(x => x.TotalMeasures);
+      lastFigures = new double[cols];
+      lastUpdate = environment.Now;
+      firstUpdate = true;
+      headerWritten = false;
+
+      if (updateType == UpdateType.Periodic) {
+        environment.Process(PeriodicUpdateProcess());
+      } else if (updateType == UpdateType.Auto) {
+        DoUpdate();
+      }
+    }
+
+    private void SimulationOnRunFinished(object sender, EventArgs e) {
+      if (updateType == UpdateType.Periodic || updateType == UpdateType.Summary) DoUpdate();
+      if (updateType == UpdateType.Summary && withHeaders) WriteHeader();
+      WriteLastFigures();
+      Output.Flush();
+    }
+
+    private void DoUpdate() {
+      if (updateType != UpdateType.Summary && !firstUpdate && environment.Now > lastUpdate) {
+        // values are written only when simulation time has actually passed to prevent 0-time updates
+        if (!headerWritten && withHeaders) {
+          WriteHeader();
+          headerWritten = true;
+        }
+        WriteLastFigures();
+      }
+      lastUpdate = environment.Now;
+      var col = 0;
+      foreach (var fig in keys) {
+        if ((fig.Measure & Measures.Min) == Measures.Min)
+          lastFigures[col++] = fig.Statistics.Min;
+        if ((fig.Measure & Measures.Max) == Measures.Max)
+          lastFigures[col++] = fig.Statistics.Max;
+        if ((fig.Measure & Measures.Sum) == Measures.Sum)
+          lastFigures[col++] = fig.Statistics.Sum;
+        if ((fig.Measure & Measures.Mean) == Measures.Mean)
+          lastFigures[col++] = fig.Statistics.Mean;
+        if ((fig.Measure & Measures.StdDev) == Measures.StdDev)
+          lastFigures[col++] = fig.Statistics.StdDev;
+        if ((fig.Measure & Measures.Last) == Measures.Last)
+          lastFigures[col++] = fig.Statistics.Last;
+      }
+      firstUpdate = false;
+    }
+
+    /// <summary>
+    /// Writes the header manually to <see cref="Output"/>. This may be useful if
+    /// headers are not automatically added.
+    /// </summary>
+    public void WriteHeader() {
+      Output.Write("Time");
+      foreach (var fig in keys) {
+        if ((fig.Measure & Measures.Min) == Measures.Min) {
+          Output.Write(separator);
+          Output.Write(fig.Name + ".Min");
+        }
+        if ((fig.Measure & Measures.Max) == Measures.Max) {
+          Output.Write(separator);
+          Output.Write(fig.Name + ".Max");
+        }
+        if ((fig.Measure & Measures.Sum) == Measures.Sum) {
+          Output.Write(separator);
+          Output.Write(fig.Name + ".Sum");
+        }
+        if ((fig.Measure & Measures.Mean) == Measures.Mean) {
+          Output.Write(separator);
+          Output.Write(fig.Name + ".Mean");
+        }
+        if ((fig.Measure & Measures.StdDev) == Measures.StdDev) {
+          Output.Write(separator);
+          Output.Write(fig.Name + ".StdDev");
+        }
+        if ((fig.Measure & Measures.Last) == Measures.Last) {
+          Output.Write(separator);
+          Output.Write(fig.Name + ".Last");
+        }
+      }
+      Output.WriteLine();
+    }
+
+    private void WriteLastFigures() {
+      var col = 0;
+      if (useDoubleTime) Output.Write(environment.ToDouble(lastUpdate - environment.StartDate));
+      else Output.Write(lastUpdate.ToString());
+      foreach (var fig in keys) {
+        if ((fig.Measure & Measures.Min) == Measures.Min) {
+          Output.Write(separator);
+          Output.Write(lastFigures[col++]);
+        }
+        if ((fig.Measure & Measures.Max) == Measures.Max) {
+          Output.Write(separator);
+          Output.Write(lastFigures[col++]);
+        }
+        if ((fig.Measure & Measures.Sum) == Measures.Sum) {
+          Output.Write(separator);
+          Output.Write(lastFigures[col++]);
+        }
+        if ((fig.Measure & Measures.Mean) == Measures.Mean) {
+          Output.Write(separator);
+          Output.Write(lastFigures[col++]);
+        }
+        if ((fig.Measure & Measures.StdDev) == Measures.StdDev) {
+          Output.Write(separator);
+          Output.Write(lastFigures[col++]);
+        }
+        if ((fig.Measure & Measures.Last) == Measures.Last) {
+          Output.Write(separator);
+          Output.Write(lastFigures[col++]);
+        }
+      }
+      Output.WriteLine();
+    }
+
+    /// <summary>
+    /// Performs a manual update. It must only be called when manual update is chosen.
+    /// </summary>
+    /// <exception cref="InvalidOperationException">Thrown when calling this function in another update mode.</exception>
+    public void Update() {
+      if (updateType != UpdateType.Manual) throw new InvalidOperationException("Update may only be called in manual update mode.");
+      DoUpdate();
+    }
+
+    private void StatisticsOnUpdated(object sender, EventArgs e) {
+      DoUpdate();
+    }
+
+    private IEnumerable<Event> PeriodicUpdateProcess() {
+      while (true) {
+        DoUpdate();
+        yield return environment.Timeout(periodicUpdateInterval);
+      }
+    }
+
+    /// <summary>
+    /// Creates a new report builder for configuring the report. A report can be generated by
+    /// calling the builder's <see cref="Builder.Build"/> method.
+    /// </summary>
+    /// <param name="env">The simulation environment for which a report should be generated.</param>
+    /// <returns>The builder instance that is used to configure a new report.</returns>
+    public static Builder CreateBuilder(Simulation env) {
+      return new Builder(env);
+    }
+
+    /// <summary>
+    /// The Builder class is used to configure and create a new report.
+    /// </summary>
+    public class Builder {
+      private Report instance;
+      /// <summary>
+      /// Creates a new builder for generating a report.
+      /// </summary>
+      /// <param name="env">The simulation environment for which the report should be generated.</param>
+      public Builder(Simulation env) {
+        instance = new Report() { environment = env };
+      }
+
+      /// <summary>
+      /// Adds a new indicator to the report.
+      /// </summary>
+      /// <param name="name">The name of the indicator for which the statistic is created.</param>
+      /// <param name="statistics">The statistics instance for the indicator that contains the values.</param>
+      /// <param name="measure">The measure(s) that should be reported.</param>
+      /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is null or empty,
+      /// or when <paramref name="measure"/> is not valid.</exception>
+      /// <exception cref="ArgumentNullException">Thrown when <paramref name="statistics"/> is null.</exception>
+      /// <returns>This builder instance.</returns>
+      public Builder Add(string name, INumericMonitor statistics, Measures measure = Measures.All) {
+        if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name must be a non-empty string", "name");
+        if (statistics == null) throw new ArgumentNullException("statistics");
+        if (measure == 0 || measure > Measures.All) throw new ArgumentException("No measures have been selected.", "measure");
+
+        instance.keys.Add(new Key { Name = name, Statistics = statistics, Measure = measure, TotalMeasures = CountSetBits((int)measure) });
+        return this;
+      }
+
+      /// <summary>
+      /// In automatic updating mode (default), the report will listen to the
+      /// <see cref="IMonitor.Updated"/> event and perform an update whenever
+      /// any of its statistics is updated.
+      /// </summary>
+      /// <remarks>Auto update is mutually exclusive to the other update modes.
+      /// Auto update with headers is the default.</remarks>
+      /// <param name="withHeaders">Whether the headers should be output before the first values are printed.</param>
+      /// <returns>This builder instance.</returns>
+      public Builder SetAutoUpdate(bool withHeaders = true) {
+        instance.withHeaders = withHeaders;
+        instance.updateType = UpdateType.Auto;
+        return this;
+      }
+
+      /// <summary>
+      /// In manual updating mode, the <see cref="Report.Update"/> method needs to be called
+      /// manually in order to record the current state.
+      /// </summary>
+      /// <remarks>Manual update is mutually exclusive to the other update modes.</remarks>
+      /// <param name="withHeaders">Whether the headers should be output before the first values are printed.</param>
+      /// <returns>This builder instance.</returns>
+      public Builder SetManualUpdate(bool withHeaders = true) {
+        instance.withHeaders = withHeaders;
+        instance.updateType = UpdateType.Manual;
+        return this;
+      }
+
+      /// <summary>
+      /// In periodic updating mode, the report will create a process that periodically
+      /// triggers the update. The process will be created upon calling <see cref="Build"/>.
+      /// </summary>
+      /// <remarks>Periodic update is mutually exclusive to the other update modes.</remarks>
+      /// <exception cref="ArgumentException">Thrown when <paramref name="interval"/> is less or equal than TimeSpan.Zero.</exception>
+      /// <param name="interval">The interval after which an update occurs.</param>
+      /// <param name="withHeaders">Whether the headers should be output before the first values are printed.</param>
+      /// <returns>This builder instance.</returns>
+      public Builder SetPeriodicUpdate(TimeSpan interval, bool withHeaders = true) {
+        if (interval <= TimeSpan.Zero) throw new ArgumentException("Interval must be > 0", "interval");
+        instance.periodicUpdateInterval = interval;
+        instance.withHeaders = withHeaders;
+        instance.updateType = UpdateType.Periodic;
+        return this;
+      }
+
+      /// <summary>
+      /// In periodic updating mode, the report will create a process that periodically
+      /// triggers the update. The process will be created upon calling <see cref="Build"/>.
+      /// </summary>
+      /// <remarks>Periodic update is mutually exclusive to the other update modes.</remarks>
+      /// <exception cref="ArgumentException">Thrown when <paramref name="interval"/> is less or equal than 0.</exception>
+      /// <param name="interval">The interval after which an update occurs.</param>
+      /// <param name="withHeaders">Whether the headers should be output before the first values are printed.</param>
+      /// <returns>This builder instance.</returns>
+      public Builder SetPeriodicUpdateD(double interval, bool withHeaders = true) {
+        if (interval <= 0) throw new ArgumentException("Interval must be > 0", "interval");
+        instance.periodicUpdateInterval = instance.environment.ToTimeSpan(interval);
+        instance.withHeaders = withHeaders;
+        instance.updateType = UpdateType.Periodic;
+        return this;
+      }
+
+      /// <summary>
+      /// In final update mode, the report will only update when the simulation terminates correctly.
+      /// This is useful for generating a summary of the results.
+      /// </summary>
+      /// <remarks>Final update is mutually exclusive to the other update modes.</remarks>
+      /// <param name="withHeaders">Whether the headers should be output together with the summary at the end.</param>
+      /// <returns>This builder instance.</returns>
+      public Builder SetFinalUpdate(bool withHeaders = true) {
+        instance.withHeaders = withHeaders;
+        instance.updateType = UpdateType.Summary;
+        return this;
+      }
+
+      /// <summary>
+      /// Whether to output the time column in DateTime format or as double (D-API).
+      /// </summary>
+      /// <param name="useDApi">Whether the time should be output as double.</param>
+      /// <returns>This builder instance.</returns>
+      public Builder SetTimeAPI(bool useDApi = true) {
+        instance.useDoubleTime = useDApi;
+        return this;
+      }
+
+      /// <summary>
+      /// Redirects the output of the report to another target.
+      /// By default it is configured to use stdout.
+      /// </summary>
+      /// <exception cref="ArgumentNullException">Thrown when <paramref name="output"/> is null.</exception>
+      /// <param name="output">The target to which the output should be directed.</param>
+      /// <returns>This builder instance.</returns>
+      public Builder SetOutput(TextWriter output) {
+        this.instance.Output = output ?? throw new ArgumentNullException("output");
+        return this;
+      }
+
+      /// <summary>
+      /// Sets the separator for the indicators' values.
+      /// </summary>
+      /// <param name="seperator">The string that separates the values.</param>
+      /// <returns>This builder instance.</returns>
+      public Builder SetSeparator(string seperator) {
+        if (seperator == null) seperator = string.Empty;
+        this.instance.separator = seperator;
+        return this;
+      }
+
+      /// <summary>
+      /// Creates and initializes the report. After calling Build(), this builder instance
+      /// is reset and can be reused to create a new report.
+      /// </summary>
+      /// <exception cref="InvalidOperationException">Thrown when no indicators have been added.</exception>
+      /// <returns>The created report instance.</returns>
+      public Report Build() {
+        if (!instance.keys.Any())
+          throw new InvalidOperationException("Nothing to build: No indicators have been added to the Builder.");
+        var result = instance;
+        instance = new Report();
+        result.Initialize();
+        return result;
+      }
+
+      private static readonly int[] numToBits = new int[16] { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
+      private static int CountSetBits(int num) {
+        return numToBits[num & 0xf] +
+               numToBits[(num >> 4) & 0xf] +
+               numToBits[(num >> 8) & 0xf] +
+               numToBits[(num >> 16) & 0xf] +
+               numToBits[(num >> 20) & 0xf] +
+               numToBits[(num >> 24) & 0xf] +
+               numToBits[(num >> 28) & 0xf];
+      }
+    }
+
+    private class Key {
+      public string Name { get; set; }
+      public INumericMonitor Statistics { get; set; }
+      public Measures Measure { get; set; }
+      public int TotalMeasures { get; set; }
+    }
+  }
+  
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/SampleMonitor.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/SampleMonitor.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/SampleMonitor.cs	(revision 18027)
@@ -0,0 +1,237 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace SimSharp {
+  /// <summary>
+  /// This class calculates some descriptive statistics by
+  /// remembering all data. All observed values are equally weighed.
+  /// 
+  /// It can be used to calculate e.g. lead times of processes.
+  /// </summary>
+  public sealed class SampleMonitor : ISampleMonitor {
+    /// <summary>
+    /// Can only be set in the constructor.
+    /// When it is true, median and percentiles can be computed and a
+    /// histogram can be printed. In addition <see cref="Samples"/>
+    /// may return all the remembered values for further processing.
+    /// </summary>
+    public bool Collect { get; }
+
+    /// <summary>
+    /// The monitor can be set to suppress updates. When it is set
+    /// to false, the statistics will not be updated and new samples
+    /// are ignored.
+    /// </summary>
+    public bool Active { get; set; }
+
+    /// <summary>
+    /// The name of the variable that is being monitored.
+    /// Used for output in <see cref="Summarize(bool, int, double?, double?)"/>.
+    /// </summary>
+    public string Name { get; set; }
+
+    public int Count { get; private set; }
+
+    public double Min { get; private set; }
+    public double Max { get; private set; }
+    public double Total { get; private set; }
+    double INumericMonitor.Sum { get { return Total; } }
+    public double Mean { get; private set; }
+    public double StdDev { get { return Math.Sqrt(Variance); } }
+    public double Variance { get { return (Count > 0) ? variance / Count : 0.0; } }
+    private double variance;
+    public double Last { get; private set; }
+
+    private List<double> samples;
+    /// <summary>
+    /// Returns the list of collected values, or an empty enumerable
+    /// when <see cref="Collect"/> was initialized to false.
+    /// </summary>
+    public IEnumerable<double> Samples { get { return samples != null ? samples.AsEnumerable() : Enumerable.Empty<double>(); } }
+
+
+    /// <summary>
+    /// Calls <see cref="GetPercentile(double)"/>.
+    /// </summary>
+    /// <remarks>
+    /// Median can only be computed when the monitor was initialized to collect the data.
+    /// 
+    /// The data is preprocessed on every call, the runtime complexity of this method is therefore O(n * log(n)).
+    /// </remarks>
+    /// <returns>The median (50th percentile) of the samples.</returns>
+    public double GetMedian() {
+      return GetPercentile(0.5);
+    }
+
+    /// <summary>
+    /// Calculates the p-percentile of the samples.
+    /// </summary>
+    /// <remarks>
+    /// Percentiles can only be computed when the monitor was initialized to collect the data.
+    /// 
+    /// The data is preprocessed on every call, the runtime complexity of this method is therefore O(n * log(n)).
+    /// </remarks>
+    /// <exception cref="ArgumentException">Thrown when <paramref name="p"/> is outside the valid range.</exception>
+    /// <param name="p">The percentile has to be in the range [0;1].</param>
+    /// <returns>The respective percentile of the samples.</returns>
+    public double GetPercentile(double p) {
+      if (p < 0 || p > 1) throw new ArgumentException("Percentile must be between 0 and 1", "p");
+      if (!Collect) return double.NaN;
+      return GetPercentile(samples, p);
+    }
+
+    private static double GetPercentile(IList<double> s, double p) {
+      if (p < 0 || p > 1) throw new ArgumentException("Percentile must be between 0 and 1", "p");
+      if (s.Count == 0) return double.NaN;
+      var n = s.Count * p;
+      var k = (int)Math.Ceiling(n);
+      if (n < k)
+        return s.OrderBy(x => x).Skip(k - 1).First();
+      return s.OrderBy(x => x).Skip(k - 1).Take(2).Average();
+    }
+
+    public SampleMonitor(string name = null, bool collect = false) {
+      Active = true;
+      Name = name;
+      Collect = collect;
+      if (collect) samples = new List<double>(64);
+    }
+
+    public void Reset() {
+      Count = 0;
+      Min = Max = Total = Mean = 0;
+      variance = 0;
+      Last = 0;
+      if (Collect) samples.Clear();
+    }
+
+    public void Add(double value) {
+      if (!Active) return;
+
+      if (double.IsNaN(value) || double.IsInfinity(value))
+        throw new ArgumentException("Not a valid double", "value");
+      Count++;
+      Total += value;
+      Last = value;
+      if (Collect) samples.Add(value);
+
+      if (Count == 1) {
+        Min = Max = Mean = value;
+      } else {
+        if (value < Min) Min = value;
+        if (value > Max) Max = value;
+
+        var oldMean = Mean;
+        Mean = oldMean + (value - oldMean) / Count;
+        variance = variance + (value - oldMean) * (value - Mean);
+      }
+
+      OnUpdated();
+    }
+
+    public event EventHandler Updated;
+    private void OnUpdated() {
+      Updated?.Invoke(this, EventArgs.Empty);
+    }
+
+    string IMonitor.Summarize() {
+      return Summarize();
+    }
+
+    /// <summary>
+    /// Provides a summary of the statistics in a certain format.
+    /// If the monitor is configured to collect data, it may also print a histogram.
+    /// </summary>
+    /// <param name="withHistogram">Whether to suppress the histogram.
+    /// This is only effective if <see cref="Collect"/> was set to true, otherwise
+    /// the data to produce the histogram is not available in the first place.</param>
+    /// <param name="maxBins">The maximum number of bins that should be used.
+    /// Note that the bin width and thus the number of bins is also governed by
+    /// <paramref name="binWidth"/> if it is defined.
+    /// This is only effective if <see cref="Collect"/> and <paramref name="withHistogram"/>
+    /// was set to true, otherwise the data to produce the histogram is not available
+    /// in the first place.</param>
+    /// <param name="histMin">The minimum for the histogram to start at or the sample
+    /// minimum in case the default (null) is given.
+    /// This is only effective if <see cref="Collect"/> and <paramref name="withHistogram"/>
+    /// was set to true, otherwise the data to produce the histogram is not available
+    /// in the first place.</param>
+    /// <param name="binWidth">The interval for the bins of the histogram or the
+    /// range (<see cref="Max"/> - <see cref="Min"/>) divided by the number of bins
+    /// (<paramref name="maxBins"/>) in case the default value (null) is given.
+    /// This is only effective if <see cref="Collect"/> and <paramref name="withHistogram"/>
+    /// was set to true, otherwise the data to produce the histogram is not available
+    /// in the first place.</param>
+    /// <returns>A formatted string that provides a summary of the statistics.</returns>
+    public string Summarize(bool withHistogram = true, int maxBins = 20, double? histMin = null, double? binWidth = null) {
+      var nozero = Collect ? samples.Where(x => x != 0).ToList() : new List<double>();
+      var nozeromin = nozero.Count > 0 ? nozero.Min() : double.NaN;
+      var nozeromax = nozero.Count > 0 ? nozero.Max() : double.NaN;
+      var nozeromean = nozero.Count > 1 ? nozero.Average() : double.NaN;
+      var nozerostdev = nozero.Count > 2 ? Math.Sqrt(nozero.Sum(x => (x - nozeromean) * (x - nozeromean)) / (nozero.Count - 1.0)) : double.NaN;
+      var sb = new StringBuilder();
+      sb.Append("Statistics");
+      if (!string.IsNullOrEmpty(Name))
+        sb.Append(" of " + Name);
+      sb.AppendLine();
+      sb.AppendLine("                all             excl.zero       zero           ");
+      sb.AppendLine("--------------- --------------- --------------- ---------------");
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15} {3,15}", "Count", Formatter.Format15(Count), Formatter.Format15(Collect ? nozero.Count : double.NaN), Formatter.Format15(Collect ? Count - nozero.Count : double.NaN)));
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Mean", Formatter.Format15(Mean), Formatter.Format15(nozeromean)));
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Std.dev", Formatter.Format15(StdDev), Formatter.Format15(nozerostdev)));
+      sb.AppendLine();
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Minimum", Formatter.Format15(Min), Formatter.Format15(nozeromin)));
+      if (Collect) {
+        sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Percentile-5%", Formatter.Format15(GetPercentile(0.05)), Formatter.Format15(GetPercentile(nozero, 0.05))));
+        sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Median", Formatter.Format15(GetMedian()), Formatter.Format15(GetPercentile(nozero, 0.5))));
+        sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Percentile-95%", Formatter.Format15(GetPercentile(0.95)), Formatter.Format15(GetPercentile(nozero, 0.95))));
+      }
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Maximum", Formatter.Format15(Max), Formatter.Format15(nozeromax)));
+
+      if (Collect && withHistogram) {
+        var min = histMin ?? Min;
+        var interval = binWidth ?? (Max - Min) / maxBins;
+        var histData = samples.GroupBy(x => x <= min ? 0 : (int)Math.Floor(Math.Min((x - min + interval) / interval, maxBins)))
+                           .Select(x => new { Key = x.Key, Value = x.Count() })
+                           .OrderBy(x => x.Key);
+        sb.AppendLine();
+        sb.AppendLine("Histogram");
+        sb.AppendLine("<=              count      %     cum%   ");
+        sb.AppendLine("--------------- ---------- ----- ------");
+        var cumul = 0.0;
+        var totStars = 0;
+        var last = -1;
+        foreach (var kvp in histData) {
+          while (kvp.Key > last + 1) {
+            last++;
+            var tmp = "|".PadLeft(totStars + 1);
+            sb.AppendLine(string.Format("{0,15} {1,10} {2,5:F1} {3,5:F1} {4}{5}", Formatter.Format15(min + last * interval), 0, 0, cumul * 100, "", tmp));
+          }
+          var prob = kvp.Value / (double)Count;
+          cumul += prob;
+          var probstars = (int)Math.Round(100 * prob / 2);
+          var cumulstars = (int)Math.Round(100 * cumul / 2);
+          var numstars = probstars;
+          if (probstars + totStars < cumulstars) numstars++;
+          var stars = string.Join("", Enumerable.Repeat("*", numstars));
+          totStars += numstars;
+          var cumulbar = "|".PadLeft(totStars + 1 - numstars);
+          sb.AppendLine(string.Format("{0,15} {1,10} {2,5:F1} {3,5:F1} {4}{5}",
+            (kvp.Key == maxBins && min + kvp.Key * interval < Max) ? "inf" : Formatter.Format15(min + kvp.Key * interval),
+            kvp.Value, prob * 100, cumul * 100, stars, cumulbar));
+          last = kvp.Key;
+        }
+      }
+      return sb.ToString();
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/TimeSeriesMonitor.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/TimeSeriesMonitor.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Analysis/TimeSeriesMonitor.cs	(revision 18027)
@@ -0,0 +1,447 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace SimSharp {
+  /// <summary>
+  /// This class calculates weighted statistics of a time series variable.
+  /// The weight is given as the duration of the variable's value.
+  /// 
+  /// It is typically used to calculate utilization of resources or inventory levels.
+  /// </summary>
+  /// <remarks>
+  /// The monitor updates the statistics online, except for <see cref="GetMedian"/>
+  /// which is calculated given the collected data whenever it is called.
+  /// When the monitor was initialized with collect = false in the constructor,
+  /// median and other percentiles cannot be computed (double.NaN is returned).
+  /// Also to print a histogram requires that the monitor was initialized to collect
+  /// all the changes to the variable's value.
+  /// 
+  /// Collecting the data naturally incurs some memory overhead.
+  /// </remarks>
+  public sealed class TimeSeriesMonitor : ITimeSeriesMonitor {
+    private readonly Simulation env;
+    /// <summary>
+    /// Can only be set in the constructor.
+    /// When it is true, median and percentiles can be computed and a
+    /// histogram can be printed. In addition <see cref="Series"/>
+    /// may return all the remembered values for further processing.
+    /// </summary>
+    public bool Collect { get; }
+
+    private bool active;
+    /// <summary>
+    /// The monitor can be set to suppress updates. When it is set
+    /// to false, the statistics, except for <see cref="Current"/>
+    /// will not be updated and new samples are ignored.
+    /// When set to true it will resume updates from the current
+    /// simulation time onward.
+    /// </summary>
+    public bool Active {
+      get { return active; }
+      set {
+        if (active == value) return;
+        if (firstSampleObserved && lastUpdateTime < env.NowD) {
+          if (value) {
+            // enable it
+            if (Current < Min) Min = Current;
+            if (Current > Max) Max = Current;
+
+            lastUpdateTime = env.NowD;
+            series.Add(new Entry { Date = env.NowD, Level = Current });
+          } else if (!value) {
+            // disable it
+            OnlineUpdate();
+          }
+          active = value;
+          OnUpdated();
+        } else active = value;
+      }
+    }
+
+    /// <summary>
+    /// The name of the variable that is being monitored.
+    /// Used for output in <see cref="Summarize(bool, int, double?, double?)"/>.
+    /// </summary>
+    public string Name { get; set; }
+
+    public double TotalTimeD { get; private set; }
+    public TimeSpan TotalTime { get { return env.ToTimeSpan(TotalTimeD); } }
+
+    public double Min { get; private set; }
+    public double Max { get; private set; }
+    public double Area {
+      get {
+        if (!UpToDate) OnlineUpdate();
+        return area;
+      }
+      private set => area = value;
+    }
+    double INumericMonitor.Sum { get { return Area; } }
+    public double Mean {
+      get {
+        if (!UpToDate) OnlineUpdate();
+        return mean;
+      }
+      private set => mean = value;
+    }
+    public double StdDev { get { return Math.Sqrt(Variance); } }
+    public double Variance {
+      get {
+        if (!UpToDate) OnlineUpdate();
+        return (TotalTimeD > 0) ? variance / TotalTimeD : 0.0;
+      }
+    }
+    public double Current { get; private set; }
+    double INumericMonitor.Last { get { return Current; } }
+
+    private bool UpToDate { get { return !Active || env.NowD == lastUpdateTime; } }
+
+    private double lastUpdateTime;
+    private double variance;
+
+    private bool firstSampleObserved;
+    private double area;
+    private double mean;
+
+    private List<Entry> series;
+    /// <summary>
+    /// Returns the list of collected values, or an empty enumerable
+    /// when <see cref="Collect"/> was initialized to false.
+    /// </summary>
+    public IEnumerable<Entry> Series {
+      get {
+        if (!UpToDate) OnlineUpdate();
+        return series != null ? series.AsEnumerable() : Enumerable.Empty<Entry>();
+      }
+    }
+
+    /// <summary>
+    /// Calls <see cref="GetPercentile(double)"/>.
+    /// </summary>
+    /// <remarks>
+    /// Median can only be computed when the monitor was initialized to collect the data.
+    /// 
+    /// The data is preprocessed on every call, the runtime complexity of this method is therefore O(n * log(n)).
+    /// </remarks>
+    /// <returns>The median (50th percentile) of the time series.</returns>
+    public double GetMedian() {
+      return GetPercentile(0.5);
+    }
+
+    /// <summary>
+    /// Calculates the weighted p-percentile of the sampled levels (duration is the weight).
+    /// </summary>
+    /// <remarks>
+    /// Percentiles can only be computed when the monitor was initialized to collect the data.
+    /// 
+    /// The data is preprocessed on every call, the runtime complexity of this method is therefore O(n * log(n)).
+    /// </remarks>
+    /// <exception cref="ArgumentException">Thrown when <paramref name="p"/> is outside the valid range.</exception>
+    /// <param name="p">The percentile has to be in the range [0;1].</param>
+    /// <returns>The respective percentile of the time series.</returns>
+    public double GetPercentile(double p) {
+      if (p < 0 || p > 1) throw new ArgumentException("Percentile must be between 0 and 1", "p");
+      if (!Collect) return double.NaN;
+      return GetPercentile(series, p);
+    }
+    private static double GetPercentile(IList<Entry> s, double p) {
+      var seq = Cumulate(s).ToList();
+      if (seq.Count == 0) return double.NaN;
+      var total = seq.Last().CumulatedDuration;
+      var n = total * p;
+      var ilower = 0;
+      while (ilower < seq.Count) {
+        if (seq[ilower].CumulatedDuration >= n) break;
+        ilower++;
+      }
+      if (seq[ilower].CumulatedDuration == n) {
+        if (ilower < seq.Count - 1)
+          return (seq[ilower].Level + seq[ilower + 1].Level) / 2.0;
+      }
+      return seq[ilower].Level;
+    }
+
+    private static IEnumerable<LevelDuration> Cumulate(IList<Entry> s) {
+      var totalDuration = 0.0;
+      // The last entry will always be skipped as it has Duration == 0
+      foreach (var g in s.Where(x => x.Duration > 0).GroupBy(x => x.Level, x => x.Duration).OrderBy(x => x.Key)) {
+        var duration = g.Sum();
+        totalDuration += duration;
+        yield return new LevelDuration { Level = g.Key, Duration = duration, CumulatedDuration = totalDuration };
+      }
+    }
+
+    public TimeSeriesMonitor(Simulation env, string name = null, bool collect = false) {
+      this.env = env;
+      active = true;
+      Name = name;
+      Collect = collect;
+      lastUpdateTime = env.NowD;
+      if (Collect) series = new List<Entry>(64);
+    }
+    public TimeSeriesMonitor(Simulation env, double initial, string name = null, bool collect = false) {
+      this.env = env;
+      active = true;
+      Name = name;
+      Collect = collect;
+      lastUpdateTime = env.NowD;
+      firstSampleObserved = true;
+      Current = Min = Max = mean = initial;
+      if (Collect) series = new List<Entry>(64) { new Entry { Date = env.NowD, Level = initial } };
+    }
+
+    public void Reset() {
+      TotalTimeD = 0;
+      Current = Min = Max = area = mean = 0;
+      if (Collect) series.Clear();
+      variance = 0;
+      firstSampleObserved = false;
+      lastUpdateTime = env.NowD;
+    }
+
+    public void Reset(double initial) {
+      TotalTimeD = 0;
+      Current = Min = Max = mean = initial;
+      if (Collect) {
+        series.Clear();
+        series.Add(new Entry { Date = env.NowD, Level = initial });
+      }
+      area = 0;
+      variance = 0;
+      firstSampleObserved = true;
+      lastUpdateTime = env.NowD;
+    }
+
+    public void Increase(double value = 1) {
+      UpdateTo(Current + value);
+    }
+
+    public void Decrease(double value = 1) {
+      UpdateTo(Current - value);
+    }
+
+    public void UpdateTo(double value) {
+      if (!Active) {
+        Current = value;
+        return;
+      }
+
+      if (!firstSampleObserved) {
+        Min = Max = mean = value;
+        firstSampleObserved = true;
+        lastUpdateTime = env.NowD;
+        if (Collect) series.Add(new Entry { Date = env.NowD, Level = value });
+        Current = value;
+      } else {
+        if (value < Min) Min = value;
+        if (value > Max) Max = value;
+
+        OnlineUpdate();
+
+        if (Current != value) {
+          if (Collect) series.Add(new Entry { Date = env.NowD, Level = value });
+          Current = value;
+        }
+      }
+
+      OnUpdated();
+    }
+
+    private void OnlineUpdate() {
+      var duration = env.NowD - lastUpdateTime;
+      if (duration > 0) {
+        if (Collect) {
+          var prevIdx = series.Count - 1;
+          var prev = series[prevIdx];
+          prev.Duration = env.NowD - prev.Date;
+          series[prevIdx] = prev;
+        }
+        area += (Current * duration);
+        var oldMean = mean;
+        mean = oldMean + (Current - oldMean) * duration / (duration + TotalTimeD);
+        variance = variance + (Current - oldMean) * (Current - mean) * duration;
+        TotalTimeD += duration;
+      }
+      lastUpdateTime = env.NowD;
+    }
+
+    public event EventHandler Updated;
+    private void OnUpdated() {
+      Updated?.Invoke(this, EventArgs.Empty);
+    }
+
+    string IMonitor.Summarize() {
+      return Summarize();
+    }
+
+    /// <summary>
+    /// Provides a summary of the statistics in a certain format.
+    /// If the monitor is configured to collect data, it may also print a histogram.
+    /// </summary>
+    /// <param name="withHistogram">Whether to suppress the histogram.
+    /// This is only effective if <see cref="Collect"/> was set to true, otherwise
+    /// the data to produce the histogram is not available in the first place.</param>
+    /// <param name="maxBins">The maximum number of bins that should be used.
+    /// Note that the bin width and thus the number of bins is also governed by
+    /// <paramref name="binWidth"/> if it is defined.
+    /// This is only effective if <see cref="Collect"/> and <paramref name="withHistogram"/>
+    /// was set to true, otherwise the data to produce the histogram is not available
+    /// in the first place.</param>
+    /// <param name="histMin">The minimum for the histogram to start at or the sample
+    /// minimum in case the default (null) is given.
+    /// This is only effective if <see cref="Collect"/> and <paramref name="withHistogram"/>
+    /// was set to true, otherwise the data to produce the histogram is not available
+    /// in the first place.</param>
+    /// <param name="binWidth">The interval for the bins of the histogram or the
+    /// range (<see cref="Max"/> - <see cref="Min"/>) divided by the number of bins
+    /// (<paramref name="maxBins"/>) in case the default value (null) is given.
+    /// This is only effective if <see cref="Collect"/> and <paramref name="withHistogram"/>
+    /// was set to true, otherwise the data to produce the histogram is not available
+    /// in the first place.</param>
+    /// <returns>A formatted string that provides a summary of the statistics.</returns>
+    public string Summarize(bool withHistogram = true, int maxBins = 20, double? histMin = null, double? binWidth = null) {
+      var nozero = Collect ? series.Where(x => x.Level != 0 && x.Duration > 0).ToList() : new List<Entry>();
+      var nozeromin = nozero.Count > 0 ? nozero.Min(x => x.Level) : double.NaN;
+      var nozeromax = nozero.Count > 0 ? nozero.Max(x => x.Level) : double.NaN;
+      var nozeroduration = Collect ? nozero.Sum(x => x.Duration) : double.NaN;
+      var nozeromean = nozero.Count > 1 ? nozero.Sum(x => x.Level * x.Duration / nozeroduration) : double.NaN;
+      var nozerostdev = nozero.Count > 2 ? Math.Sqrt(nozero.Sum(x => x.Duration * (x.Level - nozeromean) * (x.Level - nozeromean)) / nozeroduration) : double.NaN;
+      var sb = new StringBuilder();
+      sb.Append("Time series statistics");
+      if (!string.IsNullOrEmpty(Name))
+        sb.Append(" of " + Name);
+      sb.AppendLine();
+      sb.AppendLine("                all             excl.zero       zero           ");
+      sb.AppendLine("--------------- --------------- --------------- ---------------");
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15} {3,15}", "Duration", Formatter.Format15(TotalTimeD), Formatter.Format15(nozeroduration), Formatter.Format15(TotalTimeD - nozeroduration)));
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Mean", Formatter.Format15(Mean), Formatter.Format15(nozeromean)));
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Std.dev", Formatter.Format15(StdDev), Formatter.Format15(nozerostdev)));
+      sb.AppendLine();
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Minimum", Formatter.Format15(Min), Formatter.Format15(nozeromin)));
+      if (Collect) {
+        sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Percentile-5%", Formatter.Format15(GetPercentile(0.05)), Formatter.Format15(GetPercentile(nozero, 0.05))));
+        sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Median", Formatter.Format15(GetMedian()), Formatter.Format15(GetPercentile(nozero, 0.5))));
+        sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Percentile-95%", Formatter.Format15(GetPercentile(0.95)), Formatter.Format15(GetPercentile(nozero, 0.95))));
+      }
+      sb.AppendLine(string.Format("{0,15} {1,15} {2,15}", "Maximum", Formatter.Format15(Max), Formatter.Format15(nozeromax)));
+
+      if (Collect && withHistogram) {
+        var histData = Cumulate(series);
+        sb.AppendLine();
+        sb.AppendLine("Histogram");
+        sb.AppendLine("<=              duration        %     cum%   ");
+        sb.AppendLine("--------------- --------------- ----- ------");
+        var totStars = 0;
+        var iter = histData.GetEnumerator();
+        var cumul = 0.0;
+        if (!iter.MoveNext()) {
+          sb.AppendLine("no data");
+        } else {
+          var kvp = iter.Current;
+          var moreData = true;
+          for (var bin = 0; bin <= maxBins; bin++) {
+            var next = (histMin ?? Min) + bin * (binWidth ?? (Max - Min) / 20.0);
+            var dur = 0.0;
+            var prob = 0.0;
+            while (moreData && (kvp.Level <= next || bin == maxBins)) {
+              dur += kvp.Duration;
+              prob += kvp.Duration / TotalTimeD;
+              cumul = kvp.CumulatedDuration / TotalTimeD;
+              moreData = iter.MoveNext();
+              kvp = iter.Current;
+            }
+            var probstars = (int)Math.Round(100 * prob / 2);
+            var cumulstars = (int)Math.Round(100 * cumul / 2);
+            var numstars = probstars;
+            if (numstars + totStars < cumulstars) numstars++;
+            var stars = string.Join("", Enumerable.Repeat("*", numstars));
+            totStars += numstars;
+            var cumulbar = "|".PadLeft(totStars + 1 - numstars);
+            sb.AppendLine(string.Format("{0,15} {1,15} {2,5:F1} {3,5:F1} {4}{5}",
+              (!moreData && next < Max) ? "inf" : Formatter.Format15(next),
+              Formatter.Format15(dur), prob * 100, cumul * 100, stars, cumulbar));
+            if (!moreData) break;
+          }
+        }
+      }
+      return sb.ToString();
+    }
+
+    public override string ToString() {
+      return Summarize();
+    }
+
+    public struct Entry {
+      public double Date;
+      public double Duration;
+      public double Level;
+    }
+
+    private struct LevelDuration {
+      public double Level;
+      public double Duration;
+      public double CumulatedDuration;
+    }
+  }
+
+  [Obsolete("Use the class TimeSeriesMonitor instead.")]
+  public sealed class ContinuousStatistics {
+    private readonly Simulation env;
+
+    public int Count { get; private set; }
+    public double TotalTimeD { get; private set; }
+    public TimeSpan TotalTime { get { return env.ToTimeSpan(TotalTimeD); } }
+
+    public double Min { get; private set; }
+    public double Max { get; private set; }
+    public double Area { get; private set; }
+    public double Mean { get; private set; }
+    public double StdDev { get { return Math.Sqrt(Variance); } }
+    public double Variance { get { return (TotalTimeD > 0) ? variance / TotalTimeD : 0.0; } }
+
+    private double lastUpdateTime;
+    private double lastValue;
+    private double variance;
+
+    private bool firstSample;
+
+
+    public ContinuousStatistics(Simulation env) {
+      this.env = env;
+      lastUpdateTime = env.NowD;
+    }
+
+    public void Update(double value) {
+      Count++;
+
+      if (!firstSample) {
+        Min = Max = Mean = value;
+        firstSample = true;
+      } else {
+        if (value < Min) Min = value;
+        if (value > Max) Max = value;
+
+        var duration = env.NowD - lastUpdateTime;
+        if (duration > 0) {
+          Area += (lastValue * duration);
+          var oldMean = Mean;
+          Mean = oldMean + (lastValue - oldMean) * duration / (duration + TotalTimeD);
+          variance = variance + (lastValue - oldMean) * (lastValue - Mean) * duration;
+          TotalTimeD += duration;
+        }
+      }
+
+      lastUpdateTime = env.NowD;
+      lastValue = value;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Auxiliary/Formatter.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Auxiliary/Formatter.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Auxiliary/Formatter.cs	(revision 18027)
@@ -0,0 +1,39 @@
+﻿using System;
+
+namespace SimSharp {
+  public static class Formatter {
+    public static string Format12(double number, bool silenceNaN = true) {
+      if (double.IsNaN(number)) return silenceNaN ? "            " : "     nan    ";
+      if (number >= 1e7 || number <= -1e7) {
+        if (number >= 1e100 || number <= -1e100) return string.Format("{0,12:0.####E+000}", number);
+        return string.Format("{0,12:0.#####E+00}", number);
+      }
+      if (Math.Abs(number) < 1e-3) {
+        if (number == 0) return "       0    ";
+        if (Math.Abs(number) <= 1e-100) {
+          return string.Format("{0,12:0.####E-000}", number);
+        }
+        return string.Format("{0,12:0.#####E-00}", number);
+      }
+      if (number == (int)number) return string.Format("{0,8}    ", (int)number);
+      return string.Format("{0,12}", number.ToString("0.000"));
+    }
+
+    public static string Format15(double number, bool silenceNaN = true) {
+      if (double.IsNaN(number)) return silenceNaN ? "               " : "        nan    ";
+      if (number >= 1e10 || number <= -1e10) {
+        if (number >= 1e100 || number <= -1e100) return string.Format("{0,15:0.#######E+000}", number);
+        return string.Format("{0,15:0.########E+00}", number);
+      }
+      if (Math.Abs(number) < 1e-3) {
+        if (number == 0) return "          0    ";
+        if (Math.Abs(number) <= 1e-100) {
+          return string.Format("{0,15:0.#######E-000}", number);
+        }
+        return string.Format("{0,15:0.########E-00}", number);
+      }
+      if (number == (int)number) return string.Format("{0,11}    ", (int)number);
+      return string.Format("{0,15}", number.ToString("0.000"));
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/EventQueue-License.txt
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/EventQueue-License.txt	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/EventQueue-License.txt	(revision 18027)
@@ -0,0 +1,21 @@
+﻿The MIT License (MIT)
+
+Copyright (c) 2013 Daniel "BlueRaja" Pflughoeft
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/EventQueue.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/EventQueue.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/EventQueue.cs	(revision 18027)
@@ -0,0 +1,276 @@
+﻿using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace SimSharp {
+  /// <summary>
+  /// An implementation of a min-Priority Queue using a heap.  Has O(1) .Contains()!
+  /// See https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp/wiki/Getting-Started for more information
+  /// </summary>
+  /// <remarks>
+  /// There are modifications so that the type is not generic anymore and can only hold values of type EventQueueNode
+  /// </remarks>
+  public sealed class EventQueue : IEnumerable<EventQueueNode> {
+    private int _numNodes;
+    private readonly EventQueueNode[] _nodes;
+    private long _numNodesEverEnqueued;
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="maxNodes">EventQueueNodehe max nodes ever allowed to be enqueued (going over this will cause an exception)</param>
+    public EventQueue(int maxNodes) {
+      _numNodes = 0;
+      _nodes = new EventQueueNode[maxNodes + 1];
+      _numNodesEverEnqueued = 0;
+    }
+
+    /// <summary>
+    /// Returns the number of nodes in the queue.  O(1)
+    /// </summary>
+    public int Count {
+      get {
+        return _numNodes;
+      }
+    }
+
+    /// <summary>
+    /// Returns the maximum number of items that can be enqueued at once in this queue.  Once you hit this number (ie. once Count == MaxSize),
+    /// attempting to enqueue another item will throw an exception.  O(1)
+    /// </summary>
+    public int MaxSize {
+      get {
+        return _nodes.Length - 1;
+      }
+    }
+
+    /// <summary>
+    /// Removes every node from the queue.  O(n) (So, don't do this often!)
+    /// </summary>
+    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+    public void Clear() {
+      Array.Clear(_nodes, 1, _numNodes);
+      _numNodes = 0;
+    }
+
+    /// <summary>
+    /// Returns (in O(1)!) whether the given node is in the queue.  O(1)
+    /// </summary>
+    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+    public bool Contains(EventQueueNode node) {
+      return (_nodes[node.QueueIndex] == node);
+    }
+
+    /// <summary>
+    /// Enqueue a node - .Priority must be set beforehand!  O(log n)
+    /// </summary>
+    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+    public EventQueueNode Enqueue(DateTime primaryPriority, Event @event, int secondaryPriority = 0) {
+      var node = new EventQueueNode {
+        PrimaryPriority = primaryPriority,
+        SecondaryPriority = secondaryPriority,
+        Event = @event,
+        QueueIndex = ++_numNodes,
+        InsertionIndex = _numNodesEverEnqueued++
+      };
+      _nodes[_numNodes] = node;
+      CascadeUp(_nodes[_numNodes]);
+      return node;
+    }
+    
+    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+    private void Swap(EventQueueNode node1, EventQueueNode node2) {
+      //Swap the nodes
+      _nodes[node1.QueueIndex] = node2;
+      _nodes[node2.QueueIndex] = node1;
+
+      //Swap their indicies
+      int temp = node1.QueueIndex;
+      node1.QueueIndex = node2.QueueIndex;
+      node2.QueueIndex = temp;
+    }
+
+    //Performance appears to be slightly better when this is NOT inlined o_O
+    private void CascadeUp(EventQueueNode node) {
+      //aka Heapify-up
+      int parent = node.QueueIndex / 2;
+      while (parent >= 1) {
+        EventQueueNode parentNode = _nodes[parent];
+        if (HasHigherPriority(parentNode, node))
+          break;
+
+        //Node has lower priority value, so move it up the heap
+        Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown()
+
+        parent = node.QueueIndex / 2;
+      }
+    }
+
+
+    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+    private void CascadeDown(EventQueueNode node) {
+      //aka Heapify-down
+      EventQueueNode newParent;
+      int finalQueueIndex = node.QueueIndex;
+      while (true) {
+        newParent = node;
+        int childLeftIndex = 2 * finalQueueIndex;
+
+        //Check if the left-child is higher-priority than the current node
+        if (childLeftIndex > _numNodes) {
+          //This could be placed outside the loop, but then we'd have to check newParent != node twice
+          node.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = node;
+          break;
+        }
+
+        EventQueueNode childLeft = _nodes[childLeftIndex];
+        if (HasHigherPriority(childLeft, newParent)) {
+          newParent = childLeft;
+        }
+
+        //Check if the right-child is higher-priority than either the current node or the left child
+        int childRightIndex = childLeftIndex + 1;
+        if (childRightIndex <= _numNodes) {
+          EventQueueNode childRight = _nodes[childRightIndex];
+          if (HasHigherPriority(childRight, newParent)) {
+            newParent = childRight;
+          }
+        }
+
+        //If either of the children has higher (smaller) priority, swap and continue cascading
+        if (newParent != node) {
+          //Move new parent to its new index.  node will be moved once, at the end
+          //Doing it this way is one less assignment operation than calling Swap()
+          _nodes[finalQueueIndex] = newParent;
+
+          int temp = newParent.QueueIndex;
+          newParent.QueueIndex = finalQueueIndex;
+          finalQueueIndex = temp;
+        } else {
+          //See note above
+          node.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = node;
+          break;
+        }
+      }
+    }
+
+    /// <summary>
+    /// Returns true if 'higher' has higher priority than 'lower', false otherwise.
+    /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false
+    /// </summary>
+
+    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+    private bool HasHigherPriority(EventQueueNode higher, EventQueueNode lower) {
+      return (higher.PrimaryPriority < lower.PrimaryPriority ||
+          (higher.PrimaryPriority == lower.PrimaryPriority
+            && (higher.SecondaryPriority < lower.SecondaryPriority ||
+            (higher.SecondaryPriority == lower.SecondaryPriority
+              && higher.InsertionIndex < lower.InsertionIndex))));
+    }
+
+    /// <summary>
+    /// Removes the head of the queue (node with highest priority; ties are broken by order of insertion), and returns it.  O(log n)
+    /// </summary>
+    public EventQueueNode Dequeue() {
+      EventQueueNode returnMe = _nodes[1];
+      Remove(returnMe);
+      return returnMe;
+    }
+
+    /// <summary>
+    /// Returns the head of the queue, without removing it (use Dequeue() for that).  O(1)
+    /// </summary>
+    public EventQueueNode First {
+      get {
+        return _nodes[1];
+      }
+    }
+
+    /// <summary>
+    /// This method must be called on a node every time its priority changes while it is in the queue.  
+    /// <b>Forgetting to call this method will result in a corrupted queue!</b>
+    /// O(log n)
+    /// </summary>
+
+    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+    public void UpdatePriority(EventQueueNode node, DateTime primaryPriority, int secondaryPriority) {
+      node.PrimaryPriority = primaryPriority;
+      node.SecondaryPriority = secondaryPriority;
+      OnNodeUpdated(node);
+    }
+
+    internal void OnNodeUpdated(EventQueueNode node) {
+      //Bubble the updated node up or down as appropriate
+      int parentIndex = node.QueueIndex / 2;
+      EventQueueNode parentNode = _nodes[parentIndex];
+
+      if (parentIndex > 0 && HasHigherPriority(node, parentNode)) {
+        CascadeUp(node);
+      } else {
+        //Note that CascadeDown will be called if parentNode == node (that is, node is the root)
+        CascadeDown(node);
+      }
+    }
+
+    /// <summary>
+    /// Removes a node from the queue.  Note that the node does not need to be the head of the queue.  O(log n)
+    /// </summary>
+    public void Remove(EventQueueNode node) {
+      if (!Contains(node)) {
+        return;
+      }
+      if (_numNodes <= 1) {
+        _nodes[1] = null;
+        _numNodes = 0;
+        return;
+      }
+
+      //Make sure the node is the last node in the queue
+      bool wasSwapped = false;
+      EventQueueNode formerLastNode = _nodes[_numNodes];
+      if (node.QueueIndex != _numNodes) {
+        //Swap the node with the last node
+        Swap(node, formerLastNode);
+        wasSwapped = true;
+      }
+
+      _numNodes--;
+      _nodes[node.QueueIndex] = null;
+
+      if (wasSwapped) {
+        //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate
+        OnNodeUpdated(formerLastNode);
+      }
+    }
+
+    public IEnumerator<EventQueueNode> GetEnumerator() {
+      for (int i = 1; i <= _numNodes; i++)
+        yield return _nodes[i];
+    }
+
+    IEnumerator IEnumerable.GetEnumerator() {
+      return GetEnumerator();
+    }
+
+    /// <summary>
+    /// <b>Should not be called in production code.</b>
+    /// Checks to make sure the queue is still in a valid state.  Used for testing/debugging the queue.
+    /// </summary>
+    public bool IsValidQueue() {
+      for (int i = 1; i < _nodes.Length; i++) {
+        if (_nodes[i] != null) {
+          int childLeftIndex = 2 * i;
+          if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i]))
+            return false;
+
+          int childRightIndex = childLeftIndex + 1;
+          if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i]))
+            return false;
+        }
+      }
+      return true;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/EventQueueNode.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/EventQueueNode.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/EventQueueNode.cs	(revision 18027)
@@ -0,0 +1,40 @@
+﻿using System;
+
+namespace SimSharp {
+  public class EventQueueNode {
+    /// <summary>
+    /// The Priority to insert this node at. Must be set BEFORE adding a node to the queue.
+    /// </summary>
+    public DateTime PrimaryPriority { get; set; }
+
+    /// <summary>
+    /// An integer priority that will be used to distinguish nodes with the same
+    /// <see cref="PrimaryPriority"/> and before the <see cref="InsertionIndex"/> is used.
+    /// Must be set BEFORE adding a node to the queue.
+    /// </summary>
+    /// <remarks>
+    /// A lower value means higher priority, thus int.MinValue has highest priority.
+    /// </remarks>
+    public int SecondaryPriority { get; set; }
+
+    /// <summary>
+    /// The event that is associated with this node.
+    /// </summary>
+    public Event Event { get; set; }
+
+    /// <summary>
+    /// <b>Used by the priority queue - do not edit this value.</b>
+    /// Represents the order the node was inserted in
+    /// </summary>
+    /// <remarks>
+    /// This is unique among all inserted nodes and thus represents the third and final priority.
+    /// </remarks>
+    public long InsertionIndex { get; set; }
+
+    /// <summary>
+    /// <b>Used by the priority queue - do not edit this value.</b>
+    /// Represents the current position in the queue
+    /// </summary>
+    public int QueueIndex { get; set; }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/Extensions.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/Extensions.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/Extensions.cs	(revision 18027)
@@ -0,0 +1,36 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SimSharp {
+  public static class Extensions {
+    public static IEnumerable<T> MaxItems<T>(this IEnumerable<T> source, Func<T, IComparable> selector) {
+      var enumerator = source.GetEnumerator();
+      if (!enumerator.MoveNext()) return Enumerable.Empty<T>();
+      var item = enumerator.Current;
+      var max = selector(item);
+      var result = new List<T> { item };
+      
+      while (enumerator.MoveNext()) {
+        item = enumerator.Current;
+        var comparable = selector(item);
+        var comparison = comparable.CompareTo(max);
+        if (comparison > 0) {
+          result.Clear();
+          result.Add(item);
+          max = comparable;
+        } else if (comparison == 0) {
+          result.Add(item);
+        }
+      }
+      return result;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/GenericPriorityQueue.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/GenericPriorityQueue.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/GenericPriorityQueue.cs	(revision 18027)
@@ -0,0 +1,933 @@
+﻿#region License
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013 Daniel "BlueRaja" Pflughoeft
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+ */
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace SimSharp {
+  /// <summary>
+  /// A copy of StablePriorityQueue which also has generic explicit priority-type
+  /// </summary>
+  /// <remarks>
+  /// Original sources from https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
+  /// </remarks>
+  /// <typeparam name="TItem">The values in the queue.  Must extend the GenericPriorityQueue class</typeparam>
+  /// <typeparam name="TPriority">The priority-type.  Must extend IComparable&lt;TPriority&gt;</typeparam>
+  public sealed class GenericPriorityQueue<TItem, TPriority> : IEnumerable<TItem>
+      where TItem : GenericPriorityQueueNode<TPriority>
+      where TPriority : IComparable<TPriority> {
+    private int _numNodes;
+    private TItem[] _nodes;
+    private long _numNodesEverEnqueued;
+    private readonly Comparison<TPriority> _comparer;
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="maxNodes">The max nodes ever allowed to be enqueued (going over this will cause undefined behavior)</param>
+    public GenericPriorityQueue(int maxNodes) : this(maxNodes, Comparer<TPriority>.Default) { }
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="maxNodes">The max nodes ever allowed to be enqueued (going over this will cause undefined behavior)</param>
+    /// <param name="comparer">The comparer used to compare TPriority values.</param>
+    public GenericPriorityQueue(int maxNodes, IComparer<TPriority> comparer) : this(maxNodes, comparer.Compare) { }
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="maxNodes">The max nodes ever allowed to be enqueued (going over this will cause undefined behavior)</param>
+    /// <param name="comparer">The comparison function to use to compare TPriority values</param>
+    public GenericPriorityQueue(int maxNodes, Comparison<TPriority> comparer) {
+#if DEBUG
+      if (maxNodes <= 0) {
+        throw new InvalidOperationException("New queue size cannot be smaller than 1");
+      }
+#endif
+
+      _numNodes = 0;
+      _nodes = new TItem[maxNodes + 1];
+      _numNodesEverEnqueued = 0;
+      _comparer = comparer;
+    }
+
+    /// <summary>
+    /// Returns the number of nodes in the queue.
+    /// O(1)
+    /// </summary>
+    public int Count {
+      get {
+        return _numNodes;
+      }
+    }
+
+    /// <summary>
+    /// Returns the maximum number of items that can be enqueued at once in this queue.  Once you hit this number (ie. once Count == MaxSize),
+    /// attempting to enqueue another item will cause undefined behavior.  O(1)
+    /// </summary>
+    public int MaxSize {
+      get {
+        return _nodes.Length - 1;
+      }
+    }
+
+    /// <summary>
+    /// Removes every node from the queue.
+    /// O(n) (So, don't do this often!)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Clear() {
+      Array.Clear(_nodes, 1, _numNodes);
+      _numNodes = 0;
+    }
+
+    /// <summary>
+    /// Returns (in O(1)!) whether the given node is in the queue.  O(1)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public bool Contains(TItem node) {
+#if DEBUG
+      if (node == null) {
+        throw new ArgumentNullException("node");
+      }
+      if (node.QueueIndex < 0 || node.QueueIndex >= _nodes.Length) {
+        throw new InvalidOperationException("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?");
+      }
+#endif
+
+      return (_nodes[node.QueueIndex] == node);
+    }
+
+    /// <summary>
+    /// Enqueue a node to the priority queue.  Lower values are placed in front. Ties are broken by first-in-first-out.
+    /// If the queue is full, the result is undefined.
+    /// If the node is already enqueued, the result is undefined.
+    /// O(log n)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Enqueue(TItem node, TPriority priority) {
+#if DEBUG
+      if (node == null) {
+        throw new ArgumentNullException("node");
+      }
+      if (_numNodes >= _nodes.Length - 1) {
+        throw new InvalidOperationException("Queue is full - node cannot be added: " + node);
+      }
+      if (Contains(node)) {
+        throw new InvalidOperationException("Node is already enqueued: " + node);
+      }
+#endif
+
+      node.Priority = priority;
+      _numNodes++;
+      _nodes[_numNodes] = node;
+      node.QueueIndex = _numNodes;
+      node.InsertionIndex = _numNodesEverEnqueued++;
+      CascadeUp(node);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void CascadeUp(TItem node) {
+      //aka Heapify-up
+      int parent;
+      if (node.QueueIndex > 1) {
+        parent = node.QueueIndex >> 1;
+        TItem parentNode = _nodes[parent];
+        if (HasHigherPriority(parentNode, node))
+          return;
+
+        //Node has lower priority value, so move parent down the heap to make room
+        _nodes[node.QueueIndex] = parentNode;
+        parentNode.QueueIndex = node.QueueIndex;
+
+        node.QueueIndex = parent;
+      } else {
+        return;
+      }
+      while (parent > 1) {
+        parent >>= 1;
+        TItem parentNode = _nodes[parent];
+        if (HasHigherPriority(parentNode, node))
+          break;
+
+        //Node has lower priority value, so move parent down the heap to make room
+        _nodes[node.QueueIndex] = parentNode;
+        parentNode.QueueIndex = node.QueueIndex;
+
+        node.QueueIndex = parent;
+      }
+      _nodes[node.QueueIndex] = node;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void CascadeDown(TItem node) {
+      //aka Heapify-down
+      int finalQueueIndex = node.QueueIndex;
+      int childLeftIndex = 2 * finalQueueIndex;
+
+      // If leaf node, we're done
+      if (childLeftIndex > _numNodes) {
+        return;
+      }
+
+      // Check if the left-child is higher-priority than the current node
+      int childRightIndex = childLeftIndex + 1;
+      TItem childLeft = _nodes[childLeftIndex];
+      if (HasHigherPriority(childLeft, node)) {
+        // Check if there is a right child. If not, swap and finish.
+        if (childRightIndex > _numNodes) {
+          node.QueueIndex = childLeftIndex;
+          childLeft.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = childLeft;
+          _nodes[childLeftIndex] = node;
+          return;
+        }
+        // Check if the left-child is higher-priority than the right-child
+        TItem childRight = _nodes[childRightIndex];
+        if (HasHigherPriority(childLeft, childRight)) {
+          // left is highest, move it up and continue
+          childLeft.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = childLeft;
+          finalQueueIndex = childLeftIndex;
+        } else {
+          // right is even higher, move it up and continue
+          childRight.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = childRight;
+          finalQueueIndex = childRightIndex;
+        }
+      }
+      // Not swapping with left-child, does right-child exist?
+      else if (childRightIndex > _numNodes) {
+        return;
+      } else {
+        // Check if the right-child is higher-priority than the current node
+        TItem childRight = _nodes[childRightIndex];
+        if (HasHigherPriority(childRight, node)) {
+          childRight.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = childRight;
+          finalQueueIndex = childRightIndex;
+        }
+        // Neither child is higher-priority than current, so finish and stop.
+        else {
+          return;
+        }
+      }
+
+      while (true) {
+        childLeftIndex = 2 * finalQueueIndex;
+
+        // If leaf node, we're done
+        if (childLeftIndex > _numNodes) {
+          node.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = node;
+          break;
+        }
+
+        // Check if the left-child is higher-priority than the current node
+        childRightIndex = childLeftIndex + 1;
+        childLeft = _nodes[childLeftIndex];
+        if (HasHigherPriority(childLeft, node)) {
+          // Check if there is a right child. If not, swap and finish.
+          if (childRightIndex > _numNodes) {
+            node.QueueIndex = childLeftIndex;
+            childLeft.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = childLeft;
+            _nodes[childLeftIndex] = node;
+            break;
+          }
+          // Check if the left-child is higher-priority than the right-child
+          TItem childRight = _nodes[childRightIndex];
+          if (HasHigherPriority(childLeft, childRight)) {
+            // left is highest, move it up and continue
+            childLeft.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = childLeft;
+            finalQueueIndex = childLeftIndex;
+          } else {
+            // right is even higher, move it up and continue
+            childRight.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = childRight;
+            finalQueueIndex = childRightIndex;
+          }
+        }
+        // Not swapping with left-child, does right-child exist?
+        else if (childRightIndex > _numNodes) {
+          node.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = node;
+          break;
+        } else {
+          // Check if the right-child is higher-priority than the current node
+          TItem childRight = _nodes[childRightIndex];
+          if (HasHigherPriority(childRight, node)) {
+            childRight.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = childRight;
+            finalQueueIndex = childRightIndex;
+          }
+          // Neither child is higher-priority than current, so finish and stop.
+          else {
+            node.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = node;
+            break;
+          }
+        }
+      }
+    }
+
+    /// <summary>
+    /// Returns true if 'higher' has higher priority than 'lower', false otherwise.
+    /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool HasHigherPriority(TItem higher, TItem lower) {
+      var cmp = _comparer(higher.Priority, lower.Priority);
+      return (cmp < 0 || (cmp == 0 && higher.InsertionIndex < lower.InsertionIndex));
+    }
+
+    /// <summary>
+    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
+    /// If queue is empty, result is undefined
+    /// O(log n)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public TItem Dequeue() {
+#if DEBUG
+      if (_numNodes <= 0) {
+        throw new InvalidOperationException("Cannot call Dequeue() on an empty queue");
+      }
+
+      if (!IsValidQueue()) {
+        throw new InvalidOperationException("Queue has been corrupted (Did you update a node priority manually instead of calling UpdatePriority()?" +
+                                            "Or add the same node to two different queues?)");
+      }
+#endif
+
+      TItem returnMe = _nodes[1];
+      //If the node is already the last node, we can remove it immediately
+      if (_numNodes == 1) {
+        _nodes[1] = null;
+        _numNodes = 0;
+        return returnMe;
+      }
+
+      //Swap the node with the last node
+      TItem formerLastNode = _nodes[_numNodes];
+      _nodes[1] = formerLastNode;
+      formerLastNode.QueueIndex = 1;
+      _nodes[_numNodes] = null;
+      _numNodes--;
+
+      //Now bubble formerLastNode (which is no longer the last node) down
+      CascadeDown(formerLastNode);
+      return returnMe;
+    }
+
+    /// <summary>
+    /// Resize the queue so it can accept more nodes.  All currently enqueued nodes are remain.
+    /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior
+    /// O(n)
+    /// </summary>
+    public void Resize(int maxNodes) {
+#if DEBUG
+      if (maxNodes <= 0) {
+        throw new InvalidOperationException("Queue size cannot be smaller than 1");
+      }
+
+      if (maxNodes < _numNodes) {
+        throw new InvalidOperationException("Called Resize(" + maxNodes + "), but current queue contains " + _numNodes + " nodes");
+      }
+#endif
+
+      TItem[] newArray = new TItem[maxNodes + 1];
+      int highestIndexToCopy = Math.Min(maxNodes, _numNodes);
+      Array.Copy(_nodes, newArray, highestIndexToCopy + 1);
+      _nodes = newArray;
+    }
+
+    /// <summary>
+    /// Returns the head of the queue, without removing it (use Dequeue() for that).
+    /// If the queue is empty, behavior is undefined.
+    /// O(1)
+    /// </summary>
+    public TItem First {
+      get {
+#if DEBUG
+        if (_numNodes <= 0) {
+          throw new InvalidOperationException("Cannot call .First on an empty queue");
+        }
+#endif
+
+        return _nodes[1];
+      }
+    }
+
+    /// <summary>
+    /// This method must be called on a node every time its priority changes while it is in the queue.  
+    /// <b>Forgetting to call this method will result in a corrupted queue!</b>
+    /// Calling this method on a node not in the queue results in undefined behavior
+    /// O(log n)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void UpdatePriority(TItem node, TPriority priority) {
+#if DEBUG
+      if (node == null) {
+        throw new ArgumentNullException("node");
+      }
+      if (!Contains(node)) {
+        throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + node);
+      }
+#endif
+
+      node.Priority = priority;
+      OnNodeUpdated(node);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void OnNodeUpdated(TItem node) {
+      //Bubble the updated node up or down as appropriate
+      int parentIndex = node.QueueIndex >> 1;
+
+      if (parentIndex > 0 && HasHigherPriority(node, _nodes[parentIndex])) {
+        CascadeUp(node);
+      } else {
+        //Note that CascadeDown will be called if parentNode == node (that is, node is the root)
+        CascadeDown(node);
+      }
+    }
+
+    /// <summary>
+    /// Removes a node from the queue.  The node does not need to be the head of the queue.  
+    /// If the node is not in the queue, the result is undefined.  If unsure, check Contains() first
+    /// O(log n)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Remove(TItem node) {
+#if DEBUG
+      if (node == null) {
+        throw new ArgumentNullException("node");
+      }
+      if (!Contains(node)) {
+        throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + node);
+      }
+#endif
+
+      //If the node is already the last node, we can remove it immediately
+      if (node.QueueIndex == _numNodes) {
+        _nodes[_numNodes] = null;
+        _numNodes--;
+        return;
+      }
+
+      //Swap the node with the last node
+      TItem formerLastNode = _nodes[_numNodes];
+      _nodes[node.QueueIndex] = formerLastNode;
+      formerLastNode.QueueIndex = node.QueueIndex;
+      _nodes[_numNodes] = null;
+      _numNodes--;
+
+      //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate
+      OnNodeUpdated(formerLastNode);
+    }
+
+    public IEnumerator<TItem> GetEnumerator() {
+      IEnumerable<TItem> e = new ArraySegment<TItem>(_nodes, 1, _numNodes);
+      return e.GetEnumerator();
+    }
+
+    IEnumerator IEnumerable.GetEnumerator() {
+      return GetEnumerator();
+    }
+
+    /// <summary>
+    /// <b>Should not be called in production code.</b>
+    /// Checks to make sure the queue is still in a valid state.  Used for testing/debugging the queue.
+    /// </summary>
+    public bool IsValidQueue() {
+      for (int i = 1; i < _nodes.Length; i++) {
+        if (_nodes[i] != null) {
+          int childLeftIndex = 2 * i;
+          if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i]))
+            return false;
+
+          int childRightIndex = childLeftIndex + 1;
+          if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i]))
+            return false;
+        }
+      }
+      return true;
+    }
+  }
+
+  /// <summary>
+  /// A copy of StablePriorityQueue where the items themselves contain an implicit priority.
+  /// </summary>
+  /// <remarks>
+  /// Either <typeparamref name="TItem"/> implements IComparable&lt;typeparamref name="TItem"/>&gt; or
+  /// a custom comparer must be provided.
+  /// </remarks>
+  /// <typeparam name="TItem">The values in the queue. Must extend the GenericPriorityQueueNode class</typeparam>
+  public sealed class GenericPriorityQueue<TItem> : IEnumerable<TItem>
+      where TItem : GenericPriorityQueueNode {
+    private int _numNodes;
+    private TItem[] _nodes;
+    private long _numNodesEverEnqueued;
+    private readonly Comparison<TItem> _comparer;
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="maxNodes">The max nodes ever allowed to be enqueued (going over this will cause undefined behavior)</param>
+    public GenericPriorityQueue(int maxNodes) : this(maxNodes, Comparer<TItem>.Default) { }
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="maxNodes">The max nodes ever allowed to be enqueued (going over this will cause undefined behavior)</param>
+    /// <param name="comparer">The comparer used to compare TPriority values.</param>
+    public GenericPriorityQueue(int maxNodes, IComparer<TItem> comparer) : this(maxNodes, comparer.Compare) { }
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="maxNodes">The max nodes ever allowed to be enqueued (going over this will cause undefined behavior)</param>
+    /// <param name="comparer">The comparison function to use to compare TPriority values</param>
+    public GenericPriorityQueue(int maxNodes, Comparison<TItem> comparer) {
+#if DEBUG
+      if (maxNodes <= 0) {
+        throw new InvalidOperationException("New queue size cannot be smaller than 1");
+      }
+#endif
+
+      _numNodes = 0;
+      _nodes = new TItem[maxNodes + 1];
+      _numNodesEverEnqueued = 0;
+      _comparer = comparer;
+    }
+
+    /// <summary>
+    /// Returns the number of nodes in the queue.
+    /// O(1)
+    /// </summary>
+    public int Count {
+      get {
+        return _numNodes;
+      }
+    }
+
+    /// <summary>
+    /// Returns the maximum number of items that can be enqueued at once in this queue.  Once you hit this number (ie. once Count == MaxSize),
+    /// attempting to enqueue another item will cause undefined behavior.  O(1)
+    /// </summary>
+    public int MaxSize {
+      get {
+        return _nodes.Length - 1;
+      }
+    }
+
+    /// <summary>
+    /// Removes every node from the queue.
+    /// O(n) (So, don't do this often!)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Clear() {
+      Array.Clear(_nodes, 1, _numNodes);
+      _numNodes = 0;
+    }
+
+    /// <summary>
+    /// Returns (in O(1)!) whether the given node is in the queue.  O(1)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public bool Contains(TItem node) {
+#if DEBUG
+      if (node == null) {
+        throw new ArgumentNullException("node");
+      }
+      if (node.QueueIndex < 0 || node.QueueIndex >= _nodes.Length) {
+        throw new InvalidOperationException("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?");
+      }
+#endif
+
+      return (_nodes[node.QueueIndex] == node);
+    }
+
+    /// <summary>
+    /// Enqueue a node to the priority queue.  Lower values are placed in front. Ties are broken by first-in-first-out.
+    /// If the queue is full, the result is undefined.
+    /// If the node is already enqueued, the result is undefined.
+    /// O(log n)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Enqueue(TItem node) {
+#if DEBUG
+      if (node == null) {
+        throw new ArgumentNullException("node");
+      }
+      if (_numNodes >= _nodes.Length - 1) {
+        throw new InvalidOperationException("Queue is full - node cannot be added: " + node);
+      }
+      if (Contains(node)) {
+        throw new InvalidOperationException("Node is already enqueued: " + node);
+      }
+#endif
+      
+      _numNodes++;
+      _nodes[_numNodes] = node;
+      node.QueueIndex = _numNodes;
+      node.InsertionIndex = _numNodesEverEnqueued++;
+      CascadeUp(node);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void CascadeUp(TItem node) {
+      //aka Heapify-up
+      int parent;
+      if (node.QueueIndex > 1) {
+        parent = node.QueueIndex >> 1;
+        TItem parentNode = _nodes[parent];
+        if (HasHigherPriority(parentNode, node))
+          return;
+
+        //Node has lower priority value, so move parent down the heap to make room
+        _nodes[node.QueueIndex] = parentNode;
+        parentNode.QueueIndex = node.QueueIndex;
+
+        node.QueueIndex = parent;
+      } else {
+        return;
+      }
+      while (parent > 1) {
+        parent >>= 1;
+        TItem parentNode = _nodes[parent];
+        if (HasHigherPriority(parentNode, node))
+          break;
+
+        //Node has lower priority value, so move parent down the heap to make room
+        _nodes[node.QueueIndex] = parentNode;
+        parentNode.QueueIndex = node.QueueIndex;
+
+        node.QueueIndex = parent;
+      }
+      _nodes[node.QueueIndex] = node;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void CascadeDown(TItem node) {
+      //aka Heapify-down
+      int finalQueueIndex = node.QueueIndex;
+      int childLeftIndex = 2 * finalQueueIndex;
+
+      // If leaf node, we're done
+      if (childLeftIndex > _numNodes) {
+        return;
+      }
+
+      // Check if the left-child is higher-priority than the current node
+      int childRightIndex = childLeftIndex + 1;
+      TItem childLeft = _nodes[childLeftIndex];
+      if (HasHigherPriority(childLeft, node)) {
+        // Check if there is a right child. If not, swap and finish.
+        if (childRightIndex > _numNodes) {
+          node.QueueIndex = childLeftIndex;
+          childLeft.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = childLeft;
+          _nodes[childLeftIndex] = node;
+          return;
+        }
+        // Check if the left-child is higher-priority than the right-child
+        TItem childRight = _nodes[childRightIndex];
+        if (HasHigherPriority(childLeft, childRight)) {
+          // left is highest, move it up and continue
+          childLeft.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = childLeft;
+          finalQueueIndex = childLeftIndex;
+        } else {
+          // right is even higher, move it up and continue
+          childRight.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = childRight;
+          finalQueueIndex = childRightIndex;
+        }
+      }
+      // Not swapping with left-child, does right-child exist?
+      else if (childRightIndex > _numNodes) {
+        return;
+      } else {
+        // Check if the right-child is higher-priority than the current node
+        TItem childRight = _nodes[childRightIndex];
+        if (HasHigherPriority(childRight, node)) {
+          childRight.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = childRight;
+          finalQueueIndex = childRightIndex;
+        }
+        // Neither child is higher-priority than current, so finish and stop.
+        else {
+          return;
+        }
+      }
+
+      while (true) {
+        childLeftIndex = 2 * finalQueueIndex;
+
+        // If leaf node, we're done
+        if (childLeftIndex > _numNodes) {
+          node.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = node;
+          break;
+        }
+
+        // Check if the left-child is higher-priority than the current node
+        childRightIndex = childLeftIndex + 1;
+        childLeft = _nodes[childLeftIndex];
+        if (HasHigherPriority(childLeft, node)) {
+          // Check if there is a right child. If not, swap and finish.
+          if (childRightIndex > _numNodes) {
+            node.QueueIndex = childLeftIndex;
+            childLeft.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = childLeft;
+            _nodes[childLeftIndex] = node;
+            break;
+          }
+          // Check if the left-child is higher-priority than the right-child
+          TItem childRight = _nodes[childRightIndex];
+          if (HasHigherPriority(childLeft, childRight)) {
+            // left is highest, move it up and continue
+            childLeft.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = childLeft;
+            finalQueueIndex = childLeftIndex;
+          } else {
+            // right is even higher, move it up and continue
+            childRight.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = childRight;
+            finalQueueIndex = childRightIndex;
+          }
+        }
+        // Not swapping with left-child, does right-child exist?
+        else if (childRightIndex > _numNodes) {
+          node.QueueIndex = finalQueueIndex;
+          _nodes[finalQueueIndex] = node;
+          break;
+        } else {
+          // Check if the right-child is higher-priority than the current node
+          TItem childRight = _nodes[childRightIndex];
+          if (HasHigherPriority(childRight, node)) {
+            childRight.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = childRight;
+            finalQueueIndex = childRightIndex;
+          }
+          // Neither child is higher-priority than current, so finish and stop.
+          else {
+            node.QueueIndex = finalQueueIndex;
+            _nodes[finalQueueIndex] = node;
+            break;
+          }
+        }
+      }
+    }
+
+    /// <summary>
+    /// Returns true if 'higher' has higher priority than 'lower', false otherwise.
+    /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool HasHigherPriority(TItem higher, TItem lower) {
+      var cmp = _comparer(higher, lower);
+      return (cmp < 0 || (cmp == 0 && higher.InsertionIndex < lower.InsertionIndex));
+    }
+
+    /// <summary>
+    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
+    /// If queue is empty, result is undefined
+    /// O(log n)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public TItem Dequeue() {
+#if DEBUG
+      if (_numNodes <= 0) {
+        throw new InvalidOperationException("Cannot call Dequeue() on an empty queue");
+      }
+
+      if (!IsValidQueue()) {
+        throw new InvalidOperationException("Queue has been corrupted (Did you update a node priority manually instead of calling UpdatePriority()?" +
+                                            "Or add the same node to two different queues?)");
+      }
+#endif
+
+      TItem returnMe = _nodes[1];
+      //If the node is already the last node, we can remove it immediately
+      if (_numNodes == 1) {
+        _nodes[1] = null;
+        _numNodes = 0;
+        return returnMe;
+      }
+
+      //Swap the node with the last node
+      TItem formerLastNode = _nodes[_numNodes];
+      _nodes[1] = formerLastNode;
+      formerLastNode.QueueIndex = 1;
+      _nodes[_numNodes] = null;
+      _numNodes--;
+
+      //Now bubble formerLastNode (which is no longer the last node) down
+      CascadeDown(formerLastNode);
+      return returnMe;
+    }
+
+    /// <summary>
+    /// Resize the queue so it can accept more nodes.  All currently enqueued nodes are remain.
+    /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior
+    /// O(n)
+    /// </summary>
+    public void Resize(int maxNodes) {
+#if DEBUG
+      if (maxNodes <= 0) {
+        throw new InvalidOperationException("Queue size cannot be smaller than 1");
+      }
+
+      if (maxNodes < _numNodes) {
+        throw new InvalidOperationException("Called Resize(" + maxNodes + "), but current queue contains " + _numNodes + " nodes");
+      }
+#endif
+
+      TItem[] newArray = new TItem[maxNodes + 1];
+      int highestIndexToCopy = Math.Min(maxNodes, _numNodes);
+      Array.Copy(_nodes, newArray, highestIndexToCopy + 1);
+      _nodes = newArray;
+    }
+
+    /// <summary>
+    /// Returns the head of the queue, without removing it (use Dequeue() for that).
+    /// If the queue is empty, behavior is undefined.
+    /// O(1)
+    /// </summary>
+    public TItem First {
+      get {
+#if DEBUG
+        if (_numNodes <= 0) {
+          throw new InvalidOperationException("Cannot call .First on an empty queue");
+        }
+#endif
+
+        return _nodes[1];
+      }
+    }
+
+    /// <summary>
+    /// This method must be called on a node every time its priority changes while it is in the queue.  
+    /// <b>Forgetting to call this method will result in a corrupted queue!</b>
+    /// Calling this method on a node not in the queue results in undefined behavior
+    /// O(log n)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void UpdatePriority(TItem node) {
+#if DEBUG
+      if (node == null) {
+        throw new ArgumentNullException("node");
+      }
+      if (!Contains(node)) {
+        throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + node);
+      }
+#endif
+      
+      OnNodeUpdated(node);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void OnNodeUpdated(TItem node) {
+      //Bubble the updated node up or down as appropriate
+      int parentIndex = node.QueueIndex >> 1;
+
+      if (parentIndex > 0 && HasHigherPriority(node, _nodes[parentIndex])) {
+        CascadeUp(node);
+      } else {
+        //Note that CascadeDown will be called if parentNode == node (that is, node is the root)
+        CascadeDown(node);
+      }
+    }
+
+    /// <summary>
+    /// Removes a node from the queue.  The node does not need to be the head of the queue.  
+    /// If the node is not in the queue, the result is undefined.  If unsure, check Contains() first
+    /// O(log n)
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Remove(TItem node) {
+#if DEBUG
+      if (node == null) {
+        throw new ArgumentNullException("node");
+      }
+      if (!Contains(node)) {
+        throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + node);
+      }
+#endif
+
+      //If the node is already the last node, we can remove it immediately
+      if (node.QueueIndex == _numNodes) {
+        _nodes[_numNodes] = null;
+        _numNodes--;
+        return;
+      }
+
+      //Swap the node with the last node
+      TItem formerLastNode = _nodes[_numNodes];
+      _nodes[node.QueueIndex] = formerLastNode;
+      formerLastNode.QueueIndex = node.QueueIndex;
+      _nodes[_numNodes] = null;
+      _numNodes--;
+
+      //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate
+      OnNodeUpdated(formerLastNode);
+    }
+
+    public IEnumerator<TItem> GetEnumerator() {
+      IEnumerable<TItem> e = new ArraySegment<TItem>(_nodes, 1, _numNodes);
+      return e.GetEnumerator();
+    }
+
+    IEnumerator IEnumerable.GetEnumerator() {
+      return GetEnumerator();
+    }
+
+    /// <summary>
+    /// <b>Should not be called in production code.</b>
+    /// Checks to make sure the queue is still in a valid state.  Used for testing/debugging the queue.
+    /// </summary>
+    public bool IsValidQueue() {
+      for (int i = 1; i < _nodes.Length; i++) {
+        if (_nodes[i] != null) {
+          int childLeftIndex = 2 * i;
+          if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i]))
+            return false;
+
+          int childRightIndex = childLeftIndex + 1;
+          if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i]))
+            return false;
+        }
+      }
+      return true;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/GenericPriorityQueueNode.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/GenericPriorityQueueNode.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/GenericPriorityQueueNode.cs	(revision 18027)
@@ -0,0 +1,58 @@
+﻿#region License
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013 Daniel "BlueRaja" Pflughoeft
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+ */
+#endregion
+
+namespace SimSharp {
+
+  /// <summary>
+  /// A node class for the generic priority queue
+  /// </summary>
+  public class GenericPriorityQueueNode {
+    /// <summary>
+    /// Represents the current position in the queue
+    /// </summary>
+    public int QueueIndex { get; internal set; }
+
+    /// <summary>
+    /// Represents the order the node was inserted in
+    /// </summary>
+    public long InsertionIndex { get; internal set; }
+  }
+
+  /// <summary>
+  /// A node class for the generic priority queue with an explicit priority type
+  /// </summary>
+  /// <remarks>
+  /// Original sources from https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
+  /// </remarks>
+  /// <typeparam name="TPriority"></typeparam>
+  public class GenericPriorityQueueNode<TPriority> : GenericPriorityQueueNode {
+    /// <summary>
+    /// The Priority to insert this node at.  Must be set BEFORE adding a node to the queue (ideally just once, in the node's constructor).
+    /// Should not be manually edited once the node has been enqueued - use queue.UpdatePriority() instead
+    /// </summary>
+    public TPriority Priority { get; protected internal set; }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/LICENSE-PriortyQueue.txt
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/LICENSE-PriortyQueue.txt	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/LICENSE-PriortyQueue.txt	(revision 18027)
@@ -0,0 +1,15 @@
+Daniel "BlueRaja" Pflughoeft requires to take not of the following copyrights and permissions:
+
+The MIT License (MIT)
+
+Copyright (c) 2013 Daniel "BlueRaja" Pflughoeft
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/ReverseComparer.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/ReverseComparer.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/ReverseComparer.cs	(revision 18027)
@@ -0,0 +1,19 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace SimSharp {
+  public class ReverseComparer<T> : IComparer<T> where T : IComparable {
+    private static readonly IComparer<T> DefaultComparer = Comparer<T>.Default;
+
+    public int Compare(T x, T y) {
+      return DefaultComparer.Compare(y, x);
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/SimplePriorityQueue.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/SimplePriorityQueue.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/SimplePriorityQueue.cs	(revision 18027)
@@ -0,0 +1,837 @@
+﻿#region License
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013 Daniel "BlueRaja" Pflughoeft
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+ */
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SimSharp {
+  /// <summary>
+  /// A simplified priority queue implementation.  Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than
+  /// FastPriorityQueue
+  /// Methods tagged as O(1) or O(log n) are assuming there are no duplicates.  Duplicates may increase the algorithmic complexity.
+  /// </summary>
+  /// <remarks>
+  /// Original sources from https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
+  /// </remarks>
+  /// <typeparam name="TItem">The type to enqueue</typeparam>
+  /// <typeparam name="TPriority">The priority-type to use for nodes.  Must extend IComparable&lt;TPriority&gt;</typeparam>
+  public class SimplePriorityQueue<TItem, TPriority> : IEnumerable<TItem>
+      where TPriority : IComparable<TPriority> {
+    private class SimpleNode : GenericPriorityQueueNode<TPriority> {
+      public TItem Data { get; private set; }
+
+      public SimpleNode(TItem data) {
+        Data = data;
+      }
+    }
+
+    private const int INITIAL_QUEUE_SIZE = 64;
+    private readonly GenericPriorityQueue<SimpleNode, TPriority> _queue;
+    private readonly Dictionary<TItem, IList<SimpleNode>> _itemToNodesCache;
+    private readonly IList<SimpleNode> _nullNodesCache;
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    public SimplePriorityQueue() : this(Comparer<TPriority>.Default) { }
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="comparer">The comparer used to compare TPriority values.  Defaults to Comparer&lt;TPriority&gt;.default</param>
+    public SimplePriorityQueue(IComparer<TPriority> comparer) : this(comparer.Compare) { }
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="comparer">The comparison function to use to compare TPriority values</param>
+    public SimplePriorityQueue(Comparison<TPriority> comparer) {
+      _queue = new GenericPriorityQueue<SimpleNode, TPriority>(INITIAL_QUEUE_SIZE, comparer);
+      _itemToNodesCache = new Dictionary<TItem, IList<SimpleNode>>();
+      _nullNodesCache = new List<SimpleNode>();
+    }
+
+    /// <summary>
+    /// Given an item of type T, returns the exist SimpleNode in the queue
+    /// </summary>
+    private SimpleNode GetExistingNode(TItem item) {
+      if (item == null) {
+        return _nullNodesCache.Count > 0 ? _nullNodesCache[0] : null;
+      }
+
+      IList<SimpleNode> nodes;
+      if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
+        return null;
+      }
+      return nodes[0];
+    }
+
+    /// <summary>
+    /// Adds an item to the Node-cache to allow for many methods to be O(1) or O(log n)
+    /// </summary>
+    private void AddToNodeCache(SimpleNode node) {
+      if (node.Data == null) {
+        _nullNodesCache.Add(node);
+        return;
+      }
+
+      IList<SimpleNode> nodes;
+      if (!_itemToNodesCache.TryGetValue(node.Data, out nodes)) {
+        nodes = new List<SimpleNode>();
+        _itemToNodesCache[node.Data] = nodes;
+      }
+      nodes.Add(node);
+    }
+
+    /// <summary>
+    /// Removes an item to the Node-cache to allow for many methods to be O(1) or O(log n) (assuming no duplicates)
+    /// </summary>
+    private void RemoveFromNodeCache(SimpleNode node) {
+      if (node.Data == null) {
+        _nullNodesCache.Remove(node);
+        return;
+      }
+
+      IList<SimpleNode> nodes;
+      if (!_itemToNodesCache.TryGetValue(node.Data, out nodes)) {
+        return;
+      }
+      nodes.Remove(node);
+      if (nodes.Count == 0) {
+        _itemToNodesCache.Remove(node.Data);
+      }
+    }
+
+    /// <summary>
+    /// Returns the number of nodes in the queue.
+    /// O(1)
+    /// </summary>
+    public int Count {
+      get {
+        return _queue.Count;
+      }
+    }
+
+
+    /// <summary>
+    /// Returns the head of the queue, without removing it (use Dequeue() for that).
+    /// Throws an exception when the queue is empty.
+    /// O(1)
+    /// </summary>
+    public TItem First {
+      get {
+        if (_queue.Count <= 0) {
+          throw new InvalidOperationException("Cannot call .First on an empty queue");
+        }
+
+        return _queue.First.Data;
+      }
+    }
+
+    /// <summary>
+    /// Returns the top priority of the queue.
+    /// Throws an exception when the queue is empty.
+    /// O(1)
+    /// </summary>
+    public TPriority Peek {
+      get {
+        lock (_queue) {
+          if (_queue.Count <= 0) {
+            throw new InvalidOperationException("Cannot call .Peek on an empty queue");
+          }
+          return _queue.First.Priority;
+        }
+      }
+    }
+
+    /// <summary>
+    /// Removes every node from the queue.
+    /// O(n)
+    /// </summary>
+    public void Clear() {
+      _queue.Clear();
+      _itemToNodesCache.Clear();
+      _nullNodesCache.Clear();
+    }
+
+    /// <summary>
+    /// Returns whether the given item is in the queue.
+    /// O(1)
+    /// </summary>
+    public bool Contains(TItem item) {
+      if (item == null) {
+        return _nullNodesCache.Count > 0;
+      }
+      return _itemToNodesCache.ContainsKey(item);
+    }
+
+    /// <summary>
+    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
+    /// If queue is empty, throws an exception
+    /// O(log n)
+    /// </summary>
+    public TItem Dequeue() {
+      if (_queue.Count <= 0) {
+        throw new InvalidOperationException("Cannot call Dequeue() on an empty queue");
+      }
+
+      SimpleNode node = _queue.Dequeue();
+      RemoveFromNodeCache(node);
+      return node.Data;
+    }
+
+    /// <summary>
+    /// Enqueue the item with the given priority, without calling AddToNodeCache(node)
+    /// </summary>
+    /// <param name="item"></param>
+    /// <param name="priority"></param>
+    /// <returns></returns>
+    private SimpleNode EnqueueNoCache(TItem item, TPriority priority) {
+      SimpleNode node = new SimpleNode(item);
+      if (_queue.Count == _queue.MaxSize) {
+        _queue.Resize(_queue.MaxSize * 2 + 1);
+      }
+      _queue.Enqueue(node, priority);
+      return node;
+    }
+
+    /// <summary>
+    /// Enqueue a node to the priority queue.  Lower values are placed in front. Ties are broken by first-in-first-out.
+    /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'.
+    /// Duplicates and null-values are allowed.
+    /// O(log n)
+    /// </summary>
+    public void Enqueue(TItem item, TPriority priority) {
+      IList<SimpleNode> nodes;
+      if (item == null) {
+        nodes = _nullNodesCache;
+      } else if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
+        nodes = new List<SimpleNode>();
+        _itemToNodesCache[item] = nodes;
+      }
+      SimpleNode node = EnqueueNoCache(item, priority);
+      nodes.Add(node);
+    }
+
+    /// <summary>
+    /// Enqueue a node to the priority queue if it doesn't already exist.  Lower values are placed in front. Ties are broken by first-in-first-out.
+    /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'.  Null values are allowed.
+    /// Returns true if the node was successfully enqueued; false if it already exists.
+    /// O(log n)
+    /// </summary>
+    public bool EnqueueWithoutDuplicates(TItem item, TPriority priority) {
+      IList<SimpleNode> nodes;
+      if (item == null) {
+        if (_nullNodesCache.Count > 0) {
+          return false;
+        }
+        nodes = _nullNodesCache;
+      } else if (_itemToNodesCache.ContainsKey(item)) {
+        return false;
+      } else {
+        nodes = new List<SimpleNode>();
+        _itemToNodesCache[item] = nodes;
+      }
+      SimpleNode node = EnqueueNoCache(item, priority);
+      nodes.Add(node);
+      return true;
+    }
+
+    /// <summary>
+    /// Removes an item from the queue.  The item does not need to be the head of the queue.  
+    /// If the item is not in the queue, an exception is thrown.  If unsure, check Contains() first.
+    /// If multiple copies of the item are enqueued, only the first one is removed. 
+    /// O(log n)
+    /// </summary>
+    public void Remove(TItem item) {
+      SimpleNode removeMe;
+      IList<SimpleNode> nodes;
+      if (item == null) {
+        if (_nullNodesCache.Count == 0) {
+          throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
+        }
+        removeMe = _nullNodesCache[0];
+        nodes = _nullNodesCache;
+      } else {
+        if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
+          throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
+        }
+        removeMe = nodes[0];
+        if (nodes.Count == 1) {
+          _itemToNodesCache.Remove(item);
+        }
+      }
+      _queue.Remove(removeMe);
+      nodes.Remove(removeMe);
+    }
+
+    /// <summary>
+    /// Call this method to change the priority of an item.
+    /// Calling this method on a item not in the queue will throw an exception.
+    /// If the item is enqueued multiple times, only the first one will be updated.
+    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
+    /// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
+    /// O(log n)
+    /// </summary>
+    public void UpdatePriority(TItem item, TPriority priority) {
+      SimpleNode updateMe = GetExistingNode(item);
+      if (updateMe == null) {
+        throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item);
+      }
+      _queue.UpdatePriority(updateMe, priority);
+    }
+
+    /// <summary>
+    /// Returns the priority of the given item.
+    /// Calling this method on a item not in the queue will throw an exception.
+    /// If the item is enqueued multiple times, only the priority of the first will be returned.
+    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
+    /// to query all their priorities, please wrap your items in a wrapper class so they can be distinguished).
+    /// O(1)
+    /// </summary>
+    public TPriority GetPriority(TItem item) {
+      SimpleNode findMe = GetExistingNode(item);
+      if (findMe == null) {
+        throw new InvalidOperationException("Cannot call GetPriority() on a node which is not enqueued: " + item);
+      }
+      return findMe.Priority;
+    }
+
+    #region Try* methods for multithreading
+    /// Get the head of the queue, without removing it (use TryDequeue() for that).
+    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and First
+    /// Returns true if successful, false otherwise
+    /// O(1)
+    public bool TryFirst(out TItem first) {
+      if (_queue.Count <= 0) {
+        first = default(TItem);
+        return false;
+      }
+
+      first = _queue.First.Data;
+      return true;
+    }
+
+    /// <summary>
+    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and sets it to first.
+    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Dequeue()
+    /// Returns true if successful; false if queue was empty
+    /// O(log n)
+    /// </summary>
+    public bool TryDequeue(out TItem first) {
+      if (_queue.Count <= 0) {
+        first = default(TItem);
+        return false;
+      }
+
+      SimpleNode node = _queue.Dequeue();
+      first = node.Data;
+      RemoveFromNodeCache(node);
+      return true;
+    }
+
+    /// <summary>
+    /// Attempts to remove an item from the queue.  The item does not need to be the head of the queue.  
+    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Remove()
+    /// Returns true if the item was successfully removed, false if it wasn't in the queue.
+    /// If multiple copies of the item are enqueued, only the first one is removed. 
+    /// O(log n)
+    /// </summary>
+    public bool TryRemove(TItem item) {
+      SimpleNode removeMe;
+      IList<SimpleNode> nodes;
+      if (item == null) {
+        if (_nullNodesCache.Count == 0) {
+          return false;
+        }
+        removeMe = _nullNodesCache[0];
+        nodes = _nullNodesCache;
+      } else {
+        if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
+          return false;
+        }
+        removeMe = nodes[0];
+        if (nodes.Count == 1) {
+          _itemToNodesCache.Remove(item);
+        }
+      }
+      _queue.Remove(removeMe);
+      nodes.Remove(removeMe);
+      return true;
+    }
+
+    /// <summary>
+    /// Call this method to change the priority of an item.
+    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and UpdatePriority()
+    /// If the item is enqueued multiple times, only the first one will be updated.
+    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
+    /// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
+    /// Returns true if the item priority was updated, false otherwise.
+    /// O(log n)
+    /// </summary>
+    public bool TryUpdatePriority(TItem item, TPriority priority) {
+      SimpleNode updateMe = GetExistingNode(item);
+      if (updateMe == null) {
+        return false;
+      }
+      _queue.UpdatePriority(updateMe, priority);
+      return true;
+    }
+
+    /// <summary>
+    /// Attempt to get the priority of the given item.
+    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and GetPriority()
+    /// If the item is enqueued multiple times, only the priority of the first will be returned.
+    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
+    /// to query all their priorities, please wrap your items in a wrapper class so they can be distinguished).
+    /// Returns true if the item was found in the queue, false otherwise
+    /// O(1)
+    /// </summary>
+    public bool TryGetPriority(TItem item, out TPriority priority) {
+      SimpleNode findMe = GetExistingNode(item);
+      if (findMe == null) {
+        priority = default(TPriority);
+        return false;
+      }
+      priority = findMe.Priority;
+      return true;
+    }
+    #endregion
+
+    public IEnumerator<TItem> GetEnumerator() {
+      return _queue.Select(x => x.Data).GetEnumerator();
+    }
+
+    IEnumerator IEnumerable.GetEnumerator() {
+      return GetEnumerator();
+    }
+
+    public bool IsValidQueue() {
+      // Check all items in cache are in the queue
+      foreach (IList<SimpleNode> nodes in _itemToNodesCache.Values) {
+        foreach (SimpleNode node in nodes) {
+          if (!_queue.Contains(node)) {
+            return false;
+          }
+        }
+      }
+
+      // Check all items in queue are in cache
+      foreach (SimpleNode node in _queue) {
+        if (GetExistingNode(node.Data) == null) {
+          return false;
+        }
+      }
+
+      // Check queue structure itself
+      return _queue.IsValidQueue();
+    }
+  }
+
+  /// <summary>
+  /// A simplified priority queue implementation.  Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than
+  /// FastPriorityQueue
+  /// Methods tagged as O(1) or O(log n) are assuming there are no duplicates.  Duplicates may increase the algorithmic complexity.
+  /// </summary>
+  /// <typeparam name="TItem">The type to enqueue</typeparam>
+  public class SimplePriorityQueue<TItem> : IEnumerable<TItem> where TItem : IComparable<TItem> {
+    private class SimpleNode : GenericPriorityQueueNode, IComparable<SimpleNode>, IComparable {
+      public TItem Data { get; private set; }
+      private Comparison<TItem> _comparer;
+
+      public SimpleNode(TItem data, Comparison<TItem> comparer) {
+        Data = data;
+        _comparer = comparer;
+      }
+
+      public int CompareTo(SimpleNode other) {
+        if (other == null) return 1;
+        return _comparer(Data, other.Data);
+      }
+
+      public int CompareTo(object obj) {
+        if (obj is SimpleNode other) return CompareTo(other);
+        if (obj == null) return 1;
+        throw new ArgumentException("can only compare to objects of type SimpleNode.");
+      }
+    }
+
+    private const int INITIAL_QUEUE_SIZE = 64;
+    private readonly GenericPriorityQueue<SimpleNode> _queue;
+    private readonly Dictionary<TItem, IList<SimpleNode>> _itemToNodesCache;
+    private readonly IList<SimpleNode> _nullNodesCache;
+    private readonly Comparison<TItem> _comparer;
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    public SimplePriorityQueue() : this(Comparer<TItem>.Default) { }
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="comparer">The comparer used to compare TPriority values.  Defaults to Comparer&lt;TPriority&gt;.default</param>
+    public SimplePriorityQueue(IComparer<TItem> comparer) : this(comparer.Compare) { }
+
+    /// <summary>
+    /// Instantiate a new Priority Queue
+    /// </summary>
+    /// <param name="comparer">The comparison function to use to compare TPriority values</param>
+    public SimplePriorityQueue(Comparison<TItem> comparer) {
+      _comparer = comparer;
+      // the inner priority queue will always use the default comparer which in turn makes use of the specified comparer
+      _queue = new GenericPriorityQueue<SimpleNode>(INITIAL_QUEUE_SIZE, Comparer<SimpleNode>.Default);
+      _itemToNodesCache = new Dictionary<TItem, IList<SimpleNode>>();
+      _nullNodesCache = new List<SimpleNode>();
+    }
+
+    /// <summary>
+    /// Given an item of type T, returns the exist SimpleNode in the queue
+    /// </summary>
+    private SimpleNode GetExistingNode(TItem item) {
+      if (item == null) {
+        return _nullNodesCache.Count > 0 ? _nullNodesCache[0] : null;
+      }
+
+      IList<SimpleNode> nodes;
+      if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
+        return null;
+      }
+      return nodes[0];
+    }
+
+    /// <summary>
+    /// Adds an item to the Node-cache to allow for many methods to be O(1) or O(log n)
+    /// </summary>
+    private void AddToNodeCache(SimpleNode node) {
+      if (node.Data == null) {
+        _nullNodesCache.Add(node);
+        return;
+      }
+
+      IList<SimpleNode> nodes;
+      if (!_itemToNodesCache.TryGetValue(node.Data, out nodes)) {
+        nodes = new List<SimpleNode>();
+        _itemToNodesCache[node.Data] = nodes;
+      }
+      nodes.Add(node);
+    }
+
+    /// <summary>
+    /// Removes an item to the Node-cache to allow for many methods to be O(1) or O(log n) (assuming no duplicates)
+    /// </summary>
+    private void RemoveFromNodeCache(SimpleNode node) {
+      if (node.Data == null) {
+        _nullNodesCache.Remove(node);
+        return;
+      }
+
+      IList<SimpleNode> nodes;
+      if (!_itemToNodesCache.TryGetValue(node.Data, out nodes)) {
+        return;
+      }
+      nodes.Remove(node);
+      if (nodes.Count == 0) {
+        _itemToNodesCache.Remove(node.Data);
+      }
+    }
+
+    /// <summary>
+    /// Returns the number of nodes in the queue.
+    /// O(1)
+    /// </summary>
+    public int Count {
+      get {
+        return _queue.Count;
+      }
+    }
+
+
+    /// <summary>
+    /// Returns the head of the queue, without removing it (use Dequeue() for that).
+    /// Throws an exception when the queue is empty.
+    /// O(1)
+    /// </summary>
+    public TItem First {
+      get {
+        if (_queue.Count <= 0) {
+          throw new InvalidOperationException("Cannot call .First on an empty queue");
+        }
+
+        return _queue.First.Data;
+      }
+    }
+
+    /// <summary>
+    /// Removes every node from the queue.
+    /// O(n)
+    /// </summary>
+    public void Clear() {
+      _queue.Clear();
+      _itemToNodesCache.Clear();
+      _nullNodesCache.Clear();
+    }
+
+    /// <summary>
+    /// Returns whether the given item is in the queue.
+    /// O(1)
+    /// </summary>
+    public bool Contains(TItem item) {
+      if (item == null) {
+        return _nullNodesCache.Count > 0;
+      }
+      return _itemToNodesCache.ContainsKey(item);
+    }
+
+    /// <summary>
+    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
+    /// If queue is empty, throws an exception
+    /// O(log n)
+    /// </summary>
+    public TItem Dequeue() {
+      if (_queue.Count <= 0) {
+        throw new InvalidOperationException("Cannot call Dequeue() on an empty queue");
+      }
+
+      SimpleNode node = _queue.Dequeue();
+      RemoveFromNodeCache(node);
+      return node.Data;
+    }
+
+    /// <summary>
+    /// Enqueue the item with the given priority, without calling AddToNodeCache(node)
+    /// </summary>
+    /// <param name="item"></param>
+    /// <returns></returns>
+    private SimpleNode EnqueueNoCache(TItem item) {
+      SimpleNode node = new SimpleNode(item, _comparer);
+      if (_queue.Count == _queue.MaxSize) {
+        _queue.Resize(_queue.MaxSize * 2 + 1);
+      }
+      _queue.Enqueue(node);
+      return node;
+    }
+
+    /// <summary>
+    /// Enqueue a node to the priority queue.  Lower values are placed in front. Ties are broken by first-in-first-out.
+    /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'.
+    /// Duplicates and null-values are allowed.
+    /// O(log n)
+    /// </summary>
+    public void Enqueue(TItem item) {
+      IList<SimpleNode> nodes;
+      if (item == null) {
+        nodes = _nullNodesCache;
+      } else if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
+        nodes = new List<SimpleNode>();
+        _itemToNodesCache[item] = nodes;
+      }
+      SimpleNode node = EnqueueNoCache(item);
+      nodes.Add(node);
+    }
+
+    /// <summary>
+    /// Enqueue a node to the priority queue if it doesn't already exist.  Lower values are placed in front. Ties are broken by first-in-first-out.
+    /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'.  Null values are allowed.
+    /// Returns true if the node was successfully enqueued; false if it already exists.
+    /// O(log n)
+    /// </summary>
+    public bool EnqueueWithoutDuplicates(TItem item) {
+      IList<SimpleNode> nodes;
+      if (item == null) {
+        if (_nullNodesCache.Count > 0) {
+          return false;
+        }
+        nodes = _nullNodesCache;
+      } else if (_itemToNodesCache.ContainsKey(item)) {
+        return false;
+      } else {
+        nodes = new List<SimpleNode>();
+        _itemToNodesCache[item] = nodes;
+      }
+      SimpleNode node = EnqueueNoCache(item);
+      nodes.Add(node);
+      return true;
+    }
+
+    /// <summary>
+    /// Removes an item from the queue.  The item does not need to be the head of the queue.  
+    /// If the item is not in the queue, an exception is thrown.  If unsure, check Contains() first.
+    /// If multiple copies of the item are enqueued, only the first one is removed. 
+    /// O(log n)
+    /// </summary>
+    public void Remove(TItem item) {
+      SimpleNode removeMe;
+      IList<SimpleNode> nodes;
+      if (item == null) {
+        if (_nullNodesCache.Count == 0) {
+          throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
+        }
+        removeMe = _nullNodesCache[0];
+        nodes = _nullNodesCache;
+      } else {
+        if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
+          throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
+        }
+        removeMe = nodes[0];
+        if (nodes.Count == 1) {
+          _itemToNodesCache.Remove(item);
+        }
+      }
+      _queue.Remove(removeMe);
+      nodes.Remove(removeMe);
+    }
+
+    /// <summary>
+    /// Call this method to change the priority of an item.
+    /// Calling this method on a item not in the queue will throw an exception.
+    /// If the item is enqueued multiple times, only the first one will be updated.
+    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
+    /// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
+    /// O(log n)
+    /// </summary>
+    public void UpdatePriority(TItem item) {
+      SimpleNode updateMe = GetExistingNode(item);
+      if (updateMe == null) {
+        throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item);
+      }
+      _queue.UpdatePriority(updateMe);
+    }
+
+    #region Try* methods for multithreading
+    /// Get the head of the queue, without removing it (use TryDequeue() for that).
+    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and First
+    /// Returns true if successful, false otherwise
+    /// O(1)
+    public bool TryFirst(out TItem first) {
+      if (_queue.Count <= 0) {
+        first = default(TItem);
+        return false;
+      }
+
+      first = _queue.First.Data;
+      return true;
+    }
+
+    /// <summary>
+    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and sets it to first.
+    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Dequeue()
+    /// Returns true if successful; false if queue was empty
+    /// O(log n)
+    /// </summary>
+    public bool TryDequeue(out TItem first) {
+      if (_queue.Count <= 0) {
+        first = default(TItem);
+        return false;
+      }
+
+      SimpleNode node = _queue.Dequeue();
+      first = node.Data;
+      RemoveFromNodeCache(node);
+      return true;
+    }
+
+    /// <summary>
+    /// Attempts to remove an item from the queue.  The item does not need to be the head of the queue.  
+    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Remove()
+    /// Returns true if the item was successfully removed, false if it wasn't in the queue.
+    /// If multiple copies of the item are enqueued, only the first one is removed. 
+    /// O(log n)
+    /// </summary>
+    public bool TryRemove(TItem item) {
+      SimpleNode removeMe;
+      IList<SimpleNode> nodes;
+      if (item == null) {
+        if (_nullNodesCache.Count == 0) {
+          return false;
+        }
+        removeMe = _nullNodesCache[0];
+        nodes = _nullNodesCache;
+      } else {
+        if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
+          return false;
+        }
+        removeMe = nodes[0];
+        if (nodes.Count == 1) {
+          _itemToNodesCache.Remove(item);
+        }
+      }
+      _queue.Remove(removeMe);
+      nodes.Remove(removeMe);
+      return true;
+    }
+
+    /// <summary>
+    /// Call this method to change the priority of an item.
+    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and UpdatePriority()
+    /// If the item is enqueued multiple times, only the first one will be updated.
+    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
+    /// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
+    /// Returns true if the item priority was updated, false otherwise.
+    /// O(log n)
+    /// </summary>
+    public bool TryUpdatePriority(TItem item) {
+      SimpleNode updateMe = GetExistingNode(item);
+      if (updateMe == null) {
+        return false;
+      }
+      _queue.UpdatePriority(updateMe);
+      return true;
+    }
+    #endregion
+
+    public IEnumerator<TItem> GetEnumerator() {
+      return _queue.Select(x => x.Data).GetEnumerator();
+    }
+
+    IEnumerator IEnumerable.GetEnumerator() {
+      return GetEnumerator();
+    }
+
+    public bool IsValidQueue() {
+      // Check all items in cache are in the queue
+      foreach (IList<SimpleNode> nodes in _itemToNodesCache.Values) {
+        foreach (SimpleNode node in nodes) {
+          if (!_queue.Contains(node)) {
+            return false;
+          }
+        }
+      }
+
+      // Check all items in queue are in cache
+      foreach (SimpleNode node in _queue) {
+        if (GetExistingNode(node.Data) == null) {
+          return false;
+        }
+      }
+
+      // Check queue structure itself
+      return _queue.IsValidQueue();
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/ActiveObject.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/ActiveObject.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/ActiveObject.cs	(revision 18027)
@@ -0,0 +1,16 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+namespace SimSharp {
+  public abstract class ActiveObject<T> where T : Simulation {
+    protected T Environment { get; private set; }
+
+    protected ActiveObject(T environment) {
+      Environment = environment;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Environment.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Environment.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Environment.cs	(revision 18027)
@@ -0,0 +1,1138 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SimSharp {
+  /// <summary>
+  /// Simulation hold the event queues, schedule and process events.
+  /// </summary>
+  /// <remarks>
+  /// This class is not thread-safe against manipulation of the event queue. If you supply a termination
+  /// event that is set outside the simulation thread, please use the <see cref="ThreadSafeSimulation"/> environment.
+  /// 
+  /// For most purposes <see cref="Simulation"/> is however the better and faster choice.
+  /// </remarks>
+  public class Simulation {
+    private const int InitialMaxEvents = 1024;
+
+    /// <summary>
+    /// Describes the number of seconds that a logical step of 1 in the *D-API takes.
+    /// </summary>
+    protected double DefaultTimeStepSeconds { get; private set; }
+
+    /// <summary>
+    /// Calculates the logical date of the simulation by the amount of default steps
+    /// that have passed.
+    /// </summary>
+    public double NowD {
+      get { return (Now - StartDate).TotalSeconds / DefaultTimeStepSeconds; }
+    }
+
+    private DateTime now;
+    /// <summary>
+    /// The current simulation time as a calendar date.
+    /// </summary>
+    public virtual DateTime Now { get => now; protected set => now = value; }
+
+    /// <summary>
+    /// The calendar date when the simulation started. This defaults to 1970-1-1 if
+    /// no other date has been specified in the overloaded constructor.
+    /// </summary>
+    public DateTime StartDate { get; protected set; }
+
+    /// <summary>
+    /// The random number generator that is to be used in all events in
+    /// order to produce reproducible results.
+    /// </summary>
+    protected IRandom Random { get; set; }
+
+    protected EventQueue ScheduleQ;
+    public Process ActiveProcess { get; set; }
+
+    public TextWriter Logger { get; set; }
+    public int ProcessedEvents { get; protected set; }
+
+    public Simulation() : this(new DateTime(1970, 1, 1)) { }
+    public Simulation(TimeSpan? defaultStep) : this(new DateTime(1970, 1, 1), defaultStep) { }
+    public Simulation(int randomSeed, TimeSpan? defaultStep = null) : this(new DateTime(1970, 1, 1), randomSeed, defaultStep) { }
+    public Simulation(DateTime initialDateTime, TimeSpan? defaultStep = null) : this(new PcgRandom(), initialDateTime, defaultStep) { }
+    public Simulation(DateTime initialDateTime, int randomSeed, TimeSpan? defaultStep = null) : this(new PcgRandom(randomSeed), initialDateTime, defaultStep) { }
+    public Simulation(IRandom random, DateTime initialDateTime, TimeSpan? defaultStep = null) {
+      DefaultTimeStepSeconds = (defaultStep ?? TimeSpan.FromSeconds(1)).Duration().TotalSeconds;
+      StartDate = initialDateTime;
+      Now = initialDateTime;
+      Random = random;
+      ScheduleQ = new EventQueue(InitialMaxEvents);
+      Logger = Console.Out;
+    }
+
+    public double ToDouble(TimeSpan span) {
+      return span.TotalSeconds / DefaultTimeStepSeconds;
+    }
+
+    public TimeSpan ToTimeSpan(double span) {
+      return TimeSpan.FromSeconds(DefaultTimeStepSeconds * span);
+    }
+
+    /// <summary>
+    /// Creates a new process from an event generator. The process is automatically
+    /// scheduled to be started at the current simulation time.
+    /// </summary>
+    /// <param name="generator">The generator function that represents the process.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    /// <returns>The scheduled process that was created.</returns>
+    public Process Process(IEnumerable<Event> generator, int priority = 0) {
+      return new Process(this, generator, priority);
+    }
+
+    /// <summary>
+    /// Creates and returns a new timeout.
+    /// </summary>
+    /// <param name="delay">The time after which the timeout is fired.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    /// <returns>The scheduled timeout event that was created.</returns>
+    public Timeout TimeoutD(double delay, int priority = 0) {
+      return Timeout(TimeSpan.FromSeconds(DefaultTimeStepSeconds * delay), priority);
+    }
+
+    /// <summary>
+    /// Creates and returns a new timeout.
+    /// </summary>
+    /// <param name="delay">The time after which the timeout is fired.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    /// <returns>The scheduled timeout event that was created.</returns>
+    public Timeout Timeout(TimeSpan delay, int priority = 0) {
+      return new Timeout(this, delay, priority: priority);
+    }
+
+    public virtual void Reset(int randomSeed) {
+      ProcessedEvents = 0;
+      Now = StartDate;
+      Random = new PcgRandom(randomSeed);
+      ScheduleQ = new EventQueue(InitialMaxEvents);
+      useSpareNormal = false;
+    }
+
+    public virtual void ScheduleD(double delay, Event @event) {
+      Schedule(TimeSpan.FromSeconds(DefaultTimeStepSeconds * delay), @event);
+    }
+
+    /// <summary>
+    /// Schedules an event to occur at the same simulation time as the call was made.
+    /// </summary>
+    /// <param name="event">The event that should be scheduled.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    public virtual void Schedule(Event @event, int priority = 0) {
+      DoSchedule(Now, @event, priority);
+    }
+
+    /// <summary>
+    /// Schedules an event to occur after a certain (positive) delay.
+    /// </summary>
+    /// <exception cref="ArgumentException">
+    /// Thrown when <paramref name="delay"/> is negative.
+    /// </exception>
+    /// <param name="delay">The (positive) delay after which the event should be fired.</param>
+    /// <param name="event">The event that should be scheduled.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    public virtual void Schedule(TimeSpan delay, Event @event, int priority = 0) {
+      if (delay < TimeSpan.Zero)
+        throw new ArgumentException("Negative delays are not allowed in Schedule(TimeSpan, Event).");
+      var eventTime = Now + delay;
+      DoSchedule(eventTime, @event, priority);
+    }
+
+    protected virtual EventQueueNode DoSchedule(DateTime date, Event @event, int priority = 0) {
+      if (ScheduleQ.MaxSize == ScheduleQ.Count) {
+        // the capacity has to be adjusted, there are more events in the queue than anticipated
+        var oldSchedule = ScheduleQ;
+        ScheduleQ = new EventQueue(ScheduleQ.MaxSize * 2);
+        foreach (var e in oldSchedule) ScheduleQ.Enqueue(e.PrimaryPriority, e.Event, e.SecondaryPriority);
+      }
+      return ScheduleQ.Enqueue(date, @event, priority);
+    }
+
+    public virtual object RunD(double? until = null) {
+      if (!until.HasValue) return Run();
+      return Run(Now + TimeSpan.FromSeconds(DefaultTimeStepSeconds * until.Value));
+    }
+
+    public virtual object Run(TimeSpan span) {
+      return Run(Now + span);
+    }
+
+    public virtual object Run(DateTime until) {
+      if (until <= Now) throw new InvalidOperationException("Simulation end date must lie in the future.");
+      var stopEvent = new Event(this);
+      var node = DoSchedule(until, stopEvent);
+      // stop event is always the first to execute at the given time
+      node.InsertionIndex = -1;
+      ScheduleQ.OnNodeUpdated(node);
+      return Run(stopEvent);
+    }
+
+    protected CancellationTokenSource _stop = null;
+    /// <summary>
+    /// Run until a certain event is processed.
+    /// </summary>
+    /// <remarks>
+    /// This simulation environment is not thread-safe, thus triggering this event outside the environment
+    /// leads to potential race conditions. Please use the <see cref="ThreadSafeSimulation"/> environment in case you
+    /// require this functionality. Note that the performance of <see cref="ThreadSafeSimulation"/> is lower due to locking.
+    /// 
+    /// For real-time based termination, you can also call <see cref="StopAsync"/> which sets a flag indicating the simulation
+    /// to stop before processing the next event.
+    /// </remarks>
+    /// <param name="stopEvent">The event that stops the simulation.</param>
+    /// <returns></returns>
+    public virtual object Run(Event stopEvent = null) {
+      _stop = new CancellationTokenSource();
+      if (stopEvent != null) {
+        if (stopEvent.IsProcessed) {
+          return stopEvent.Value;
+        }
+        stopEvent.AddCallback(StopSimulation);
+      }
+      OnRunStarted();
+      try {
+        var stop = ScheduleQ.Count == 0 || _stop.IsCancellationRequested;
+        while (!stop) {
+          Step();
+          stop = ScheduleQ.Count == 0 || _stop.IsCancellationRequested;
+        }
+      } catch (StopSimulationException e) { OnRunFinished(); return e.Value; }
+      OnRunFinished();
+      if (stopEvent == null) return null;
+      if (!_stop.IsCancellationRequested && !stopEvent.IsTriggered) throw new InvalidOperationException("No scheduled events left but \"until\" event was not triggered.");
+      return stopEvent.Value;
+    }
+
+    public virtual void StopAsync() {
+      _stop?.Cancel();
+    }
+
+    public event EventHandler RunStarted;
+    protected void OnRunStarted() {
+      RunStarted?.Invoke(this, EventArgs.Empty);
+    }
+
+    public event EventHandler RunFinished;
+    protected void OnRunFinished() {
+      RunFinished?.Invoke(this, EventArgs.Empty);
+    }
+
+    /// <summary>
+    /// Performs a single step of the simulation, i.e. process a single event
+    /// </summary>
+    /// <remarks>
+    /// This method is not thread-safe
+    /// </remarks>
+    public virtual void Step() {
+      Event evt;
+      var next = ScheduleQ.Dequeue();
+      Now = next.PrimaryPriority;
+      evt = next.Event;
+      evt.Process();
+      ProcessedEvents++;
+    }
+
+    /// <summary>
+    /// Peeks at the time of the next event in terms of the defined step
+    /// </summary>
+    /// <remarks>
+    /// This method is not thread-safe
+    /// </remarks>
+    public virtual double PeekD() {
+      if (ScheduleQ.Count == 0) return double.MaxValue;
+      return (Peek() - StartDate).TotalSeconds / DefaultTimeStepSeconds;
+    }
+
+    /// <summary>
+    /// Peeks at the time of the next event
+    /// </summary>
+    /// <remarks>
+    /// This method is not thread-safe
+    /// </remarks>
+    public virtual DateTime Peek() {
+      return ScheduleQ.Count > 0 ? ScheduleQ.First.PrimaryPriority : DateTime.MaxValue;
+    }
+
+    protected virtual void StopSimulation(Event @event) {
+      throw new StopSimulationException(@event.Value);
+    }
+
+    public virtual void Log(string message, params object[] args) {
+      if (Logger != null)
+        Logger.WriteLine(message, args);
+    }
+
+    #region Random number distributions
+    public double RandUniform(IRandom random, double a, double b) {
+      return a + (b - a) * random.NextDouble();
+    }
+    public double RandUniform(double a, double b) {
+      return RandUniform(Random, a, b);
+    }
+
+    public TimeSpan RandUniform(IRandom random, TimeSpan a, TimeSpan b) {
+      return TimeSpan.FromSeconds(RandUniform(random, a.TotalSeconds, b.TotalSeconds));
+    }
+    public TimeSpan RandUniform(TimeSpan a, TimeSpan b) {
+      return RandUniform(Random, a, b);
+    }
+    public double RandTriangular(IRandom random, double low, double high) {
+      var u = random.NextDouble();
+      if (u > 0.5)
+        return high + (low - high) * Math.Sqrt(((1.0 - u) / 2));
+      return low + (high - low) * Math.Sqrt(u / 2);
+    }
+    public double RandTriangular(double low, double high) {
+      return RandTriangular(Random, low, high);
+    }
+
+    public TimeSpan RandTriangular(IRandom random, TimeSpan low, TimeSpan high) {
+      return TimeSpan.FromSeconds(RandTriangular(random, low.TotalSeconds, high.TotalSeconds));
+    }
+    public TimeSpan RandTriangular(TimeSpan low, TimeSpan high) {
+      return RandTriangular(Random, low, high);
+    }
+
+    public double RandTriangular(IRandom random, double low, double high, double mode) {
+      var u = random.NextDouble();
+      var c = (mode - low) / (high - low);
+      if (u > c)
+        return high + (low - high) * Math.Sqrt(((1.0 - u) * (1.0 - c)));
+      return low + (high - low) * Math.Sqrt(u * c);
+    }
+    public double RandTriangular(double low, double high, double mode) {
+      return RandTriangular(Random, low, high, mode);
+    }
+
+    public TimeSpan RandTriangular(IRandom random, TimeSpan low, TimeSpan high, TimeSpan mode) {
+      return TimeSpan.FromSeconds(RandTriangular(random, low.TotalSeconds, high.TotalSeconds, mode.TotalSeconds));
+    }
+    public TimeSpan RandTriangular(TimeSpan low, TimeSpan high, TimeSpan mode) {
+      return RandTriangular(Random, low, high, mode);
+    }
+
+    /// <summary>
+    /// Returns a number that is exponentially distributed given a certain mean.
+    /// </summary>
+    /// <remarks>
+    /// Unlike in other APIs here the mean should be given and not the lambda parameter.
+    /// </remarks>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="mean">The mean(!) of the distribution is 1 / lambda.</param>
+    /// <returns>A number that is exponentially distributed</returns>
+    public double RandExponential(IRandom random, double mean) {
+      return -Math.Log(1 - random.NextDouble()) * mean;
+    }
+    /// <summary>
+    /// Returns a number that is exponentially distributed given a certain mean.
+    /// </summary>
+    /// <remarks>
+    /// Unlike in other APIs here the mean should be given and not the lambda parameter.
+    /// </remarks>
+    /// <param name="mean">The mean(!) of the distribution is 1 / lambda.</param>
+    /// <returns>A number that is exponentially distributed</returns>
+    public double RandExponential(double mean) {
+      return RandExponential(Random, mean);
+    }
+
+    /// <summary>
+    /// Returns a timespan that is exponentially distributed given a certain mean.
+    /// </summary>
+    /// <remarks>
+    /// Unlike in other APIs here the mean should be given and not the lambda parameter.
+    /// </remarks>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="mean">The mean(!) of the distribution is 1 / lambda.</param>
+    /// <returns>A number that is exponentially distributed</returns>
+    public TimeSpan RandExponential(IRandom random, TimeSpan mean) {
+      return TimeSpan.FromSeconds(RandExponential(random, mean.TotalSeconds));
+    }
+    /// <summary>
+    /// Returns a timespan that is exponentially distributed given a certain mean.
+    /// </summary>
+    /// <remarks>
+    /// Unlike in other APIs here the mean should be given and not the lambda parameter.
+    /// </remarks>
+    /// <param name="mean">The mean(!) of the distribution is 1 / lambda.</param>
+    /// <returns>A number that is exponentially distributed</returns>
+    public TimeSpan RandExponential(TimeSpan mean) {
+      return RandExponential(Random, mean);
+    }
+
+    private bool useSpareNormal = false;
+    private double spareNormal = double.NaN;
+
+    /// <summary>
+    /// Uses the Marsaglia polar method to generate a random variable
+    /// from two uniform random distributed values.
+    /// </summary>
+    /// <remarks>
+    /// Unlike <see cref="RandNormal(double, double)"/> this method does not
+    /// make use of a spare random variable. It discards the spare and thus
+    /// requires twice the number of calls to the underlying IRandom instance.
+    /// </remarks>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="mu">The mean of the normal distribution.</param>
+    /// <param name="sigma">The standard deviation of the normal distribution.</param>
+    /// <returns>A number that is normal distributed.</returns>
+    public virtual double RandNormal(IRandom random, double mu, double sigma) {
+      return MarsagliaPolar(random, mu, sigma, out _); // do not reuse the spare normal in this case, because it could be from a different RNG
+    }
+    /// <summary>
+    /// Uses the Marsaglia polar method to generate a random variable
+    /// from two uniform random distributed values.
+    /// </summary>
+    /// <remarks>
+    /// A spare random variable is generated from the second uniformly
+    /// distributed value. Thus, the two calls to the uniform random number
+    /// generator will be made only every second call.
+    /// </remarks>
+    /// <param name="mu">The mean of the normal distribution.</param>
+    /// <param name="sigma">The standard deviation of the normal distribution.</param>
+    /// <returns>A number that is normal distributed.</returns>
+    public virtual double RandNormal(double mu, double sigma) {
+      if (useSpareNormal) {
+        useSpareNormal = false;
+        return spareNormal * sigma + mu;
+      } else {
+        useSpareNormal = true;
+        return MarsagliaPolar(Random, mu, sigma, out spareNormal);
+      }
+    }
+    private double MarsagliaPolar(IRandom random, double mu, double sigma, out double spare) {
+      double u, v, s;
+      do {
+        u = random.NextDouble() * 2 - 1;
+        v = random.NextDouble() * 2 - 1;
+        s = u * u + v * v;
+      } while (s > 1 || s == 0);
+      var mul = Math.Sqrt(-2.0 * Math.Log(s) / s);
+      spare = v * mul;
+      return mu + sigma * u * mul;
+    }
+
+    /// <summary>
+    /// Uses the Marsaglia polar method to generate a random variable
+    /// from two uniform random distributed values.
+    /// </summary>
+    /// <remarks>
+    /// A spare random variable is generated from the second uniformly
+    /// distributed value. Thus, the two calls to the uniform random number
+    /// generator will be made only every second call.
+    /// </remarks>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="mu">The mean of the normal distribution.</param>
+    /// <param name="sigma">The standard deviation of the normal distribution.</param>
+    /// <returns>A number that is normal distributed.</returns>
+    public TimeSpan RandNormal(IRandom random, TimeSpan mu, TimeSpan sigma) {
+      return TimeSpan.FromSeconds(RandNormal(random, mu.TotalSeconds, sigma.TotalSeconds));
+    }
+    /// <summary>
+    /// Uses the Marsaglia polar method to generate a random variable
+    /// from two uniform random distributed values.
+    /// </summary>
+    /// <remarks>
+    /// A spare random variable is generated from the second uniformly
+    /// distributed value. Thus, the two calls to the uniform random number
+    /// generator will be made only every second call.
+    /// </remarks>
+    /// <param name="mu">The mean of the normal distribution.</param>
+    /// <param name="sigma">The standard deviation of the normal distribution.</param>
+    /// <returns>A number that is normal distributed.</returns>
+    public TimeSpan RandNormal(TimeSpan mu, TimeSpan sigma) {
+      return RandNormal(Random, mu, sigma);
+    }
+
+    public double RandNormalPositive(IRandom random, double mu, double sigma) {
+      double val;
+      do {
+        val = RandNormal(random, mu, sigma);
+      } while (val <= 0);
+      return val;
+    }
+    public double RandNormalPositive(double mu, double sigma) {
+      return RandNormalPositive(Random, mu, sigma);
+    }
+
+    public TimeSpan RandNormalPositive(IRandom random, TimeSpan mu, TimeSpan sigma) {
+      return TimeSpan.FromSeconds(RandNormalPositive(random, mu.TotalSeconds, sigma.TotalSeconds));
+    }
+    public TimeSpan RandNormalPositive(TimeSpan mu, TimeSpan sigma) {
+      return RandNormalPositive(Random, mu, sigma);
+    }
+
+    public double RandNormalNegative(IRandom random, double mu, double sigma) {
+      double val;
+      do {
+        val = RandNormal(random, mu, sigma);
+      } while (val >= 0);
+      return val;
+    }
+    public double RandNormalNegative(double mu, double sigma) {
+      return RandNormalNegative(Random, mu, sigma);
+    }
+
+    public TimeSpan RandNormalNegative(IRandom random, TimeSpan mu, TimeSpan sigma) {
+      return TimeSpan.FromSeconds(RandNormalNegative(random, mu.TotalSeconds, sigma.TotalSeconds));
+    }
+    public TimeSpan RandNormalNegative(TimeSpan mu, TimeSpan sigma) {
+      return RandNormalNegative(Random, mu, sigma);
+    }
+
+    /// <summary>
+    /// Returns values from a log-normal distribution with the mean
+    /// exp(mu + sigma^2 / 2)
+    /// and the standard deviation
+    /// sqrt([exp(sigma^2)-1] * exp(2 * mu + sigma^2))
+    /// </summary>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="mu">The mu parameter of the log-normal distribution (not the mean).</param>
+    /// <param name="sigma">The sigma parameter of the log-normal distribution (not the standard deviation).</param>
+    /// <returns>A log-normal distributed random value.</returns>
+    public double RandLogNormal(IRandom random, double mu, double sigma) {
+      return Math.Exp(RandNormal(random, mu, sigma));
+    }
+    /// <summary>
+    /// Returns values from a log-normal distribution with the mean
+    /// exp(mu + sigma^2 / 2)
+    /// and the standard deviation
+    /// sqrt([exp(sigma^2)-1] * exp(2 * mu + sigma^2))
+    /// </summary>
+    /// <param name="mu">The mu parameter of the log-normal distribution (not the mean).</param>
+    /// <param name="sigma">The sigma parameter of the log-normal distribution (not the standard deviation).</param>
+    /// <returns>A log-normal distributed random value.</returns>
+    public double RandLogNormal(double mu, double sigma) {
+      return RandLogNormal(Random, mu, sigma);
+    }
+
+    /// <summary>
+    /// Returns values from a log-normal distribution with
+    /// the mean <paramref name="mean"/> and standard deviation <paramref name="stdev"/>.
+    /// </summary>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="mean">The distribution mean.</param>
+    /// <param name="stdev">The distribution standard deviation.</param>
+    /// <returns>A log-normal distributed random value.</returns>
+    public double RandLogNormal2(IRandom random, double mean, double stdev) {
+      if (stdev == 0) return mean;
+      var sigma = Math.Sqrt(Math.Log(stdev * stdev / (mean * mean) + 1));
+      var mu = Math.Log(mean) - 0.5 * sigma * sigma;
+      return Math.Exp(RandNormal(random, mu, sigma));
+    }
+    /// <summary>
+    /// Returns values from a log-normal distribution with
+    /// the mean <paramref name="mean"/> and standard deviation <paramref name="stdev"/>.
+    /// </summary>
+    /// <param name="mean">The distribution mean.</param>
+    /// <param name="stdev">The distribution standard deviation.</param>
+    /// <returns>A log-normal distributed random value.</returns>
+    public double RandLogNormal2(double mean, double stdev) {
+      return RandLogNormal2(Random, mean, stdev);
+    }
+
+    /// <summary>
+    /// Returns a timespan value from a log-normal distribution with the mean
+    /// exp(mu + sigma^2 / 2)
+    /// and the standard deviation
+    /// sqrt([exp(sigma^2)-1] * exp(2 * mu + sigma^2))
+    /// </summary>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="mu">The mu parameter of the log-normal distribution (not the mean).</param>
+    /// <param name="sigma">The sigma parameter of the log-normal distribution (not the standard deviation).</param>
+    /// <returns>A log-normal distributed random timespan.</returns>
+    public TimeSpan RandLogNormal(IRandom random, TimeSpan mu, TimeSpan sigma) {
+      return TimeSpan.FromSeconds(RandLogNormal(random, mu.TotalSeconds, sigma.TotalSeconds));
+    }
+    /// <summary>
+    /// Returns a timespan value from a log-normal distribution with the mean
+    /// exp(mu + sigma^2 / 2)
+    /// and the standard deviation
+    /// sqrt([exp(sigma^2)-1] * exp(2 * mu + sigma^2))
+    /// </summary>
+    /// <param name="mu">The mu parameter of the log-normal distribution (not the mean).</param>
+    /// <param name="sigma">The sigma parameter of the log-normal distribution (not the standard deviation).</param>
+    /// <returns>A log-normal distributed random timespan.</returns>
+    public TimeSpan RandLogNormal(TimeSpan mu, TimeSpan sigma) {
+      return RandLogNormal(Random, mu, sigma);
+    }
+
+    /// <summary>
+    /// Returns a timespan value from a log-normal distribution with
+    /// the mean <paramref name="mean"/> and standard deviation <paramref name="stdev"/>.
+    /// </summary>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="mean">The distribution mean.</param>
+    /// <param name="stdev">The distribution standard deviation.</param>
+    /// <returns>A log-normal distributed random timespan.</returns>
+    public TimeSpan RandLogNormal2(IRandom random, TimeSpan mean, TimeSpan stdev) {
+      return TimeSpan.FromSeconds(RandLogNormal2(random, mean.TotalSeconds, stdev.TotalSeconds));
+    }
+    /// <summary>
+    /// Returns a timespan value from a log-normal distribution with
+    /// the mean <paramref name="mean"/> and standard deviation <paramref name="stdev"/>.
+    /// </summary>
+    /// <param name="mean">The distribution mean.</param>
+    /// <param name="stdev">The distribution standard deviation.</param>
+    /// <returns>A log-normal distributed random timespan.</returns>
+    public TimeSpan RandLogNormal2(TimeSpan mean, TimeSpan stdev) {
+      return RandLogNormal2(Random, mean, stdev);
+    }
+
+    public double RandCauchy(IRandom random, double x0, double gamma) {
+      return x0 + gamma * Math.Tan(Math.PI * (random.NextDouble() - 0.5));
+    }
+    public double RandCauchy(double x0, double gamma) {
+      return RandCauchy(Random, x0, gamma);
+    }
+
+    public TimeSpan RandCauchy(IRandom random, TimeSpan x0, TimeSpan gamma) {
+      return TimeSpan.FromSeconds(RandCauchy(random, x0.TotalSeconds, gamma.TotalSeconds));
+    }
+    public TimeSpan RandCauchy(TimeSpan x0, TimeSpan gamma) {
+      return RandCauchy(Random, x0, gamma);
+    }
+
+    public double RandWeibull(IRandom random, double alpha, double beta) {
+      return alpha * Math.Pow(-Math.Log(1 - random.NextDouble()), 1 / beta);
+    }
+    public double RandWeibull(double alpha, double beta) {
+      return RandWeibull(Random, alpha, beta);
+    }
+
+    public TimeSpan RandWeibull(IRandom random, TimeSpan alpha, TimeSpan beta) {
+      return TimeSpan.FromSeconds(RandWeibull(random, alpha.TotalSeconds, beta.TotalSeconds));
+    }
+    public TimeSpan RandWeibull(TimeSpan alpha, TimeSpan beta) {
+      return RandWeibull(Random, alpha, beta);
+    }
+
+
+    /// <summary>
+    /// Generates a random sample from a given source
+    /// </summary>
+    /// <typeparam name="T">The type of the element in parameter source</typeparam>
+    /// <exception cref="ArgumentException">
+    /// Thrown when <paramref name="source"/> and <paramref name="weights"/> have different size.
+    /// or when <paramref name="weights"/> contains an invalid or negative value.
+    /// or when <paramref name="weights"/> sum equals zero or an invalid value.
+    /// </exception>
+    /// <param name="random">The random number generator to use.</param>
+    /// <param name="source">a random sample is generated from its elements.</param>
+    /// <param name="weights">The weight associated with each entry in source.</param>
+    /// <returns>The generated random samples</returns>
+    public T RandChoice<T>(IRandom random, IList<T> source, IList<double> weights) {
+      if (source.Count != weights.Count) {
+        throw new ArgumentException("source and weights must have same size");
+      }
+
+      double totalW = 0;
+      foreach (var w in weights) {
+        if (w < 0) {
+          throw new ArgumentException("weight values must be non-negative", nameof(weights));
+        }
+        totalW += w;
+      }
+
+      if (double.IsNaN(totalW) || double.IsInfinity(totalW))
+        throw new ArgumentException("Not a valid weight", nameof(weights));
+      if (totalW == 0)
+        throw new ArgumentException("total weight must be greater than 0", nameof(weights));
+
+      var rnd = random.NextDouble();
+      double aggWeight = 0;
+      int idx = 0;
+      foreach (var w in weights) {
+        if (w > 0) {
+          aggWeight += (w / totalW);
+          if (rnd <= aggWeight) {
+            break;
+          }
+        }
+        idx++;
+      }
+      return source[idx];
+    }
+    /// <summary>
+    /// Generates a random sample from a given source
+    /// </summary>
+    /// <typeparam name="T">The type of the element in parameter source</typeparam>
+    /// <exception cref="ArgumentException">
+    /// Thrown when <paramref name="source"/> and <paramref name="weights"/> have different size.
+    /// or when <paramref name="weights"/> contains an invalid or negative value.
+    /// or when <paramref name="weights"/> sum equals zero
+    /// </exception>
+    /// <param name="source">a random sample is generated from its elements.</param>
+    /// <param name="weights">The weight associated with each entry in source.</param>
+    /// <returns>The generated random samples</returns>
+    public T RandChoice<T>(IList<T> source, IList<double> weights) {
+      return RandChoice(Random, source, weights);
+    }
+
+    #endregion
+
+    #region Random timeouts
+    public Timeout TimeoutUniformD(IRandom random, double a, double b) {
+      return new Timeout(this, ToTimeSpan(RandUniform(random, a, b)));
+    }
+    public Timeout TimeoutUniformD(double a, double b) {
+      return TimeoutUniformD(Random, a, b);
+    }
+
+    public Timeout TimeoutUniform(IRandom random, TimeSpan a, TimeSpan b) {
+      return new Timeout(this, RandUniform(random, a, b));
+    }
+    public Timeout TimeoutUniform(TimeSpan a, TimeSpan b) {
+      return TimeoutUniform(Random, a, b);
+    }
+
+    public Timeout TimeoutTriangularD(IRandom random, double low, double high) {
+      return new Timeout(this, ToTimeSpan(RandTriangular(random, low, high)));
+    }
+    public Timeout TimeoutTriangularD(double low, double high) {
+      return TimeoutTriangularD(Random, low, high);
+    }
+
+    public Timeout TimeoutTriangular(IRandom random, TimeSpan low, TimeSpan high) {
+      return new Timeout(this, RandTriangular(random, low, high));
+    }
+    public Timeout TimeoutTriangular(TimeSpan low, TimeSpan high) {
+      return TimeoutTriangular(Random, low, high);
+    }
+
+    public Timeout TimeoutTriangularD(IRandom random, double low, double high, double mode) {
+      return new Timeout(this, ToTimeSpan(RandTriangular(random, low, high, mode)));
+    }
+    public Timeout TimeoutTriangularD(double low, double high, double mode) {
+      return TimeoutTriangularD(Random, low, high, mode);
+    }
+
+    public Timeout TimeoutTriangular(IRandom random, TimeSpan low, TimeSpan high, TimeSpan mode) {
+      return new Timeout(this, RandTriangular(random, low, high, mode));
+    }
+    public Timeout TimeoutTriangular(TimeSpan low, TimeSpan high, TimeSpan mode) {
+      return TimeoutTriangular(Random, low, high, mode);
+    }
+
+    public Timeout TimeoutExponentialD(IRandom random, double mean) {
+      return new Timeout(this, ToTimeSpan(RandExponential(random, mean)));
+    }
+    public Timeout TimeoutExponentialD(double mean) {
+      return TimeoutExponentialD(Random, mean);
+    }
+
+    public Timeout TimeoutExponential(IRandom random, TimeSpan mean) {
+      return new Timeout(this, RandExponential(random, mean));
+    }
+    public Timeout TimeoutExponential(TimeSpan mean) {
+      return TimeoutExponential(Random, mean);
+    }
+
+    public Timeout TimeoutNormalPositiveD(IRandom random, double mu, double sigma) {
+      return new Timeout(this, ToTimeSpan(RandNormalPositive(random, mu, sigma)));
+    }
+    public Timeout TimeoutNormalPositiveD(double mu, double sigma) {
+      return TimeoutNormalPositiveD(Random, mu, sigma);
+    }
+
+    public Timeout TimeoutNormalPositive(IRandom random, TimeSpan mu, TimeSpan sigma) {
+      return new Timeout(this, RandNormalPositive(random, mu, sigma));
+    }
+    public Timeout TimeoutNormalPositive(TimeSpan mu, TimeSpan sigma) {
+      return TimeoutNormalPositive(Random, mu, sigma);
+    }
+
+    public Timeout TimeoutLogNormalD(IRandom random, double mu, double sigma) {
+      return new Timeout(this, ToTimeSpan(RandLogNormal(random, mu, sigma)));
+    }
+    public Timeout TimeoutLogNormalD(double mu, double sigma) {
+      return TimeoutLogNormalD(Random, mu, sigma);
+    }
+
+    public Timeout TimeoutLogNormal2D(IRandom random, double mean, double stdev) {
+      return new Timeout(this, ToTimeSpan(RandLogNormal2(random, mean, stdev)));
+    }
+    public Timeout TimeoutLogNormal2D(double mean, double stdev) {
+      return TimeoutLogNormal2D(Random, mean, stdev);
+    }
+
+    public Timeout TimeoutLogNormal(IRandom random, TimeSpan mu, TimeSpan sigma) {
+      return new Timeout(this, RandLogNormal(random, mu, sigma));
+    }
+    public Timeout TimeoutLogNormal(TimeSpan mu, TimeSpan sigma) {
+      return TimeoutLogNormal(Random, mu, sigma);
+    }
+
+    public Timeout TimeoutLogNormal2(IRandom random, TimeSpan mean, TimeSpan stdev) {
+      return new Timeout(this, RandLogNormal2(random, mean, stdev));
+    }
+    public Timeout TimeoutLogNormal2(TimeSpan mean, TimeSpan stdev) {
+      return TimeoutLogNormal2(Random, mean, stdev);
+    }
+    #endregion
+  }
+
+  /// <summary>
+  /// Provides a simulation environment that is thread-safe against manipulations of the event queue.
+  /// Its performance is somewhat lower than the non-thread-safe environment (cf. <see cref="Simulation"/>)
+  /// due to the locking involved.
+  /// </summary>
+  /// <remarks>
+  /// Please carefully consider if you must really schedule the stop event in a separate thread. You can also
+  /// call <see cref="Simulation.StopAsync"/> to request termination after the current event has been processed.
+  /// 
+  /// The simulation will still run in only one thread and execute all events sequentially.
+  /// </remarks>
+  public class ThreadSafeSimulation : Simulation {
+    protected object _locker;
+
+    public ThreadSafeSimulation() : this(new DateTime(1970, 1, 1)) { }
+    public ThreadSafeSimulation(TimeSpan? defaultStep) : this(new DateTime(1970, 1, 1), defaultStep) { }
+    public ThreadSafeSimulation(DateTime initialDateTime, TimeSpan? defaultStep = null) : this(new PcgRandom(), initialDateTime, defaultStep) { }
+    public ThreadSafeSimulation(int randomSeed, TimeSpan? defaultStep = null) : this(new DateTime(1970, 1, 1), randomSeed, defaultStep) { }
+    public ThreadSafeSimulation(DateTime initialDateTime, int randomSeed, TimeSpan? defaultStep = null) : this(new PcgRandom(randomSeed), initialDateTime, defaultStep) { }
+    public ThreadSafeSimulation(IRandom random, DateTime initialDateTime, TimeSpan? defaultStep = null) : base(random, initialDateTime, defaultStep) {
+      _locker = new object();
+    }
+
+
+    /// <summary>
+    /// Schedules an event to occur at the same simulation time as the call was made.
+    /// </summary>
+    /// <remarks>
+    /// This method is thread-safe against manipulations of the event queue
+    /// </remarks>
+    /// <param name="event">The event that should be scheduled.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    public override void Schedule(Event @event, int priority = 0) {
+      lock (_locker) {
+        DoSchedule(Now, @event, priority);
+      }
+    }
+
+    /// <summary>
+    /// Schedules an event to occur after a certain (positive) delay.
+    /// </summary>
+    /// <remarks>
+    /// This method is thread-safe against manipulations of the event queue
+    /// </remarks>
+    /// <exception cref="ArgumentException">
+    /// Thrown when <paramref name="delay"/> is negative.
+    /// </exception>
+    /// <param name="delay">The (positive) delay after which the event should be fired.</param>
+    /// <param name="event">The event that should be scheduled.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    public override void Schedule(TimeSpan delay, Event @event, int priority = 0) {
+      if (delay < TimeSpan.Zero)
+        throw new ArgumentException("Negative delays are not allowed in Schedule(TimeSpan, Event).");
+      lock (_locker) {
+        var eventTime = Now + delay;
+        DoSchedule(eventTime, @event, priority);
+      }
+    }
+
+    /// <summary>
+    /// Run until a certain event is processed.
+    /// </summary>
+    /// <remarks>
+    /// This method is thread-safe against manipulations of the event queue
+    /// </remarks>
+    /// <param name="stopEvent">The event that stops the simulation.</param>
+    /// <returns></returns>
+    public override object Run(Event stopEvent = null) {
+      _stop = new CancellationTokenSource();
+      if (stopEvent != null) {
+        if (stopEvent.IsProcessed) {
+          return stopEvent.Value;
+        }
+        stopEvent.AddCallback(StopSimulation);
+      }
+      OnRunStarted();
+      try {
+        var stop = false;
+        lock (_locker) {
+          stop = ScheduleQ.Count == 0 || _stop.IsCancellationRequested;
+        }
+        while (!stop) {
+          Step();
+          lock (_locker) {
+            stop = ScheduleQ.Count == 0 || _stop.IsCancellationRequested;
+          }
+        }
+      } catch (StopSimulationException e) { OnRunFinished(); return e.Value; }
+      OnRunFinished();
+      if (stopEvent == null) return null;
+      if (!_stop.IsCancellationRequested && !stopEvent.IsTriggered) throw new InvalidOperationException("No scheduled events left but \"until\" event was not triggered.");
+      return stopEvent.Value;
+    }
+
+    public Task<object> RunAsync(TimeSpan duration) {
+      return Task.Run(() => Run(duration));
+    }
+
+    public Task<object> RunAsync(DateTime until) {
+      return Task.Run(() => Run(until));
+    }
+
+    /// <summary>
+    /// Run until a certain event is processed, but does not block.
+    /// </summary>
+    /// <param name="stopEvent">The event that stops the simulation.</param>
+    /// <returns></returns>
+    public Task<object> RunAsync(Event stopEvent = null) {
+      return Task.Run(() => Run(stopEvent));
+    }
+
+    /// <summary>
+    /// Performs a single step of the simulation, i.e. process a single event
+    /// </summary>
+    /// <remarks>
+    /// This method is thread-safe against manipulations of the event queue
+    /// </remarks>
+    public override void Step() {
+      Event evt;
+      lock (_locker) {
+        var next = ScheduleQ.Dequeue();
+        Now = next.PrimaryPriority;
+        evt = next.Event;
+      }
+      evt.Process();
+      ProcessedEvents++;
+    }
+
+    /// <summary>
+    /// Peeks at the time of the next event in terms of the defined step
+    /// </summary>
+    /// <remarks>
+    /// This method is thread-safe against manipulations of the event queue
+    /// </remarks>
+    public override double PeekD() {
+      lock (_locker) {
+        if (ScheduleQ.Count == 0) return double.MaxValue;
+        return (Peek() - StartDate).TotalSeconds / DefaultTimeStepSeconds;
+      }
+    }
+
+    /// <summary>
+    /// Peeks at the time of the next event
+    /// </summary>
+    /// <remarks>
+    /// This method is thread-safe against manipulations of the event queue
+    /// </remarks>
+    public override DateTime Peek() {
+      lock (_locker) {
+        return ScheduleQ.Count > 0 ? ScheduleQ.First.PrimaryPriority : DateTime.MaxValue;
+      }
+    }
+  }
+
+  /// <summary>
+  /// Provides a simulation environment where delays in simulation time may result in a similar
+  /// delay in wall-clock time. The environment is not an actual realtime simulation environment
+  /// in that there is no guarantee that 3 seconds in model time are also exactly 3 seconds in
+  /// observed wall-clock time. This simulation environment is a bit slower, as the overhead of
+  /// the simulation kernel (event creation, queuing, processing, etc.) is not accounted for.
+  /// 
+  /// However, it features a switch between virtual and realtime, thus allowing it to be used
+  /// in contexts where realtime is only necessary sometimes (e.g. during interaction with
+  /// long-running co-processes). Such use cases may arise in simulation control problems.
+  /// </summary>
+  public class PseudoRealtimeSimulation : ThreadSafeSimulation {
+    public const double DefaultRealtimeScale = 1;
+
+    /// <summary>
+    /// The scale at which the simulation runs in comparison to realtime. A value smaller
+    /// than 1 results in longer-than-realtime delays, while a value larger than 1 results
+    /// in shorter-than-realtime delays. A value of exactly 1 is realtime.
+    /// </summary>
+    public double? RealtimeScale { get; protected set; } = DefaultRealtimeScale;
+    /// <summary>
+    /// Whether a non-null <see cref="RealtimeScale"/> has been set.
+    /// </summary>
+    public bool IsRunningInRealtime => RealtimeScale.HasValue;
+
+    private object _timeLocker = new object();
+    /// <summary>
+    /// The current model time. Note that, while in realtime, this may continuously change.
+    /// </summary>
+    public override DateTime Now {
+      get {
+        lock (_timeLocker) {
+          if (!IsRunningInRealtime) return base.Now;
+          return base.Now + TimeSpan.FromMilliseconds(_rtDelayTime.Elapsed.TotalMilliseconds * RealtimeScale.Value);
+        }
+      }
+      protected set => base.Now = value;
+    }
+
+    protected CancellationTokenSource _rtDelayCtrl = null;
+    protected Stopwatch _rtDelayTime = new Stopwatch();
+
+
+    public PseudoRealtimeSimulation() : this(new DateTime(1970, 1, 1)) { }
+    public PseudoRealtimeSimulation(TimeSpan? defaultStep) : this(new DateTime(1970, 1, 1), defaultStep) { }
+    public PseudoRealtimeSimulation(DateTime initialDateTime, TimeSpan? defaultStep = null) : this(new PcgRandom(), initialDateTime, defaultStep) { }
+    public PseudoRealtimeSimulation(int randomSeed, TimeSpan? defaultStep = null) : this(new DateTime(1970, 1, 1), randomSeed, defaultStep) { }
+    public PseudoRealtimeSimulation(DateTime initialDateTime, int randomSeed, TimeSpan? defaultStep = null) : this(new PcgRandom(randomSeed), initialDateTime, defaultStep) { }
+    public PseudoRealtimeSimulation(IRandom random, DateTime initialDateTime, TimeSpan? defaultStep = null) : base(random, initialDateTime, defaultStep) { }
+    
+    protected override EventQueueNode DoSchedule(DateTime date, Event @event, int priority = 0) {
+      if (ScheduleQ.Count > 0 && date < ScheduleQ.First.PrimaryPriority) _rtDelayCtrl?.Cancel();
+      return base.DoSchedule(date, @event, priority);
+    }
+
+    public override void Step() {
+      var delay = TimeSpan.Zero;
+      double? rtScale = null;
+      lock (_locker) {
+        if (IsRunningInRealtime) {
+          rtScale = RealtimeScale;
+          var next = ScheduleQ.First.PrimaryPriority;
+          delay = next - base.Now;
+          if (rtScale.Value != 1.0) delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds / rtScale.Value);
+          _rtDelayCtrl = CancellationTokenSource.CreateLinkedTokenSource(_stop.Token);
+        }
+      }
+
+      if (delay > TimeSpan.Zero) {
+        _rtDelayTime.Start();
+        Task.Delay(delay, _rtDelayCtrl.Token).ContinueWith(_ => { }).Wait();
+        _rtDelayTime.Stop();
+        var observed = _rtDelayTime.Elapsed;
+
+        lock (_locker) {
+          if (rtScale.Value != 1.0) observed = TimeSpan.FromMilliseconds(observed.TotalMilliseconds * rtScale.Value);
+          if (_rtDelayCtrl.IsCancellationRequested && observed < delay) {
+            lock (_timeLocker) {
+              Now = base.Now + observed;
+              _rtDelayTime.Reset();
+            }
+            return; // next event is not processed, step is not actually completed
+          }
+        }
+      }
+
+      Event evt;
+      lock (_locker) {
+        var next = ScheduleQ.Dequeue();
+        lock (_timeLocker) {
+          _rtDelayTime.Reset();
+          Now = next.PrimaryPriority;
+        }
+        evt = next.Event;
+      }
+      evt.Process();
+      ProcessedEvents++;
+    }
+
+    /// <summary>
+    /// Switches the simulation to virtual time mode, i.e., running as fast as possible.
+    /// In this mode, events are processed without delay just like in a <see cref="ThreadSafeSimulation"/>.
+    /// </summary>
+    /// <remarks>
+    /// An ongoing real-time delay is being canceled when this method is called. Usually, this
+    /// is only the case when this method is called from a thread other than the main simulation thread.
+    /// 
+    /// If the simulation is already in virtual time mode, this method has no effect.
+    /// </remarks>
+    public virtual void SetVirtualtime() {
+      lock (_locker) {
+        if (!IsRunningInRealtime) return;
+        RealtimeScale = null;
+        _rtDelayCtrl?.Cancel();
+      }
+    }
+
+    /// <summary>
+    /// Switches the simulation to real time mode. The real time factor of
+    /// this default mode is configurable.
+    /// </summary>
+    /// <remarks>
+    /// If this method is called while running in real-time mode, but given a different
+    /// <paramref name="realtimeScale"/>, the current delay is canceled and the remaining
+    /// time is delayed using the new time factor.
+    /// 
+    /// The default factor is 1, i.e., real time - a timeout of 5 seconds would cause
+    /// a wall-clock delay of 5 seconds. With a factor of 2, the delay as measured by
+    /// a wall clock would be 2.5 seconds, whereas a factor of 0.5, a wall-clock delay of
+    /// 10 seconds would be observed.
+    /// </remarks>
+    /// <param name="realtimeScale">A value strictly greater than 0 used to scale real time events.</param>
+    public virtual void SetRealtime(double realtimeScale = DefaultRealtimeScale) {
+      lock (_locker) {
+        if (realtimeScale <= 0.0) throw new ArgumentException("The simulation speed scaling factor must be strictly positive.", nameof(realtimeScale));
+        if (IsRunningInRealtime && realtimeScale != RealtimeScale) _rtDelayCtrl?.Cancel();
+        RealtimeScale = realtimeScale;
+      }
+    }
+
+    /// <summary>
+    /// This is only a convenience for mixed real- and virtual time simulations.
+    /// It creates a new pseudo realtime process which will set the simulation
+    /// to realtime every time it continues (e.g., if it has been set to virtual time).
+    /// The process is automatically scheduled to be started at the current simulation time.
+    /// </summary>
+    /// <param name="generator">The generator function that represents the process.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    /// <param name="realtimeScale">A value strictly greater than 0 used to scale real time events (1 = realtime).</param>
+    /// <returns>The scheduled process that was created.</returns>
+    public Process PseudoRealtimeProcess(IEnumerable<Event> generator, int priority = 0, double realtimeScale = DefaultRealtimeScale) {
+      return new PseudoRealtimeProcess(this, generator, priority, realtimeScale);
+    }
+  }
+
+  /// <summary>
+  /// Environments hold the event queues, schedule and process events.
+  /// </summary>
+  [Obsolete("Use class Simulation or ThreadSafeSimulation instead. Due to name clashes with System.Environment the class SimSharp.Environment is being outphased.")]
+  public class Environment : ThreadSafeSimulation {
+    public Environment()
+      : base() {
+      Random = new SystemRandom();
+    }
+    public Environment(TimeSpan? defaultStep)
+      : base(defaultStep) {
+      Random = new SystemRandom();
+    }
+    public Environment(int randomSeed, TimeSpan? defaultStep = null)
+      : base(randomSeed, defaultStep) {
+      Random = new SystemRandom(randomSeed);
+    }
+    public Environment(DateTime initialDateTime, TimeSpan? defaultStep = null)
+      : base(initialDateTime, defaultStep) {
+      Random = new SystemRandom();
+    }
+    public Environment(DateTime initialDateTime, int randomSeed, TimeSpan? defaultStep = null)
+      : base(initialDateTime, randomSeed, defaultStep) {
+      Random = new SystemRandom(randomSeed);
+    }
+
+    protected static readonly double NormalMagicConst = 4 * Math.Exp(-0.5) / Math.Sqrt(2.0);
+    public override double RandNormal(double mu, double sigma) {
+      return RandNormal(Random, mu, sigma);
+    }
+    public override double RandNormal(IRandom random, double mu, double sigma) {
+      double z, zz, u1, u2;
+      do {
+        u1 = random.NextDouble();
+        u2 = 1 - random.NextDouble();
+        z = NormalMagicConst * (u1 - 0.5) / u2;
+        zz = z * z / 4.0;
+      } while (zz > -Math.Log(u2));
+      return mu + z * sigma;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/AllOf.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/AllOf.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/AllOf.cs	(revision 18027)
@@ -0,0 +1,19 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace SimSharp {
+  public class AllOf : Condition {
+    public AllOf(Simulation environment, params Event[] events) : base(environment, events) { }
+    public AllOf(Simulation environment, IEnumerable<Event> events) : base(environment, events) { }
+
+    protected override bool Evaluate() {
+      return FiredEvents.Count == Events.Count;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/AnyOf.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/AnyOf.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/AnyOf.cs	(revision 18027)
@@ -0,0 +1,19 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace SimSharp {
+  public class AnyOf : Condition {
+    public AnyOf(Simulation environment, params Event[] events) : base(environment, events) { }
+    public AnyOf(Simulation environment, IEnumerable<Event> events) : base(environment, events) { }
+
+    protected override bool Evaluate() {
+      return FiredEvents.Count > 0 || Events.Count == 0;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Condition.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Condition.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Condition.cs	(revision 18027)
@@ -0,0 +1,86 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace SimSharp {
+  /// <summary>
+  /// Conditions are events that execute when any or all of its sub-events are executed.
+  /// </summary>
+  public abstract class Condition : Event {
+
+    public new OrderedDictionary Value {
+      get { return (OrderedDictionary)base.Value; }
+      set { base.Value = value; }
+    }
+
+    protected List<Event> Events { get; private set; }
+
+    protected List<Event> FiredEvents { get; private set; }
+
+    protected Condition(Simulation environment, params Event[] events)
+      : this(environment, (IEnumerable<Event>)events) { }
+    protected Condition(Simulation environment, IEnumerable<Event> events)
+      : base(environment) {
+      CallbackList.Add(CollectValues);
+      Events = new List<Event>(events);
+      FiredEvents = new List<Event>();
+
+      foreach (var @event in Events) {
+        if (Environment != @event.Environment)
+          throw new ArgumentException("It is not allowed to mix events from different environments");
+        if (@event.IsProcessed) Check(@event);
+        else @event.AddCallback(Check);
+      }
+
+      if (IsAlive && Evaluate())
+        Succeed();
+    }
+
+    protected void Check(Event @event) {
+      if (IsTriggered || IsProcessed) {
+        if (!@event.IsOk) throw new InvalidOperationException(
+@"Errors that happen after the condition has been triggered will not be
+handled by the condition and cause the simulation to crash.");
+        return;
+      }
+      FiredEvents.Add(@event);
+
+      if (!@event.IsOk)
+        Fail(@event.Value);
+      else if (Evaluate()) {
+        Succeed();
+      }
+    }
+
+    protected virtual IEnumerable<KeyValuePair<object, object>> GetValues() {
+      var values = new List<KeyValuePair<object, object>>();
+      foreach (var e in Events) {
+        var condition = e as Condition;
+        if (condition != null) {
+          values.AddRange(condition.GetValues());
+        } else if (e.IsProcessed) {
+          values.Add(new KeyValuePair<object, object>(e, e.Value));
+        }
+      }
+      return values;
+    }
+
+    protected virtual void CollectValues(Event @event) {
+      if (@event.IsOk) {
+        var value = new OrderedDictionary();
+        foreach (var v in GetValues())
+          value.Add(v.Key, v.Value);
+        Value = value;
+      }
+    }
+
+    protected abstract bool Evaluate();
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Event.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Event.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Event.cs	(revision 18027)
@@ -0,0 +1,189 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace SimSharp {
+  /// <summary>
+  /// The base class for all events in SimSharp.
+  /// An event can be in one of three states at any time:
+  ///  - Alive: The event object exists, but is neither scheduled to
+  ///           be executed, nor is it already executed.
+  ///  - Triggered: The event has been put in the event queue and is
+  ///               going to be executed.
+  ///  - Processed: The event has been executed.
+  /// 
+  /// Usually, the event is alive until its Trigger, Succeed, or Fail
+  /// method have been called. Then it becomes triggered. When the
+  /// Environment progresses to the event and executes its callbacks
+  /// the event becomes processed.
+  /// </summary>
+  public class Event {
+    protected internal Simulation Environment { get; private set; }
+    protected List<Action<Event>> CallbackList { get; set; }
+
+    /// <summary>
+    /// The value property can be used to return arbitrary data from a
+    /// process or an event. It also represents the interrupt cause to
+    /// a process.
+    /// </summary>
+    public object Value { get; protected set; }
+
+    /// <summary>
+    /// The IsOk flag indicates if the event succeeded or failed. An event
+    /// that failed indicates to a waiting process that the action could
+    /// not be performed and that the faulting situation must be handled.
+    /// Typically, interrupting a process sets the IsOk flag to false.
+    /// </summary>
+    public bool IsOk { get; protected set; }
+    /// <summary>
+    /// An event is alive when it is not triggered and not processed. That
+    /// is, when it exists in memory without being scheduled. Typically,
+    /// a Process is alive until its last event has been processed and the
+    /// process event itself is to be processed.
+    /// </summary>
+    public bool IsAlive { get { return !IsTriggered && !IsProcessed; } }
+    /// <summary>
+    /// An event becomes processed when its callbacks have been executed.
+    /// Events may only be processed once and an exception will be thrown
+    /// if they are to be processed multiple times.
+    /// </summary>
+    public bool IsProcessed { get; protected set; }
+    /// <summary>
+    /// An event becomes triggered when it is placed into the event queue.
+    /// That is, when its callbacks are going to be executed.
+    /// An even that is triggered may later not be failed or retriggered.
+    /// </summary>
+    public bool IsTriggered { get; protected set; }
+
+    public Event(Simulation environment) {
+      Environment = environment;
+      CallbackList = new List<Action<Event>>();
+    }
+
+    /// <summary>
+    /// This method schedules the event right now. It takes the IsOk state
+    /// and uses the <see cref="Value"/> of the given <paramref name="@event"/>.
+    /// Thus if the given event fails, this event will also be triggered as
+    /// failing.
+    /// </summary>
+    /// <exception cref="InvalidOperationException">
+    /// Thrown when the event has already been triggered.
+    /// </exception>
+    /// <remarks>
+    /// The signature of this method allows it to be used as a callback.
+    /// </remarks>
+    /// <param name="event">The event that triggers this event.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    public virtual void Trigger(Event @event, int priority = 0) {
+      if (IsTriggered)
+        throw new InvalidOperationException("Event has already been triggered.");
+      IsOk = @event.IsOk;
+      Value = @event.Value;
+      IsTriggered = true;
+      Environment.Schedule(this, priority);
+    }
+
+    /// <summary>
+    /// This method schedules the event right now. It sets IsOk state to true
+    /// and optionally uses also the value. If urgent is given, the event may
+    /// be scheduled as urgent. Urgent events are placed in a separate event
+    /// queue. The callbacks of urgent events are executed before normal events.
+    /// </summary>
+    /// <exception cref="InvalidOperationException">
+    /// Thrown when the event has already been triggered.
+    /// </exception>
+    /// <param name="value">The value that the event should use.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    public virtual void Succeed(object value = null, int priority = 0) {
+      if (IsTriggered)
+        throw new InvalidOperationException("Event has already been triggered.");
+      IsOk = true;
+      Value = value;
+      IsTriggered = true;
+      Environment.Schedule(this, priority);
+    }
+
+    /// <summary>
+    /// This method schedules the event right now. It sets IsOk state to false
+    /// and optionally uses also the value. If urgent is given, the event may
+    /// be scheduled as urgent. Urgent events are placed in a separate event
+    /// queue. The callbacks of urgent events are executed before normal events.
+    /// </summary>
+    /// <exception cref="InvalidOperationException">
+    /// Thrown when the event has already been triggered.
+    /// </exception>
+    /// <param name="value">The value that the event should use.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    public virtual void Fail(object value = null, int priority = 0) {
+      if (IsTriggered)
+        throw new InvalidOperationException("Event has already been triggered.");
+      IsOk = false;
+      Value = value;
+      IsTriggered = true;
+      Environment.Schedule(this, priority);
+    }
+
+    /// <summary>
+    /// This method adds a callback to the list of callbacks. Callbacks will be
+    /// executed in the order they have been added.
+    /// </summary>
+    /// <param name="callback">The callback to execute when the event is being
+    /// processed.</param>
+    public virtual void AddCallback(Action<Event> callback) {
+      if (IsProcessed) throw new InvalidOperationException("Event is already processed.");
+      CallbackList.Add(callback);
+    }
+
+    /// <summary>
+    /// This method adds a range of callbacks to the list of callbacks. Callbacks
+    /// will be executed in the order they have been added.
+    /// </summary>
+    /// <param name="callbacks">The callbacks to execute when the event is being
+    /// processed.</param>
+    public virtual void AddCallbacks(IEnumerable<Action<Event>> callbacks) {
+      if (IsProcessed) throw new InvalidOperationException("Event is already processed.");
+      CallbackList.AddRange(callbacks);
+    }
+
+    /// <summary>
+    /// This method removes a callback to the list of callbacks.
+    /// </summary>
+    /// <remarks>
+    /// It is not checked if the callback has actually been added before and
+    /// no exception will be thrown if it had not been present.
+    /// </remarks>
+    /// <param name="callback">The callback to remove.</param>
+    public virtual void RemoveCallback(Action<Event> callback) {
+      if (IsProcessed) throw new InvalidOperationException("Event is already processed.");
+      CallbackList.Remove(callback);
+    }
+
+    /// <summary>
+    /// This method processes the event, that is, it calls all the callbacks.
+    /// When it finishes it will be marked IsProcessed and cannot be processed
+    /// again.
+    /// </summary>
+    /// <exception cref="InvalidOperationException">When the event has already
+    /// been processed.</exception>
+    public virtual void Process() {
+      if (IsProcessed) throw new InvalidOperationException("Event has already been processed.");
+      IsProcessed = true;
+      for (var i = 0; i < CallbackList.Count; i++)
+        CallbackList[i](this);
+      CallbackList = null;
+    }
+
+    public static Condition operator &(Event event1, Event event2) {
+      return new AllOf(event1.Environment, event1, event2);
+    }
+    public static Condition operator |(Event event1, Event event2) {
+      return new AnyOf(event1.Environment, event1, event2);
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Process.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Process.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Process.cs	(revision 18027)
@@ -0,0 +1,183 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace SimSharp {
+  /// <summary>
+  /// A Process handles the iteration of events. Processes may define steps that
+  /// a certain entity in the simulation has to perform. Each time the process
+  /// should wait it yields an event and will be resumed when that event is processed.
+  /// </summary>
+  /// <remarks>
+  /// Since an iterator method does not have access to its process, the method can
+  /// retrieve the associated Process through the ActiveProcess property of the
+  /// environment. Each Process sets and resets that property during Resume.
+  /// </remarks>
+  public class Process : Event {
+    private readonly IEnumerator<Event> generator;
+    private Event target;
+    /// <summary>
+    /// Target is the event that is expected to be executed next in the process.
+    /// </summary>
+    public Event Target {
+      get { return target; }
+      protected set { target = value; }
+    }
+
+    /// <summary>
+    /// Sets up a new process.
+    /// The process places an initialize event into the event queue which starts
+    /// the process by retrieving events from the generator.
+    /// </summary>
+    /// <param name="environment">The environment in which the process lives.</param>
+    /// <param name="generator">The generator function of the process.</param>
+    /// <param name="priority">The priority if multiple processes are started at the same time.</param>
+    public Process(Simulation environment, IEnumerable<Event> generator, int priority = 0)
+      : base(environment) {
+      this.generator = generator.GetEnumerator();
+      IsOk = true;
+      target = new Initialize(environment, this, priority);
+    }
+
+    /// <summary>
+    /// This interrupts a process and causes the IsOk flag to be set to false.
+    /// If a process is interrupted the iterator method needs to call HandleFault()
+    /// before continuing to yield further events.
+    /// </summary>
+    /// <exception cref="InvalidOperationException">This is thrown in three conditions:
+    ///  - If the process has already been triggered.
+    ///  - If the process attempts to interrupt itself.
+    ///  - If the process continues to yield events despite being faulted.</exception>
+    /// <param name="cause">The cause of the interrupt.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    public virtual void Interrupt(object cause = null, int priority = 0) {
+      if (IsTriggered) throw new InvalidOperationException("The process has terminated and cannot be interrupted.");
+      if (Environment.ActiveProcess == this) throw new InvalidOperationException("A process is not allowed to interrupt itself.");
+
+      var interruptEvent = new Event(Environment);
+      interruptEvent.AddCallback(Resume);
+      interruptEvent.Fail(cause, priority);
+
+      if (Target != null)
+        Target.RemoveCallback(Resume);
+    }
+
+    protected virtual void Resume(Event @event) {
+      Environment.ActiveProcess = this;
+      while (true) {
+        if (@event.IsOk) {
+          if (generator.MoveNext()) {
+            if (IsTriggered) {
+              // the generator called e.g. Environment.ActiveProcess.Fail
+              Environment.ActiveProcess = null;
+              return;
+            }
+            if (!ProceedToEvent()) {
+              @event = target;
+              continue;
+            } else break;
+          } else if (!IsTriggered) {
+            Succeed(@event.Value);
+            break;
+          } else break;
+        } else {
+          /* Fault handling differs from SimPy as in .NET it is not possible to inject an
+         * exception into an enumerator and it is impossible to put a yield return inside
+         * a try-catch block. In SimSharp the Process will set IsOk and will then move to
+         * the next yield in the generator. However, if after this move IsOk is still false
+         * we know that the error was not handled. It is assumed the error is handled if
+         * HandleFault() is called on the environment's ActiveProcess which will reset the
+         * flag. */
+          IsOk = false;
+          Value = @event.Value;
+
+          if (generator.MoveNext()) {
+            if (IsTriggered) {
+              // the generator called e.g. Environment.ActiveProcess.Fail
+              Environment.ActiveProcess = null;
+              return;
+            }
+            // if we move next, but IsOk is still false
+            if (!IsOk) throw new InvalidOperationException("The process did not react to being faulted.");
+            // otherwise HandleFault was called and the fault was handled
+            if (ProceedToEvent()) break;
+          } else if (!IsTriggered) {
+            if (!IsOk) Fail(@event.Value);
+            else Succeed(@event.Value);
+            break;
+          } else break;
+        }
+      }
+      Environment.ActiveProcess = null;
+    }
+    
+    protected virtual bool ProceedToEvent() {
+      target = generator.Current;
+      Value = target.Value;
+      if (target.IsProcessed) return false;
+      target.AddCallback(Resume);
+      return true;
+    }
+
+    /// <summary>
+    /// This method must be called to reset the IsOk flag of the process back to true.
+    /// The IsOk flag may be set to false if the process waited on an event that failed.
+    /// </summary>
+    /// <remarks>
+    /// In SimPy a faulting process would throw an exception which is then catched and
+    /// chained. In SimSharp catching exceptions from a yield is not possible as a yield
+    /// return statement may not throw an exception.
+    /// If a processes faulted the Value property may indicate a cause for the fault.
+    /// </remarks>
+    /// <returns>True if a faulting situation needs to be handled, false if the process
+    /// is okay and the last yielded event succeeded.</returns>
+    public virtual bool HandleFault() {
+      if (IsOk) return false;
+      IsOk = true;
+      return true;
+    }
+
+    private class Initialize : Event {
+      public Initialize(Simulation environment, Process process, int priority)
+        : base(environment) {
+        CallbackList.Add(process.Resume);
+        IsOk = true;
+        IsTriggered = true;
+        environment.Schedule(this, priority);
+      }
+    }
+  }
+
+  public class PseudoRealtimeProcess : Process {
+    public double RealtimeScale { get; set; }
+
+    public new PseudoRealtimeSimulation Environment {
+      get { return (PseudoRealtimeSimulation)base.Environment; }
+    }
+
+    /// <summary>
+    /// Sets up a new process.
+    /// The process places an initialize event into the event queue which starts
+    /// the process by retrieving events from the generator.
+    /// </summary>
+    /// <param name="environment">The environment in which the process lives.</param>
+    /// <param name="generator">The generator function of the process.</param>
+    /// <param name="priority">The priority if multiple processes are started at the same time.</param>
+    /// <param name="realtimeScale">A value strictly greater than 0 used to scale real time events (1 = realtime).</param>
+    public PseudoRealtimeProcess(PseudoRealtimeSimulation environment, IEnumerable<Event> generator, int priority = 0, double realtimeScale = PseudoRealtimeSimulation.DefaultRealtimeScale)
+      : base(environment, generator, priority) {
+      RealtimeScale = realtimeScale;
+    }
+
+    protected override void Resume(Event @event) {
+      Environment.SetRealtime(RealtimeScale);
+      base.Resume(@event);
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Timeout.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Timeout.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Events/Timeout.cs	(revision 18027)
@@ -0,0 +1,35 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  /// <summary>
+  /// Timeouts are simple events that are executed after a certain timespan has passed.
+  /// </summary>
+  public sealed class Timeout : Event {
+    /// <summary>
+    /// A timeout is an event that is executed after a certain timespan has passed.
+    /// </summary>
+    /// <remarks>
+    /// Timeout events are scheduled when they are created. They are always triggered
+    /// when they are created.
+    /// </remarks>
+    /// <param name="environment">The environment in which it is scheduled.</param>
+    /// <param name="delay">The timespan for the timeout.</param>
+    /// <param name="value">The value of the timeout.</param>
+    /// <param name="isOk">Whether the timeout should succeed or fail.</param>
+    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
+    public Timeout(Simulation environment, TimeSpan delay, object value = null, bool isOk = true, int priority = 0)
+      : base(environment) {
+      IsOk = isOk;
+      Value = value;
+      IsTriggered = true;
+      environment.Schedule(delay, this, priority);
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Exceptions/EmptyScheduleException.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Exceptions/EmptyScheduleException.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Exceptions/EmptyScheduleException.cs	(revision 18027)
@@ -0,0 +1,21 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  /// <summary>
+  /// An exception that is thrown to stop the simulation.
+  /// </summary>
+  public class StopSimulationException : Exception {
+    public object Value { get; private set; }
+    
+    public StopSimulationException(object value) {
+      Value = value;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Preempted.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Preempted.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Preempted.cs	(revision 18027)
@@ -0,0 +1,20 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public sealed class Preempted {
+    public Process By { get; private set; }
+    public DateTime UsageSince { get; private set; }
+
+    public Preempted(Process by, DateTime usageSince) {
+      By = by;
+      UsageSince = usageSince;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Container.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Container.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Container.cs	(revision 18027)
@@ -0,0 +1,165 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace SimSharp {
+  /// <summary>
+  /// A container holds a variable amount of a single continuous entity, e.g. water, coal, grain, etc.
+  /// 
+  /// Put and Get are in FIFO order only when they can be satisfied.
+  /// Any put or get that can be satisfied takes precedence.
+  /// Put events that attempt to add more to the Container than there is capacity for and
+  /// Get events that remove more than there is are backlogged.
+  /// </summary>
+  public class Container {
+
+    public double Capacity { get; protected set; }
+
+    public double Level { get; protected set; }
+
+    protected Simulation Environment { get; private set; }
+
+    protected Queue<ContainerPut> PutQueue { get; private set; }
+    protected Queue<ContainerGet> GetQueue { get; private set; }
+    protected SimplePriorityQueue<Event, double> WhenAtLeastQueue { get; private set; }
+    protected SimplePriorityQueue<Event, double> WhenAtMostQueue { get; private set; }
+    protected List<Event> WhenChangeQueue { get; private set; }
+
+    public ITimeSeriesMonitor Fillrate { get; set; }
+    public ITimeSeriesMonitor PutQueueLength { get; set; }
+    public ISampleMonitor PutWaitingTime { get; set; }
+    public ITimeSeriesMonitor GetQueueLength { get; set; }
+    public ISampleMonitor GetWaitingTime { get; set; }
+
+    public Container(Simulation environment, double capacity = double.MaxValue, double initial = 0) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must be > 0", "capacity");
+      if (initial < 0) throw new ArgumentException("Initial must be >= 0", "initial");
+      if (initial > capacity) throw new ArgumentException("Initial must be <= capacity", "initial");
+      Environment = environment;
+      Capacity = capacity;
+      Level = initial;
+      PutQueue = new Queue<ContainerPut>();
+      GetQueue = new Queue<ContainerGet>();
+      WhenAtLeastQueue = new SimplePriorityQueue<Event, double>();
+      WhenAtMostQueue = new SimplePriorityQueue<Event, double>(new ReverseComparer<double>());
+      WhenChangeQueue = new List<Event>();
+    }
+
+    public virtual ContainerPut Put(double amount) {
+      if (amount < 0) throw new ArgumentException("Cannot put negative amount", "amount");
+      if (amount > Capacity) throw new ArgumentException("Cannot put more than capacity", "amount");
+      var put = new ContainerPut(Environment, TriggerGet, amount);
+      PutQueue.Enqueue(put);
+      TriggerPut();
+      return put;
+    }
+
+    public virtual ContainerGet Get(double amount) {
+      if (amount < 0) throw new ArgumentException("Cannot get negative amount", "amount");
+      if (amount > Capacity) throw new ArgumentException("Cannot get more than capacity", "amount");
+      var get = new ContainerGet(Environment, TriggerPut, amount);
+      GetQueue.Enqueue(get);
+      TriggerGet();
+      return get;
+    }
+
+    public virtual Event WhenAtLeast(double level) {
+      var whenAtLeast = new Event(Environment);
+      WhenAtLeastQueue.Enqueue(whenAtLeast, level);
+      TriggerWhenAtLeast();
+      return whenAtLeast;
+    }
+
+    public virtual Event WhenFull() {
+      return WhenAtLeast(Capacity);
+    }
+
+    public virtual Event WhenAtMost(double level) {
+      var whenAtMost = new Event(Environment);
+      WhenAtMostQueue.Enqueue(whenAtMost, level);
+      TriggerWhenAtMost();
+      return whenAtMost;
+    }
+
+    public virtual Event WhenEmpty() {
+      return WhenAtMost(0);
+    }
+
+    public virtual Event WhenChange() {
+      var whenChange = new Event(Environment);
+      WhenChangeQueue.Add(whenChange);
+      return whenChange;
+    }
+
+    protected virtual void DoPut(ContainerPut put) {
+      if (Capacity - Level >= put.Amount) {
+        PutWaitingTime?.Add(Environment.ToDouble(Environment.Now - put.Time));
+        Level += put.Amount;
+        put.Succeed();
+      }
+    }
+
+    protected virtual void DoGet(ContainerGet get) {
+      if (Level >= get.Amount) {
+        GetWaitingTime?.Add(Environment.ToDouble(Environment.Now - get.Time));
+        Level -= get.Amount;
+        get.Succeed();
+      }
+    }
+
+    protected virtual void TriggerPut(Event @event = null) {
+      while (PutQueue.Count > 0) {
+        var put = PutQueue.Peek();
+        DoPut(put);
+        if (put.IsTriggered) {
+          PutQueue.Dequeue();
+          TriggerWhenAtLeast();
+          TriggerWhenChange();
+        } else break;
+      }
+      Fillrate?.UpdateTo(Level / Capacity);
+      PutQueueLength?.UpdateTo(PutQueue.Count);
+    }
+
+    protected virtual void TriggerGet(Event @event = null) {
+      while (GetQueue.Count > 0) {
+        var get = GetQueue.Peek();
+        DoGet(get);
+        if (get.IsTriggered) {
+          GetQueue.Dequeue();
+          TriggerWhenAtMost();
+          TriggerWhenChange();
+        } else break;
+      }
+      Fillrate?.UpdateTo(Level / Capacity);
+      GetQueueLength?.UpdateTo(GetQueue.Count);
+    }
+
+    protected virtual void TriggerWhenAtLeast() {
+      while (WhenAtLeastQueue.Count > 0 && Level >= WhenAtLeastQueue.Peek) {
+        var whenAtLeast = WhenAtLeastQueue.Dequeue();
+        whenAtLeast.Succeed();
+      }
+    }
+
+    protected virtual void TriggerWhenAtMost() {
+      while (WhenAtMostQueue.Count > 0 && Level <= WhenAtMostQueue.Peek) {
+        var whenAtMost = WhenAtMostQueue.Dequeue();
+        whenAtMost.Succeed();
+      }
+    }
+
+    protected virtual void TriggerWhenChange() {
+      if (WhenChangeQueue.Count == 0) return;
+      foreach (var evt in WhenChangeQueue)
+        evt.Succeed();
+      WhenChangeQueue.Clear();
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/ContainerGet.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/ContainerGet.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/ContainerGet.cs	(revision 18027)
@@ -0,0 +1,25 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public class ContainerGet : Event {
+    public double Amount { get; protected set; }
+    public DateTime Time { get; private set; }
+    public Process Owner { get; set; }
+
+    public ContainerGet(Simulation environment, Action<Event> callback, double amount)
+      : base(environment) {
+      if (amount <= 0) throw new ArgumentException("Amount must be > 0.", "amount");
+      Amount = amount;
+      CallbackList.Add(callback);
+      Time = environment.Now;
+      Owner = environment.ActiveProcess;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/ContainerPut.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/ContainerPut.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/ContainerPut.cs	(revision 18027)
@@ -0,0 +1,25 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public class ContainerPut : Event {
+    public double Amount { get; protected set; }
+    public DateTime Time { get; private set; }
+    public Process Owner { get; set; }
+
+    public ContainerPut(Simulation environment, Action<Event> callback, double amount)
+      : base(environment) {
+      if (amount <= 0) throw new ArgumentException("Amount must be > 0.", "amount");
+      Amount = amount;
+      CallbackList.Add(callback);
+      Time = environment.Now;
+      Owner = environment.ActiveProcess;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/FilterStoreGet.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/FilterStoreGet.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/FilterStoreGet.cs	(revision 18027)
@@ -0,0 +1,19 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public class FilterStoreGet : StoreGet {
+    public Func<object, bool> Filter { get; private set; }
+
+    public FilterStoreGet(Simulation environment, Action<Event> callback, Func<object, bool> filter)
+      : base(environment, callback) {
+      Filter = filter;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/PreemptiveRequest.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/PreemptiveRequest.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/PreemptiveRequest.cs	(revision 18027)
@@ -0,0 +1,38 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public sealed class PreemptiveRequest : Request, IComparable<PreemptiveRequest>, IComparable {
+    public double Priority { get; private set; }
+    public bool Preempt { get; private set; }
+    public bool IsPreempted { get; internal set; }
+
+    public PreemptiveRequest(Simulation environment, Action<Event> callback, Action<Event> disposeCallback, double priority = 1, bool preempt = false)
+      : base(environment, callback, disposeCallback) {
+      Priority = priority;
+      Preempt = preempt;
+    }
+
+    public int CompareTo(PreemptiveRequest other) {
+      if (Priority > other.Priority) return 1;
+      else if (Priority < other.Priority) return -1;
+      if (Time > other.Time) return 1;
+      else if (Time < other.Time) return -1;
+      if (!Preempt && other.Preempt) return 1;
+      else if (Preempt && !other.Preempt) return -1;
+      return 0;
+    }
+
+    public int CompareTo(object obj) {
+      if (obj is PreemptiveRequest other) return CompareTo(other);
+      if (obj == null) return 1;
+      throw new ArgumentException("Can only compare to other objects of type PreemptiveRequest");
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/Release.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/Release.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/Release.cs	(revision 18027)
@@ -0,0 +1,20 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public sealed class Release : Event {
+    public Request Request { get; private set; }
+
+    public Release(Simulation environment, Request request, Action<Event> callback)
+      : base(environment) {
+      Request = request;
+      CallbackList.Add(callback);
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/Request.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/Request.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/Request.cs	(revision 18027)
@@ -0,0 +1,28 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public class Request : Event, IDisposable {
+    private readonly Action<Event> disposeCallback;
+    public DateTime Time { get; private set; }
+    public Process Owner { get; set; }
+
+    public Request(Simulation environment, Action<Event> callback, Action<Event> disposeCallback)
+      : base(environment) {
+      CallbackList.Add(callback);
+      this.disposeCallback = disposeCallback;
+      Time = environment.Now;
+      Owner = environment.ActiveProcess;
+    }
+
+    public virtual void Dispose() {
+      if (disposeCallback != null) disposeCallback(this);
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/ResourcePoolRequest.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/ResourcePoolRequest.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/ResourcePoolRequest.cs	(revision 18027)
@@ -0,0 +1,19 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public class ResourcePoolRequest : Request {
+    public Func<object, bool> Filter { get; private set; }
+
+    public ResourcePoolRequest(Simulation environment, Action<Event> callback, Action<Event> disposeCallback, Func<object, bool> filter)
+      : base(environment, callback, disposeCallback) {
+      Filter = filter;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/StoreGet.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/StoreGet.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/StoreGet.cs	(revision 18027)
@@ -0,0 +1,22 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public class StoreGet : Event {
+    public DateTime Time { get; private set; }
+    public Process Owner { get; set; }
+
+    public StoreGet(Simulation environment, Action<Event> callback)
+      : base(environment) {
+      CallbackList.Add(callback);
+      Time = environment.Now;
+      Owner = environment.ActiveProcess;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/StorePut.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/StorePut.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Events/StorePut.cs	(revision 18027)
@@ -0,0 +1,24 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public class StorePut : Event {
+    public DateTime Time { get; private set; }
+    public Process Owner { get; set; }
+
+    public StorePut(Simulation environment, Action<Event> callback, object value)
+      : base(environment) {
+      if (value == null) throw new ArgumentNullException("value", "Value to put in a Store cannot be null.");
+      CallbackList.Add(callback);
+      Value = value;
+      Time = environment.Now;
+      Owner = environment.ActiveProcess;
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/FilterStore.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/FilterStore.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/FilterStore.cs	(revision 18027)
@@ -0,0 +1,223 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SimSharp {
+  /// <summary>
+  /// A filter store is similar to a <see cref="Store"/>.
+  /// However, in Get it is possible to specify the property of the item wished to retrieve.
+  /// 
+  /// FilterStore holds a variable number of individual items.
+  /// Put are always performed in FIFO order. 
+  /// Get are performed in FIFO order only when they match at least one item in the store.
+  /// </summary>
+  public class FilterStore {
+    protected static readonly Func<object, bool> TrueFunc = _ => true;
+
+    public int Capacity { get; protected set; }
+
+    public int Count { get { return Items.Count; } }
+
+    protected Simulation Environment { get; private set; }
+
+    protected Queue<StorePut> PutQueue { get; private set; }
+    protected LinkedList<FilterStoreGet> GetQueue { get; private set; }
+    protected List<StoreItem> Items { get; private set; }
+    protected List<Event> WhenNewQueue { get; private set; }
+    protected List<Event> WhenAnyQueue { get; private set; }
+    protected List<Event> WhenFullQueue { get; private set; }
+    protected List<Event> WhenEmptyQueue { get; private set; }
+    protected List<Event> WhenChangeQueue { get; private set; }
+
+    public ITimeSeriesMonitor Utilization { get; set; }
+    public ITimeSeriesMonitor WIP { get; set; }
+    public ISampleMonitor LeadTime { get; set; }
+    public ITimeSeriesMonitor PutQueueLength { get; set; }
+    public ISampleMonitor PutWaitingTime { get; set; }
+    public ITimeSeriesMonitor GetQueueLength { get; set; }
+    public ISampleMonitor GetWaitingTime { get; set; }
+
+    public FilterStore(Simulation environment, int capacity = int.MaxValue) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must be > 0", "capacity");
+      Environment = environment;
+      Capacity = capacity;
+      PutQueue = new Queue<StorePut>();
+      GetQueue = new LinkedList<FilterStoreGet>();
+      Items = new List<StoreItem>();
+      WhenNewQueue = new List<Event>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+    }
+    public FilterStore(Simulation environment, IEnumerable<object> items, int capacity = int.MaxValue) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must be > 0", "capacity");
+      Environment = environment;
+      Capacity = capacity;
+      PutQueue = new Queue<StorePut>();
+      GetQueue = new LinkedList<FilterStoreGet>();
+      Items = new List<StoreItem>(items.Select(x => new StoreItem() { AdmissionDate = Environment.Now, Item = x }));
+      WhenNewQueue = new List<Event>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+      if (capacity < Items.Count) throw new ArgumentException("There are more initial items than there is capacity.", "items");
+    }
+
+    public virtual bool IsAvailable(Func<object, bool> filter) {
+      return Items.Select(x => x.Item).Any(filter);
+    }
+
+    public virtual StorePut Put(object item) {
+      var put = new StorePut(Environment, TriggerGet, item);
+      PutQueue.Enqueue(put);
+      TriggerPut();
+      return put;
+    }
+
+    public virtual FilterStoreGet Get(Func<object, bool> filter = null) {
+      var get = new FilterStoreGet(Environment, TriggerPut, filter ?? TrueFunc);
+      GetQueue.AddLast(get);
+      TriggerGet();
+      return get;
+    }
+
+    public virtual Event WhenNew() {
+      var whenNew = new Event(Environment);
+      WhenNewQueue.Add(whenNew);
+      return whenNew;
+    }
+
+    public virtual Event WhenAny() {
+      var whenAny = new Event(Environment);
+      WhenAnyQueue.Add(whenAny);
+      TriggerWhenAny();
+      return whenAny;
+    }
+
+    public virtual Event WhenFull() {
+      var whenFull = new Event(Environment);
+      WhenFullQueue.Add(whenFull);
+      TriggerWhenFull();
+      return whenFull;
+    }
+
+    public virtual Event WhenEmpty() {
+      var whenEmpty = new Event(Environment);
+      WhenEmptyQueue.Add(whenEmpty);
+      TriggerWhenEmpty();
+      return whenEmpty;
+    }
+
+    public virtual Event WhenChange() {
+      var whenChange = new Event(Environment);
+      WhenChangeQueue.Add(whenChange);
+      return whenChange;
+    }
+
+    protected virtual void DoPut(StorePut put) {
+      if (Items.Count < Capacity) {
+        PutWaitingTime?.Add(Environment.ToDouble(Environment.Now - put.Time));
+        Items.Add(new StoreItem() { AdmissionDate = Environment.Now, Item = put.Value });
+        put.Succeed();
+      }
+    }
+
+    protected virtual void DoGet(FilterStoreGet get) {
+      for (int i = 0; i < Items.Count; i++) {
+        var item = Items[i];
+        if (!get.Filter(item.Item)) continue;
+        GetWaitingTime?.Add(Environment.ToDouble(Environment.Now - get.Time));
+        LeadTime?.Add(Environment.ToDouble(Environment.Now - item.AdmissionDate));
+        Items.RemoveAt(i);
+        get.Succeed(item.Item);
+        return;
+      }
+    }
+
+    protected virtual void TriggerPut(Event @event = null) {
+      while (PutQueue.Count > 0) {
+        var put = PutQueue.Peek();
+        DoPut(put);
+        if (put.IsTriggered) {
+          PutQueue.Dequeue();
+          TriggerWhenNew();
+          TriggerWhenAny();
+          TriggerWhenFull();
+          TriggerWhenChange();
+        } else break;
+      }
+      Utilization?.UpdateTo(Count / (double)Capacity);
+      WIP?.UpdateTo(Count + PutQueue.Count + GetQueue.Count);
+      PutQueueLength?.UpdateTo(PutQueue.Count);
+    }
+
+    protected virtual void TriggerGet(Event @event = null) {
+      var current = GetQueue.First;
+      while (current != null) {
+        var get = current.Value;
+        DoGet(get);
+        if (get.IsTriggered) {
+          var next = current.Next;
+          GetQueue.Remove(current);
+          current = next;
+          TriggerWhenEmpty();
+          TriggerWhenChange();
+        } else current = current.Next;
+        if (Items.Count == 0) break;
+      }
+      Utilization?.UpdateTo(Count / (double)Capacity);
+      WIP?.UpdateTo(Count + PutQueue.Count + GetQueue.Count);
+      GetQueueLength?.UpdateTo(GetQueue.Count);
+    }
+
+    protected virtual void TriggerWhenNew() {
+      if (WhenNewQueue.Count == 0) return;
+      foreach (var evt in WhenNewQueue)
+        evt.Succeed();
+      WhenNewQueue.Clear();
+    }
+
+    protected virtual void TriggerWhenAny() {
+      if (Items.Count > 0) {
+        if (WhenAnyQueue.Count == 0) return;
+        foreach (var evt in WhenAnyQueue)
+          evt.Succeed();
+        WhenAnyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenFull() {
+      if (Count == Capacity) {
+        if (WhenFullQueue.Count == 0) return;
+        foreach (var evt in WhenFullQueue)
+          evt.Succeed();
+        WhenFullQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenEmpty() {
+      if (Count == 0) {
+        if (WhenEmptyQueue.Count == 0) return;
+        foreach (var evt in WhenEmptyQueue)
+          evt.Succeed();
+        WhenEmptyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenChange() {
+      if (WhenChangeQueue.Count == 0) return;
+      foreach (var evt in WhenChangeQueue)
+        evt.Succeed();
+      WhenChangeQueue.Clear();
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/PreemptiveResource.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/PreemptiveResource.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/PreemptiveResource.cs	(revision 18027)
@@ -0,0 +1,211 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SimSharp {
+  /// <summary>
+  /// A PreemptiveResource is similar to a <see cref="PriorityResource"/>. However,
+  /// it may be possible to interrupt a lower-priority user and hand over the resource.
+  /// 
+  /// PreemptiveResource holds a fixed amount of anonymous entities.
+  /// Requests are processed in this order: priority, time, preemption, and finally FIFO.
+  /// Releases are processed in FIFO order (usually no simulation time passes for a Release).
+  /// </summary>
+  /// <remarks>
+  /// Working with PreemptiveResource, a process holding a request must always call
+  /// <see cref="Process.HandleFault"/> after yielding an event and handle a potential
+  /// interruption.
+  /// </remarks>
+  public class PreemptiveResource {
+
+    public int Capacity { get; protected set; }
+
+    public int InUse { get { return Users.Count; } }
+
+    public int Remaining { get { return Capacity - InUse; } }
+
+    protected Simulation Environment { get; private set; }
+
+    protected SimplePriorityQueue<PreemptiveRequest> RequestQueue { get; private set; }
+    protected Queue<Release> ReleaseQueue { get; private set; }
+    protected HashSet<PreemptiveRequest> Users { get; private set; }
+    protected List<Event> WhenAnyQueue { get; private set; }
+    protected List<Event> WhenFullQueue { get; private set; }
+    protected List<Event> WhenEmptyQueue { get; private set; }
+    protected List<Event> WhenChangeQueue { get; private set; }
+
+    public ITimeSeriesMonitor Utilization { get; set; }
+    public ITimeSeriesMonitor WIP { get; set; }
+    public ITimeSeriesMonitor QueueLength { get; set; }
+    public ISampleMonitor LeadTime { get; set; }
+    public ISampleMonitor WaitingTime { get; set; }
+    public ISampleMonitor BreakOffTime { get; set; }
+    public ISampleMonitor InterruptTime { get; set; }
+
+    public PreemptiveResource(Simulation environment, int capacity = 1) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must be > 0.", "capacity");
+      Environment = environment;
+      Capacity = capacity;
+      RequestQueue = new SimplePriorityQueue<PreemptiveRequest>();
+      ReleaseQueue = new Queue<Release>();
+      Users = new HashSet<PreemptiveRequest>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+    }
+
+    public virtual PreemptiveRequest Request(double priority = 1, bool preempt = false) {
+      var request = new PreemptiveRequest(Environment, TriggerRelease, DisposeCallback, priority, preempt);
+      RequestQueue.Enqueue(request);
+      TriggerRequest();
+      return request;
+    }
+
+    public virtual Release Release(PreemptiveRequest request) {
+      var release = new Release(Environment, request, TriggerRequest);
+      ReleaseQueue.Enqueue(release);
+      TriggerRelease();
+      return release;
+    }
+
+    public virtual Event WhenAny() {
+      var whenAny = new Event(Environment);
+      WhenAnyQueue.Add(whenAny);
+      TriggerWhenAny();
+      return whenAny;
+    }
+
+    public virtual Event WhenFull() {
+      var whenFull = new Event(Environment);
+      WhenFullQueue.Add(whenFull);
+      TriggerWhenFull();
+      return whenFull;
+    }
+
+    public virtual Event WhenEmpty() {
+      var whenEmpty = new Event(Environment);
+      WhenEmptyQueue.Add(whenEmpty);
+      TriggerWhenEmpty();
+      return whenEmpty;
+    }
+
+    public virtual Event WhenChange() {
+      var whenChange = new Event(Environment);
+      WhenChangeQueue.Add(whenChange);
+      return whenChange;
+    }
+
+    protected void DisposeCallback(Event @event) {
+      var request = @event as PreemptiveRequest;
+      if (request != null) Release(request);
+    }
+
+    protected virtual void DoRequest(PreemptiveRequest request) {
+      if (Users.Count >= Capacity && request.Preempt) {
+        // Check if we can preempt another process
+        // MaxItems are the least important according to priorty, time, and preemption flag
+        var preempt = Users.MaxItems(x => x).Last();
+        if (preempt.CompareTo(request) > 0) {
+          InterruptTime?.Add(Environment.ToDouble(Environment.Now - request.Time));
+          preempt.IsPreempted = true;
+          Users.Remove(preempt);
+          preempt.Owner?.Interrupt(new Preempted(request.Owner, preempt.Time));
+        }
+      }
+      if (Users.Count < Capacity) {
+        WaitingTime?.Add(Environment.ToDouble(Environment.Now - request.Time));
+        Users.Add(request);
+        request.Succeed();
+      }
+    }
+
+    protected virtual void DoRelease(Release release) {
+      var req = (PreemptiveRequest)release.Request;
+      if (!Users.Remove(req) && !req.IsPreempted)
+        throw new InvalidOperationException("Released request does not have a user.");
+      LeadTime?.Add(Environment.ToDouble(Environment.Now - release.Request.Time));
+      release.Succeed();
+    }
+
+    protected virtual void TriggerRequest(Event @event = null) {
+      while (RequestQueue.Count > 0) {
+        var request = RequestQueue.First;
+        DoRequest(request);
+        if (request.IsTriggered) {
+          RequestQueue.Dequeue();
+          TriggerWhenEmpty();
+          TriggerWhenChange();
+        } else break;
+      }
+      Utilization?.UpdateTo(InUse / (double)Capacity);
+      WIP?.UpdateTo(InUse + RequestQueue.Count);
+      QueueLength?.UpdateTo(RequestQueue.Count);
+    }
+
+    protected virtual void TriggerRelease(Event @event = null) {
+      while (ReleaseQueue.Count > 0) {
+        var release = ReleaseQueue.Peek();
+        if (release.Request.IsAlive) {
+          if (!RequestQueue.TryRemove((PreemptiveRequest)release.Request))
+            throw new InvalidOperationException("Failed to cancel a request.");
+          BreakOffTime?.Add(Environment.ToDouble(Environment.Now - release.Request.Time));
+          release.Succeed();
+          ReleaseQueue.Dequeue();
+        } else {
+          DoRelease(release);
+          if (release.IsTriggered) {
+            ReleaseQueue.Dequeue();
+            TriggerWhenAny();
+            TriggerWhenFull();
+            TriggerWhenChange();
+          } else break;
+        }
+      }
+      Utilization?.UpdateTo(InUse / (double)Capacity);
+      WIP?.UpdateTo(InUse + RequestQueue.Count);
+      QueueLength?.UpdateTo(RequestQueue.Count);
+    }
+
+    protected virtual void TriggerWhenAny() {
+      if (Remaining > 0) {
+        if (WhenAnyQueue.Count == 0) return;
+        foreach (var evt in WhenAnyQueue)
+          evt.Succeed();
+        WhenAnyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenFull() {
+      if (InUse == 0) {
+        if (WhenFullQueue.Count == 0) return;
+        foreach (var evt in WhenFullQueue)
+          evt.Succeed();
+        WhenFullQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenEmpty() {
+      if (Remaining == 0) {
+        if (WhenEmptyQueue.Count == 0) return;
+        foreach (var evt in WhenEmptyQueue)
+          evt.Succeed();
+        WhenEmptyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenChange() {
+      if (WhenChangeQueue.Count == 0) return;
+      foreach (var evt in WhenChangeQueue)
+        evt.Succeed();
+      WhenChangeQueue.Clear();
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/PriorityResource.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/PriorityResource.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/PriorityResource.cs	(revision 18027)
@@ -0,0 +1,191 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace SimSharp {
+  /// <summary>
+  /// A PriorityResource is similar to a <see cref="Resource"/>.
+  /// However, it enables prioritizing requests.
+  /// 
+  /// PriorityResource holds a fixed number of anonymous entities.
+  /// Requests are processed in order priority first, FIFO second.
+  /// Releases are processed in FIFO order (usually no simulation time passes for a Release).
+  /// </summary>
+  public class PriorityResource {
+
+    public int Capacity { get; protected set; }
+
+    public int InUse { get { return Users.Count; } }
+
+    public int Remaining { get { return Capacity - InUse; } }
+
+    protected Simulation Environment { get; private set; }
+
+    protected SimplePriorityQueue<Request, double> RequestQueue { get; private set; }
+    protected Queue<Release> ReleaseQueue { get; private set; }
+    protected HashSet<Request> Users { get; private set; }
+    protected List<Event> WhenAnyQueue { get; private set; }
+    protected List<Event> WhenFullQueue { get; private set; }
+    protected List<Event> WhenEmptyQueue { get; private set; }
+    protected List<Event> WhenChangeQueue { get; private set; }
+
+    public ITimeSeriesMonitor Utilization { get; set; }
+    public ITimeSeriesMonitor WIP { get; set; }
+    public ITimeSeriesMonitor QueueLength { get; set; }
+    public ISampleMonitor LeadTime { get; set; }
+    public ISampleMonitor WaitingTime { get; set; }
+    public ISampleMonitor BreakOffTime { get; set; }
+
+    public PriorityResource(Simulation environment, int capacity = 1) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must be > 0.", "capacity");
+      Environment = environment;
+      Capacity = capacity;
+      RequestQueue = new SimplePriorityQueue<Request, double>();
+      ReleaseQueue = new Queue<Release>();
+      Users = new HashSet<Request>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+    }
+
+    public virtual Request Request(double priority = 1) {
+      var request = new Request(Environment, TriggerRelease, DisposeCallback);
+      RequestQueue.Enqueue(request, priority);
+      TriggerRequest();
+      return request;
+    }
+
+    public virtual Release Release(Request request) {
+      var release = new Release(Environment, request, TriggerRequest);
+      ReleaseQueue.Enqueue(release);
+      TriggerRelease();
+      return release;
+    }
+
+    public virtual Event WhenAny() {
+      var whenAny = new Event(Environment);
+      WhenAnyQueue.Add(whenAny);
+      TriggerWhenAny();
+      return whenAny;
+    }
+
+    public virtual Event WhenFull() {
+      var whenFull = new Event(Environment);
+      WhenFullQueue.Add(whenFull);
+      TriggerWhenFull();
+      return whenFull;
+    }
+
+    public virtual Event WhenEmpty() {
+      var whenEmpty = new Event(Environment);
+      WhenEmptyQueue.Add(whenEmpty);
+      TriggerWhenEmpty();
+      return whenEmpty;
+    }
+
+    public virtual Event WhenChange() {
+      var whenChange = new Event(Environment);
+      WhenChangeQueue.Add(whenChange);
+      return whenChange;
+    }
+
+    protected void DisposeCallback(Event @event) {
+      var request = @event as Request;
+      if (request != null) Release(request);
+    }
+
+    protected virtual void DoRequest(Request request) {
+      if (Users.Count < Capacity) {
+        WaitingTime?.Add(Environment.ToDouble(Environment.Now - request.Time));
+        Users.Add(request);
+        request.Succeed();
+      }
+    }
+
+    protected virtual void DoRelease(Release release) {
+      if (!Users.Remove(release.Request))
+        throw new InvalidOperationException("Released request does not have a user.");
+      LeadTime?.Add(Environment.ToDouble(Environment.Now - release.Request.Time));
+      release.Succeed();
+    }
+
+    protected virtual void TriggerRequest(Event @event = null) {
+      while (RequestQueue.Count > 0) {
+        var request = RequestQueue.First;
+        DoRequest(request);
+        if (request.IsTriggered) {
+          RequestQueue.Dequeue();
+          TriggerWhenEmpty();
+          TriggerWhenChange();
+        } else break;
+      }
+      Utilization?.UpdateTo(InUse / (double)Capacity);
+      WIP?.UpdateTo(InUse + RequestQueue.Count);
+      QueueLength?.UpdateTo(RequestQueue.Count);
+    }
+
+    protected virtual void TriggerRelease(Event @event = null) {
+      while (ReleaseQueue.Count > 0) {
+        var release = ReleaseQueue.Peek();
+        if (release.Request.IsAlive) {
+          if (!RequestQueue.TryRemove(release.Request))
+            throw new InvalidOperationException("Failed to cancel a request.");
+          BreakOffTime?.Add(Environment.ToDouble(Environment.Now - release.Request.Time));
+          release.Succeed();
+          ReleaseQueue.Dequeue();
+        }
+        DoRelease(release);
+        if (release.IsTriggered) {
+          ReleaseQueue.Dequeue();
+          TriggerWhenAny();
+          TriggerWhenFull();
+          TriggerWhenChange();
+        } else break;
+      }
+      Utilization?.UpdateTo(InUse / (double)Capacity);
+      WIP?.UpdateTo(InUse + RequestQueue.Count);
+      QueueLength?.UpdateTo(RequestQueue.Count);
+    }
+
+    protected virtual void TriggerWhenAny() {
+      if (Remaining > 0) {
+        if (WhenAnyQueue.Count == 0) return;
+        foreach (var evt in WhenAnyQueue)
+          evt.Succeed();
+        WhenAnyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenFull() {
+      if (InUse == 0) {
+        if (WhenFullQueue.Count == 0) return;
+        foreach (var evt in WhenFullQueue)
+          evt.Succeed();
+        WhenFullQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenEmpty() {
+      if (Remaining == 0) {
+        if (WhenEmptyQueue.Count == 0) return;
+        foreach (var evt in WhenEmptyQueue)
+          evt.Succeed();
+        WhenEmptyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenChange() {
+      if (WhenChangeQueue.Count == 0) return;
+      foreach (var evt in WhenChangeQueue)
+        evt.Succeed();
+      WhenChangeQueue.Clear();
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/PriorityStore.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/PriorityStore.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/PriorityStore.cs	(revision 18027)
@@ -0,0 +1,226 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SimSharp {
+  /// <summary>
+  /// A PriorityStore is similar to a <see cref="Store"/>.
+  /// However, items are removed from the store in order of their priority.
+  /// 
+  /// PriorityStore holds a variable number of individual items.
+  /// Put and Get are both processed in strict FIFO order.
+  /// </summary>
+  public class PriorityStore {
+
+    public int Capacity { get; protected set; }
+
+    public int Count { get { return Items.Count; } }
+
+    protected Simulation Environment { get; private set; }
+
+    protected Queue<StorePut> PutQueue { get; private set; }
+    protected Queue<StoreGet> GetQueue { get; private set; }
+    protected SimplePriorityQueue<StoreItem, double> Items { get; private set; }
+    protected List<Event> WhenNewQueue { get; private set; }
+    protected List<Event> WhenAnyQueue { get; private set; }
+    protected List<Event> WhenFullQueue { get; private set; }
+    protected List<Event> WhenEmptyQueue { get; private set; }
+    protected List<Event> WhenChangeQueue { get; private set; }
+
+    public ITimeSeriesMonitor Utilization { get; set; }
+    public ITimeSeriesMonitor WIP { get; set; }
+    public ISampleMonitor LeadTime { get; set; }
+    public ITimeSeriesMonitor PutQueueLength { get; set; }
+    public ISampleMonitor PutWaitingTime { get; set; }
+    public ITimeSeriesMonitor GetQueueLength { get; set; }
+    public ISampleMonitor GetWaitingTime { get; set; }
+
+    public PriorityStore(Simulation environment, int capacity = int.MaxValue) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must be > 0", "capacity");
+      Environment = environment;
+      Capacity = capacity;
+      PutQueue = new Queue<StorePut>();
+      GetQueue = new Queue<StoreGet>();
+      Items = new SimplePriorityQueue<StoreItem, double>();
+      WhenNewQueue = new List<Event>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+    }
+    public PriorityStore(Simulation environment, IEnumerable<object> items, IEnumerable<double> priorities, int capacity = int.MaxValue) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must be > 0", "capacity");
+      Environment = environment;
+      Capacity = capacity;
+      PutQueue = new Queue<StorePut>();
+      GetQueue = new Queue<StoreGet>();
+      Items = new SimplePriorityQueue<StoreItem, double>();
+      var itemsList = items.ToList();
+      foreach (var zip in itemsList.Zip(priorities, (a, b) => new { Item = a, Prio = b }))
+        Items.Enqueue(new StoreItem() { AdmissionDate = environment.Now, Item = zip.Item }, zip.Prio);
+      if (Items.Count != itemsList.Count) throw new ArgumentException("Fewer priorities than items are given.", "priorities");
+
+      WhenNewQueue = new List<Event>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+      if (capacity < Items.Count) throw new ArgumentException("There are more initial items than there is capacity.", "items");
+    }
+
+    public virtual StorePut Put(object item, double priority = 1) {
+      var put = new StorePut(Environment, TriggerGet, new PriorityItem(priority, item));
+      PutQueue.Enqueue(put);
+      TriggerPut();
+      return put;
+    }
+
+    public virtual StoreGet Get() {
+      var get = new StoreGet(Environment, TriggerPut);
+      GetQueue.Enqueue(get);
+      TriggerGet();
+      return get;
+    }
+
+    public virtual Event WhenNew() {
+      var whenNew = new Event(Environment);
+      WhenNewQueue.Add(whenNew);
+      return whenNew;
+    }
+
+    public virtual Event WhenAny() {
+      var whenAny = new Event(Environment);
+      WhenAnyQueue.Add(whenAny);
+      TriggerWhenAny();
+      return whenAny;
+    }
+
+    public virtual Event WhenFull() {
+      var whenFull = new Event(Environment);
+      WhenFullQueue.Add(whenFull);
+      TriggerWhenFull();
+      return whenFull;
+    }
+
+    public virtual Event WhenEmpty() {
+      var whenEmpty = new Event(Environment);
+      WhenEmptyQueue.Add(whenEmpty);
+      TriggerWhenEmpty();
+      return whenEmpty;
+    }
+
+    public virtual Event WhenChange() {
+      var whenChange = new Event(Environment);
+      WhenChangeQueue.Add(whenChange);
+      return whenChange;
+    }
+
+    protected virtual void DoPut(StorePut put) {
+      if (Items.Count < Capacity) {
+        var pi = (PriorityItem)put.Value;
+        PutWaitingTime?.Add(Environment.ToDouble(Environment.Now - put.Time));
+        Items.Enqueue(new StoreItem() { AdmissionDate = Environment.Now, Item = pi.Item}, pi.Priority);
+        put.Succeed();
+      }
+    }
+
+    protected virtual void DoGet(StoreGet get) {
+      if (Items.Count > 0) {
+        var item = Items.Dequeue();
+        GetWaitingTime?.Add(Environment.ToDouble(Environment.Now - get.Time));
+        LeadTime?.Add(Environment.ToDouble(Environment.Now - item.AdmissionDate));
+        get.Succeed(item.Item);
+      }
+    }
+
+    protected virtual void TriggerPut(Event @event = null) {
+      while (PutQueue.Count > 0) {
+        var put = PutQueue.Peek();
+        DoPut(put);
+        if (put.IsTriggered) {
+          PutQueue.Dequeue();
+          TriggerWhenNew();
+          TriggerWhenAny();
+          TriggerWhenFull();
+          TriggerWhenChange();
+        } else break;
+      }
+      Utilization?.UpdateTo(Count / (double)Capacity);
+      WIP?.UpdateTo(Count + PutQueue.Count + GetQueue.Count);
+      PutQueueLength?.UpdateTo(PutQueue.Count);
+    }
+
+    protected virtual void TriggerGet(Event @event = null) {
+      while (GetQueue.Count > 0) {
+        var get = GetQueue.Peek();
+        DoGet(get);
+        if (get.IsTriggered) {
+          GetQueue.Dequeue();
+          TriggerWhenEmpty();
+          TriggerWhenChange();
+        } else break;
+      }
+      Utilization?.UpdateTo(Count / (double)Capacity);
+      WIP?.UpdateTo(Count + PutQueue.Count + GetQueue.Count);
+      GetQueueLength?.UpdateTo(GetQueue.Count);
+    }
+
+    protected virtual void TriggerWhenNew() {
+      if (WhenNewQueue.Count == 0) return;
+      foreach (var evt in WhenNewQueue)
+        evt.Succeed();
+      WhenNewQueue.Clear();
+    }
+
+    protected virtual void TriggerWhenAny() {
+      if (Items.Count > 0) {
+        if (WhenAnyQueue.Count == 0) return;
+        foreach (var evt in WhenAnyQueue)
+          evt.Succeed();
+        WhenAnyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenFull() {
+      if (Count == Capacity) {
+        if (WhenFullQueue.Count == 0) return;
+        foreach (var evt in WhenFullQueue)
+          evt.Succeed();
+        WhenFullQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenEmpty() {
+      if (Count == 0) {
+        if (WhenEmptyQueue.Count == 0) return;
+        foreach (var evt in WhenEmptyQueue)
+          evt.Succeed();
+        WhenEmptyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenChange() {
+      if (WhenChangeQueue.Count == 0) return;
+      foreach (var evt in WhenChangeQueue)
+        evt.Succeed();
+      WhenChangeQueue.Clear();
+    }
+
+    protected struct PriorityItem {
+      public double Priority { get; }
+      public object Item { get; }
+
+      public PriorityItem(double priority, object item) {
+        Priority = priority;
+        Item = item;
+      }
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Resource.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Resource.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Resource.cs	(revision 18027)
@@ -0,0 +1,192 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace SimSharp {
+  /// <summary>
+  /// A resource holds a fixed number of anonymous entities.
+  /// 
+  /// Requests are processed in FIFO order.
+  /// Releases are processed in FIFO order (usually no simulation time passes for a Release).
+  /// </summary>
+  public class Resource {
+
+    public int Capacity { get; protected set; }
+
+    public int InUse { get { return Users.Count; } }
+
+    public int Remaining { get { return Capacity - InUse; } }
+
+    protected Simulation Environment { get; private set; }
+
+    protected LinkedList<Request> RequestQueue { get; private set; }
+    protected Queue<Release> ReleaseQueue { get; private set; }
+    protected HashSet<Request> Users { get; private set; }
+    protected List<Event> WhenAnyQueue { get; private set; }
+    protected List<Event> WhenFullQueue { get; private set; }
+    protected List<Event> WhenEmptyQueue { get; private set; }
+    protected List<Event> WhenChangeQueue { get; private set; }
+
+    public ITimeSeriesMonitor Utilization { get; set; }
+    public ITimeSeriesMonitor WIP { get; set; }
+    public ITimeSeriesMonitor QueueLength { get; set; }
+    public ISampleMonitor LeadTime { get; set; }
+    public ISampleMonitor WaitingTime { get; set; }
+    public ISampleMonitor BreakOffTime { get; set; }
+
+    public Resource(Simulation environment, int capacity = 1) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must > 0.", "capacity");
+      Environment = environment;
+      Capacity = capacity;
+      RequestQueue = new LinkedList<Request>();
+      ReleaseQueue = new Queue<Release>();
+      Users = new HashSet<Request>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+    }
+
+    public virtual Request Request() {
+      var request = new Request(Environment, TriggerRelease, DisposeCallback);
+      RequestQueue.AddLast(request);
+      TriggerRequest();
+      return request;
+    }
+
+    public virtual Release Release(Request request) {
+      var release = new Release(Environment, request, TriggerRequest);
+      ReleaseQueue.Enqueue(release);
+      TriggerRelease();
+      return release;
+    }
+
+    public virtual Event WhenAny() {
+      var whenAny = new Event(Environment);
+      WhenAnyQueue.Add(whenAny);
+      TriggerWhenAny();
+      return whenAny;
+    }
+
+    public virtual Event WhenFull() {
+      var whenFull = new Event(Environment);
+      WhenFullQueue.Add(whenFull);
+      TriggerWhenFull();
+      return whenFull;
+    }
+
+    public virtual Event WhenEmpty() {
+      var whenEmpty = new Event(Environment);
+      WhenEmptyQueue.Add(whenEmpty);
+      TriggerWhenEmpty();
+      return whenEmpty;
+    }
+
+    public virtual Event WhenChange() {
+      var whenChange = new Event(Environment);
+      WhenChangeQueue.Add(whenChange);
+      return whenChange;
+    }
+
+    protected virtual void DisposeCallback(Event @event) {
+      var request = @event as Request;
+      if (request != null) {
+        Release(request);
+      }
+    }
+
+    protected virtual void DoRequest(Request request) {
+      if (Users.Count < Capacity) {
+        WaitingTime?.Add(Environment.ToDouble(Environment.Now - request.Time));
+        Users.Add(request);
+        request.Succeed();
+      }
+    }
+
+    protected virtual void DoRelease(Release release) {
+      if (!Users.Remove(release.Request))
+        throw new InvalidOperationException("Released request does not have a user.");
+      LeadTime?.Add(Environment.ToDouble(Environment.Now - release.Request.Time));
+      release.Succeed();
+    }
+
+    protected virtual void TriggerRequest(Event @event = null) {
+      while (RequestQueue.Count > 0) {
+        var request = RequestQueue.First.Value;
+        DoRequest(request);
+        if (request.IsTriggered) {
+          RequestQueue.RemoveFirst();
+          TriggerWhenEmpty();
+          TriggerWhenChange();
+        } else break;
+      }
+      Utilization?.UpdateTo(InUse / (double)Capacity);
+      WIP?.UpdateTo(InUse + RequestQueue.Count);
+      QueueLength?.UpdateTo(RequestQueue.Count);
+    }
+
+    protected virtual void TriggerRelease(Event @event = null) {
+      while (ReleaseQueue.Count > 0) {
+        var release = ReleaseQueue.Peek();
+        if (release.Request.IsAlive) {
+          if (!RequestQueue.Remove(release.Request))
+            throw new InvalidOperationException("Failed to cancel a request.");
+          BreakOffTime?.Add(Environment.ToDouble(Environment.Now - release.Request.Time));
+          release.Succeed();
+          ReleaseQueue.Dequeue();
+        } else {
+          DoRelease(release);
+          if (release.IsTriggered) {
+            ReleaseQueue.Dequeue();
+            TriggerWhenAny();
+            TriggerWhenFull();
+            TriggerWhenChange();
+          } else break;
+        }
+      }
+      Utilization?.UpdateTo(InUse / (double)Capacity);
+      WIP?.UpdateTo(InUse + RequestQueue.Count);
+      QueueLength?.UpdateTo(RequestQueue.Count);
+    }
+
+    protected virtual void TriggerWhenAny() {
+      if (Remaining > 0) {
+        if (WhenAnyQueue.Count == 0) return;
+        foreach (var evt in WhenAnyQueue)
+          evt.Succeed();
+        WhenAnyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenFull() {
+      if (InUse == 0) {
+        if (WhenFullQueue.Count == 0) return;
+        foreach (var evt in WhenFullQueue)
+          evt.Succeed();
+        WhenFullQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenEmpty() {
+      if (Remaining == 0) {
+        if (WhenEmptyQueue.Count == 0) return;
+        foreach (var evt in WhenEmptyQueue)
+          evt.Succeed();
+        WhenEmptyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenChange() {
+      if (WhenChangeQueue.Count == 0) return;
+      foreach (var evt in WhenChangeQueue)
+        evt.Succeed();
+      WhenChangeQueue.Clear();
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/ResourcePool.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/ResourcePool.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/ResourcePool.cs	(revision 18027)
@@ -0,0 +1,203 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SimSharp {
+  /// <summary>
+  /// A ResourcePool is a crossover between a <see cref="Resource"/> and a <see cref="Store"/>.
+  /// There is a fixed number of non-anonymous resources.
+  /// 
+  /// Requests are performed in FIFO order only when they match at least one resource in the pool.
+  /// Releases are processed in FIFO order (usually no simulation time passes for a Release).
+  /// </summary>
+  public class ResourcePool {
+    protected static readonly Func<object, bool> TrueFunc = _ => true;
+
+    public int Capacity { get; protected set; }
+
+    public int InUse { get { return Capacity - Remaining; } }
+
+    public int Remaining { get { return Resources.Count; } }
+
+    protected Simulation Environment { get; private set; }
+
+    protected LinkedList<ResourcePoolRequest> RequestQueue { get; private set; }
+    protected Queue<Release> ReleaseQueue { get; private set; }
+    protected List<object> Resources { get; private set; }
+    protected List<Event> WhenAnyQueue { get; private set; }
+    protected List<Event> WhenFullQueue { get; private set; }
+    protected List<Event> WhenEmptyQueue { get; private set; }
+    protected List<Event> WhenChangeQueue { get; private set; }
+
+    public ITimeSeriesMonitor Utilization { get; set; }
+    public ITimeSeriesMonitor WIP { get; set; }
+    public ITimeSeriesMonitor QueueLength { get; set; }
+    public ISampleMonitor LeadTime { get; set; }
+    public ISampleMonitor WaitingTime { get; set; }
+    public ISampleMonitor BreakOffTime { get; set; }
+
+    public ResourcePool(Simulation environment, IEnumerable<object> resources) {
+      Environment = environment;
+      if (resources == null) throw new ArgumentNullException("resources");
+      Resources = new List<object>(resources);
+      Capacity = Resources.Count;
+      if (Capacity == 0) throw new ArgumentException("There must be at least one resource", "resources");
+      RequestQueue = new LinkedList<ResourcePoolRequest>();
+      ReleaseQueue = new Queue<Release>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+    }
+
+    public virtual bool IsAvailable(Func<object, bool> filter) {
+      return Resources.Any(filter);
+    }
+
+    public virtual ResourcePoolRequest Request(Func<object, bool> filter = null) {
+      var request = new ResourcePoolRequest(Environment, TriggerRelease, DisposeCallback, filter ?? TrueFunc);
+      RequestQueue.AddLast(request);
+      TriggerRequest();
+      return request;
+    }
+
+    public virtual Release Release(Request request) {
+      var release = new Release(Environment, request, TriggerRequest);
+      ReleaseQueue.Enqueue(release);
+      TriggerRelease();
+      return release;
+    }
+
+    public virtual Event WhenAny() {
+      var whenAny = new Event(Environment);
+      WhenAnyQueue.Add(whenAny);
+      TriggerWhenAny();
+      return whenAny;
+    }
+
+    public virtual Event WhenFull() {
+      var whenFull = new Event(Environment);
+      WhenFullQueue.Add(whenFull);
+      TriggerWhenFull();
+      return whenFull;
+    }
+
+    public virtual Event WhenEmpty() {
+      var whenEmpty = new Event(Environment);
+      WhenEmptyQueue.Add(whenEmpty);
+      TriggerWhenEmpty();
+      return whenEmpty;
+    }
+
+    public virtual Event WhenChange() {
+      var whenChange = new Event(Environment);
+      WhenChangeQueue.Add(whenChange);
+      return whenChange;
+    }
+
+    protected virtual void DisposeCallback(Event @event) {
+      var request = @event as Request;
+      if (request != null) Release(request);
+    }
+
+    protected virtual void DoRequest(ResourcePoolRequest request) {
+      foreach (var o in Resources) {
+        if (!request.Filter(o)) continue;
+        WaitingTime?.Add(Environment.ToDouble(Environment.Now - request.Time));
+        Resources.Remove(o);
+        request.Succeed(o);
+        return;
+      }
+    }
+
+    protected virtual void DoRelease(Release release) {
+      Resources.Add(release.Request.Value);
+      LeadTime?.Add(Environment.ToDouble(Environment.Now - release.Request.Time));
+      release.Succeed();
+    }
+
+    protected virtual void TriggerRequest(Event @event = null) {
+      var current = RequestQueue.First;
+      while (current != null) {
+        var request = current.Value;
+        DoRequest(request);
+        if (request.IsTriggered) {
+          var next = current.Next;
+          RequestQueue.Remove(current);
+          current = next;
+          TriggerWhenEmpty();
+          TriggerWhenChange();
+        } else current = current.Next;
+        if (Resources.Count == 0) break;
+      }
+      Utilization?.UpdateTo(InUse / (double)Capacity);
+      WIP?.UpdateTo(InUse + RequestQueue.Count);
+      QueueLength?.UpdateTo(RequestQueue.Count);
+    }
+
+    protected virtual void TriggerRelease(Event @event = null) {
+      while (ReleaseQueue.Count > 0) {
+        var release = ReleaseQueue.Peek();
+        if (release.Request.IsAlive) {
+          if (!RequestQueue.Remove((ResourcePoolRequest)release.Request))
+            throw new InvalidOperationException("Failed to cancel a request.");
+          BreakOffTime?.Add(Environment.ToDouble(Environment.Now - release.Request.Time));
+          release.Succeed();
+          ReleaseQueue.Dequeue();
+        } else {
+          DoRelease(release);
+          if (release.IsTriggered) {
+            ReleaseQueue.Dequeue();
+            TriggerWhenAny();
+            TriggerWhenFull();
+            TriggerWhenChange();
+          } else break;
+        }
+      }
+      Utilization?.UpdateTo(InUse / (double)Capacity);
+      WIP?.UpdateTo(InUse + RequestQueue.Count);
+      QueueLength?.UpdateTo(RequestQueue.Count);
+    }
+
+    protected virtual void TriggerWhenAny() {
+      if (Remaining > 0) {
+        if (WhenAnyQueue.Count == 0) return;
+        foreach (var evt in WhenAnyQueue)
+          evt.Succeed();
+        WhenAnyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenFull() {
+      if (InUse == 0) {
+        if (WhenFullQueue.Count == 0) return;
+        foreach (var evt in WhenFullQueue)
+          evt.Succeed();
+        WhenFullQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenEmpty() {
+      if (Remaining == 0) {
+        if (WhenEmptyQueue.Count == 0) return;
+        foreach (var evt in WhenEmptyQueue)
+          evt.Succeed();
+        WhenEmptyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenChange() {
+      if (WhenChangeQueue.Count == 0) return;
+      foreach (var evt in WhenChangeQueue)
+        evt.Succeed();
+      WhenChangeQueue.Clear();
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Store.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Store.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Core/Resources/Store.cs	(revision 18027)
@@ -0,0 +1,215 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SimSharp {
+  /// <summary>
+  /// The store holds a variable number of individual items.
+  /// The items are removed from the store in FIFO order.
+  /// 
+  /// Put are processed in FIFO order.
+  /// Get are processed in FIFO order.
+  /// </summary>
+  public class Store {
+
+    public int Capacity { get; protected set; }
+
+    public int Count { get { return Items.Count; } }
+
+    protected Simulation Environment { get; private set; }
+
+    protected Queue<StorePut> PutQueue { get; private set; }
+    protected Queue<StoreGet> GetQueue { get; private set; }
+    protected Queue<StoreItem> Items { get; private set; }
+    protected List<Event> WhenNewQueue { get; private set; }
+    protected List<Event> WhenAnyQueue { get; private set; }
+    protected List<Event> WhenFullQueue { get; private set; }
+    protected List<Event> WhenEmptyQueue { get; private set; }
+    protected List<Event> WhenChangeQueue { get; private set; }
+
+    public ITimeSeriesMonitor Utilization { get; set; }
+    public ITimeSeriesMonitor WIP { get; set; }
+    public ISampleMonitor LeadTime { get; set; }
+    public ITimeSeriesMonitor PutQueueLength { get; set; }
+    public ISampleMonitor PutWaitingTime { get; set; }
+    public ITimeSeriesMonitor GetQueueLength { get; set; }
+    public ISampleMonitor GetWaitingTime { get; set; }
+
+    public Store(Simulation environment, int capacity = int.MaxValue) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must be > 0", "capacity");
+      Environment = environment;
+      Capacity = capacity;
+      PutQueue = new Queue<StorePut>();
+      GetQueue = new Queue<StoreGet>();
+      Items = new Queue<StoreItem>();
+      WhenNewQueue = new List<Event>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+    }
+    public Store(Simulation environment, IEnumerable<object> items, int capacity = int.MaxValue) {
+      if (capacity <= 0) throw new ArgumentException("Capacity must be > 0", "capacity");
+      Environment = environment;
+      Capacity = capacity;
+      PutQueue = new Queue<StorePut>();
+      GetQueue = new Queue<StoreGet>();
+      Items = new Queue<StoreItem>(items.Select(x => new StoreItem() { AdmissionDate = environment.Now, Item = x }));
+      WhenNewQueue = new List<Event>();
+      WhenAnyQueue = new List<Event>();
+      WhenFullQueue = new List<Event>();
+      WhenEmptyQueue = new List<Event>();
+      WhenChangeQueue = new List<Event>();
+      if (capacity < Items.Count) throw new ArgumentException("There are more initial items than there is capacity.", "items");
+    }
+
+    public virtual StorePut Put(object item) {
+      var put = new StorePut(Environment, TriggerGet, item);
+      PutQueue.Enqueue(put);
+      TriggerPut();
+      return put;
+    }
+
+    public virtual StoreGet Get() {
+      var get = new StoreGet(Environment, TriggerPut);
+      GetQueue.Enqueue(get);
+      TriggerGet();
+      return get;
+    }
+
+    public virtual Event WhenNew() {
+      var whenNew = new Event(Environment);
+      WhenNewQueue.Add(whenNew);
+      return whenNew;
+    }
+
+    public virtual Event WhenAny() {
+      var whenAny = new Event(Environment);
+      WhenAnyQueue.Add(whenAny);
+      TriggerWhenAny();
+      return whenAny;
+    }
+
+    public virtual Event WhenFull() {
+      var whenFull = new Event(Environment);
+      WhenFullQueue.Add(whenFull);
+      TriggerWhenFull();
+      return whenFull;
+    }
+
+    public virtual Event WhenEmpty() {
+      var whenEmpty = new Event(Environment);
+      WhenEmptyQueue.Add(whenEmpty);
+      TriggerWhenEmpty();
+      return whenEmpty;
+    }
+
+    public virtual Event WhenChange() {
+      var whenChange = new Event(Environment);
+      WhenChangeQueue.Add(whenChange);
+      return whenChange;
+    }
+
+    protected virtual void DoPut(StorePut put) {
+      if (Items.Count < Capacity) {
+        PutWaitingTime?.Add(Environment.ToDouble(Environment.Now - put.Time));
+        Items.Enqueue(new StoreItem() { AdmissionDate = Environment.Now, Item = put.Value });
+        put.Succeed();
+      }
+    }
+
+    protected virtual void DoGet(StoreGet get) {
+      if (Items.Count > 0) {
+        var item = Items.Dequeue();
+        GetWaitingTime?.Add(Environment.ToDouble(Environment.Now - get.Time));
+        LeadTime?.Add(Environment.ToDouble(Environment.Now - item.AdmissionDate));
+        get.Succeed(item.Item);
+      }
+    }
+
+    protected virtual void TriggerPut(Event @event = null) {
+      while (PutQueue.Count > 0) {
+        var put = PutQueue.Peek();
+        DoPut(put);
+        if (put.IsTriggered) {
+          PutQueue.Dequeue();
+          TriggerWhenNew();
+          TriggerWhenAny();
+          TriggerWhenFull();
+          TriggerWhenChange();
+        } else break;
+      }
+      Utilization?.UpdateTo(Count / (double)Capacity);
+      WIP?.UpdateTo(Count + PutQueue.Count + GetQueue.Count);
+      PutQueueLength?.UpdateTo(PutQueue.Count);
+    }
+
+    protected virtual void TriggerGet(Event @event = null) {
+      while (GetQueue.Count > 0) {
+        var get = GetQueue.Peek();
+        DoGet(get);
+        if (get.IsTriggered) {
+          GetQueue.Dequeue();
+          TriggerWhenEmpty();
+          TriggerWhenChange();
+        } else break;
+      }
+      Utilization?.UpdateTo(Count / (double)Capacity);
+      WIP?.UpdateTo(Count + PutQueue.Count + GetQueue.Count);
+      GetQueueLength?.UpdateTo(GetQueue.Count);
+    }
+
+    protected virtual void TriggerWhenNew() {
+      if (WhenNewQueue.Count == 0) return;
+      foreach (var evt in WhenNewQueue)
+        evt.Succeed();
+      WhenNewQueue.Clear();
+    }
+
+    protected virtual void TriggerWhenAny() {
+      if (Count > 0) {
+        if (WhenAnyQueue.Count == 0) return;
+        foreach (var evt in WhenAnyQueue)
+          evt.Succeed();
+        WhenAnyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenFull() {
+      if (Count == Capacity) {
+        if (WhenFullQueue.Count == 0) return;
+        foreach (var evt in WhenFullQueue)
+          evt.Succeed();
+        WhenFullQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenEmpty() {
+      if (Count == 0) {
+        if (WhenEmptyQueue.Count == 0) return;
+        foreach (var evt in WhenEmptyQueue)
+          evt.Succeed();
+        WhenEmptyQueue.Clear();
+      }
+    }
+
+    protected virtual void TriggerWhenChange() {
+      if (WhenChangeQueue.Count == 0) return;
+      foreach (var evt in WhenChangeQueue)
+        evt.Succeed();
+      WhenChangeQueue.Clear();
+    }
+  }
+
+  public struct StoreItem {
+    public DateTime AdmissionDate;
+    public object Item;
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/IRandom.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/IRandom.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/IRandom.cs	(revision 18027)
@@ -0,0 +1,17 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+namespace SimSharp {
+  public interface IRandom {
+    int Next();
+    int Next(int upperBound);
+    int Next(int lowerBound, int upperBound);
+    double NextDouble();
+
+    void Reinitialise(int seed);
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/LICENSE-PCG.txt
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/LICENSE-PCG.txt	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/LICENSE-PCG.txt	(revision 18027)
@@ -0,0 +1,13 @@
+MIT License
+
+Copyright (c) 2016 Bismur Studios Ltd.
+Copyright (c) 2016 Ioannis Giagkiozis
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
+and associated documentation files (the "Software"), to deal in the Software without restriction, 
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
+subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or substantial 
+ portions of the Software.
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/Pcg.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/Pcg.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/Pcg.cs	(revision 18027)
@@ -0,0 +1,420 @@
+﻿// MIT License
+// 
+// Copyright (c) 2016 Bismur Studios Ltd.
+// Copyright (c) 2016 Ioannis Giagkiozis
+//
+// This file is part of PCGSharp.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
+// and associated documentation files (the "Software"), to deal in the Software without restriction, 
+// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
+// subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in all copies or substantial 
+//  portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
+//  LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
+//  NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+//  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// This file is based on PCG, the original has the following license: 
+/*
+ * PCG Random Number Generation for C.
+ *
+ * Copyright 2014 Melissa O'Neill <oneill@pcg-random.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * For additional information about the PCG random number generation scheme,
+ * including its license and other licensing options, visit
+ *
+ *       http://www.pcg-random.org
+ */
+
+using System;
+
+// Note, you will see that API is duplicated in PCG and PCGExtended, this is not by accident or 
+// neglect. It would be "cleaner" to have a base class and just implement the core of the generator
+// in derived classes, however, this has a performance cost which for random number generators, in my
+// view, is too high. That said, there is still some room for performance optimisation.
+
+namespace SimSharp {
+
+  /// <summary>
+  /// PCG (Permuted Congruential Generator) is a C# port from C the base PCG generator
+  /// presented in "PCG: A Family of Simple Fast Space-Efficient Statistically Good
+  /// Algorithms for Random Number Generation" by Melissa E. O'Neill. The code follows closely the one 
+  /// made available by O'Neill at her site: http://www.pcg-random.org/download.html
+  /// To understand how exactly this generator works read this:
+  /// http://www.pcg-random.org/pdf/toms-oneill-pcg-family-v1.02.pdf 
+  /// </summary>
+  internal class Pcg {
+
+    ulong _state;
+    // This shifted to the left and or'ed with 1ul results in the default increment.
+    const ulong ShiftedIncrement = 721347520444481703ul;
+    ulong _increment = 1442695040888963407ul;
+    const ulong Multiplier = 6364136223846793005ul;
+    const double ToDouble01 = 1.0 / 4294967296.0;
+    
+    public int Next() {
+      uint result = NextUInt();
+      return (int)(result >> 1);
+    }
+
+    public int Next(int maxExclusive) {
+      if (maxExclusive <= 0)
+        throw new ArgumentException("Max Exclusive must be positive");
+
+      uint uMaxExclusive = (uint)(maxExclusive);
+      uint threshold = (uint)(-uMaxExclusive) % uMaxExclusive;
+
+      while (true) {
+        uint result = NextUInt();
+        if (result >= threshold)
+          return (int)(result % uMaxExclusive);
+      }
+    }
+
+    public int Next(int minInclusive, int maxExclusive) {
+      if (maxExclusive <= minInclusive)
+        throw new ArgumentException("MaxExclusive must be larger than MinInclusive");
+
+      uint uMaxExclusive = unchecked((uint)(maxExclusive - minInclusive));
+      uint threshold = (uint)(-uMaxExclusive) % uMaxExclusive;
+
+      while (true) {
+        uint result = NextUInt();
+        if (result >= threshold)
+          return (int)(unchecked((result % uMaxExclusive) + minInclusive));
+      }
+    }
+
+    public int[] NextInts(int count) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new int[count];
+      for (var i = 0; i < count; i++) {
+        resultA[i] = Next();
+      }
+      return resultA;
+    }
+
+    public int[] NextInts(int count, int maxExclusive) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new int[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = Next(maxExclusive);
+      }
+      return resultA;
+    }
+
+    public int[] NextInts(int count, int minInclusive, int maxExclusive) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new int[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = Next(minInclusive, maxExclusive);
+      }
+      return resultA;
+    }
+
+    public uint NextUInt() {
+      ulong oldState = _state;
+      _state = unchecked(oldState * Multiplier + _increment);
+      uint xorShifted = (uint)(((oldState >> 18) ^ oldState) >> 27);
+      int rot = (int)(oldState >> 59);
+      uint result = (xorShifted >> rot) | (xorShifted << ((-rot) & 31));
+      return result;
+    }
+
+    public uint NextUInt(uint maxExclusive) {
+      uint threshold = (uint)(-maxExclusive) % maxExclusive;
+
+      while (true) {
+        uint result = NextUInt();
+        if (result >= threshold)
+          return result % maxExclusive;
+      }
+    }
+
+    public uint NextUInt(uint minInclusive, uint maxExclusive) {
+      if (maxExclusive <= minInclusive)
+        throw new ArgumentException();
+
+      uint diff = (maxExclusive - minInclusive);
+      uint threshold = (uint)(-diff) % diff;
+
+      while (true) {
+        uint result = NextUInt();
+        if (result >= threshold)
+          return (result % diff) + minInclusive;
+      }
+    }
+
+    public uint[] NextUInts(int count) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new uint[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = NextUInt();
+      }
+      return resultA;
+    }
+
+    public uint[] NextUInts(int count, uint maxExclusive) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new uint[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = NextUInt(maxExclusive);
+      }
+      return resultA;
+    }
+
+    public uint[] NextUInts(int count, uint minInclusive, uint maxExclusive) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new uint[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = NextUInt(minInclusive, maxExclusive);
+      }
+      return resultA;
+    }
+
+    public float NextFloat() {
+      return (float)(NextUInt() * ToDouble01);
+    }
+
+    public float NextFloat(float maxInclusive) {
+      if (maxInclusive <= 0)
+        throw new ArgumentException("MaxInclusive must be larger than 0");
+
+      return (float)(NextUInt() * ToDouble01) * maxInclusive;
+    }
+
+    public float NextFloat(float minInclusive, float maxInclusive) {
+      if (maxInclusive < minInclusive)
+        throw new ArgumentException("Max must be larger than min");
+
+      return (float)(NextUInt() * ToDouble01) * (maxInclusive - minInclusive) + minInclusive;
+    }
+
+    public float[] NextFloats(int count) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new float[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = NextFloat();
+      }
+      return resultA;
+    }
+
+    public float[] NextFloats(int count, float maxInclusive) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new float[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = NextFloat(maxInclusive);
+      }
+      return resultA;
+    }
+
+    public float[] NextFloats(int count, float minInclusive, float maxInclusive) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new float[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = NextFloat(minInclusive, maxInclusive);
+      }
+      return resultA;
+    }
+
+    public double NextDouble() {
+      return NextUInt() * ToDouble01;
+    }
+
+    public double NextDouble(double maxInclusive) {
+      if (maxInclusive <= 0)
+        throw new ArgumentException("Max must be larger than 0");
+
+      return NextUInt() * ToDouble01 * maxInclusive;
+    }
+
+    public double NextDouble(double minInclusive, double maxInclusive) {
+      if (maxInclusive < minInclusive)
+        throw new ArgumentException("Max must be larger than min");
+
+      return NextUInt() * ToDouble01 * (maxInclusive - minInclusive) + minInclusive;
+    }
+
+    public double[] NextDoubles(int count) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new double[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = NextDouble();
+      }
+      return resultA;
+    }
+
+    public double[] NextDoubles(int count, double maxInclusive) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new double[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = NextDouble(maxInclusive);
+      }
+      return resultA;
+    }
+
+    public double[] NextDoubles(int count, double minInclusive, double maxInclusive) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new double[count];
+      for (int i = 0; i < count; i++) {
+        resultA[i] = NextDouble(minInclusive, maxInclusive);
+      }
+      return resultA;
+    }
+
+    public byte NextByte() {
+      var result = NextUInt();
+      return (byte)(result % 256);
+    }
+
+    public byte[] NextBytes(int count) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new byte[count];
+      for (var i = 0; i < count; i++) {
+        resultA[i] = NextByte();
+      }
+      return resultA;
+    }
+
+    public bool NextBool() {
+      var result = NextUInt();
+      return result % 2 == 1;
+    }
+
+    public bool[] NextBools(int count) {
+      if (count <= 0)
+        throw new ArgumentException("Zero count");
+
+      var resultA = new bool[count];
+      for (var i = 0; i < count; i++) {
+        resultA[i] = NextBool();
+      }
+      return resultA;
+    }
+
+    public int PeriodPow2() {
+      return 64;
+    }
+
+    public void SetStream(int sequence) {
+      SetStream((ulong)sequence);
+    }
+
+    public void SetStream(ulong sequence) {
+      _increment = (sequence << 1) | 1;
+    }
+
+    public ulong CurrentStream() {
+      return _increment >> 1;
+    }
+
+    public Pcg() : this(PcgSeed.GuidBasedSeed()) {
+    }
+
+    public Pcg(int seed) : this((ulong)seed) {
+    }
+
+    public Pcg(int seed, int sequence) : this((ulong)seed, (ulong)sequence) {
+    }
+
+    public Pcg(ulong seed, ulong sequence = ShiftedIncrement) {
+      Initialize(seed, sequence);
+    }
+
+    void Initialize(ulong seed, ulong initseq) {
+      _state = 0ul;
+      SetStream(initseq);
+      NextUInt();
+      _state += seed;
+      NextUInt();
+    }
+
+  }
+
+// MIT License
+// 
+// Copyright (c) 2016 Bismur Studios Ltd.
+// Copyright (c) 2016 Ioannis Giagkiozis
+//
+// This file is part of PCGSharp.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
+// and associated documentation files (the "Software"), to deal in the Software without restriction, 
+// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
+// subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in all copies or substantial 
+//  portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
+//  LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
+//  NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+//  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+  internal static class PcgSeed {
+
+    /// <summary>
+    /// Provides a time-dependent seed value, matching the default behavior of System.Random.
+    /// </summary>
+    public static ulong TimeBasedSeed() {
+      return (ulong)(System.Environment.TickCount);
+    }
+
+    /// <summary>
+    /// Provides a seed based on time and unique GUIDs.
+    /// </summary>
+    public static ulong GuidBasedSeed() {
+      ulong upper = (ulong)(System.Environment.TickCount ^ Guid.NewGuid().GetHashCode()) << 32;
+      ulong lower = (ulong)(System.Environment.TickCount ^ Guid.NewGuid().GetHashCode());
+      return (upper | lower);
+    }
+
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/PcgRandom.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/PcgRandom.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/PcgRandom.cs	(revision 18027)
@@ -0,0 +1,39 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+namespace SimSharp {
+  public class PcgRandom : IRandom {
+    private Pcg pcg;
+
+    public PcgRandom() {
+      pcg = new Pcg();
+    }
+
+    public PcgRandom(int seed) {
+      pcg = new Pcg(seed);
+    }
+    public int Next() {
+      return pcg.Next();
+    }
+
+    public int Next(int upperBound) {
+      return pcg.Next(upperBound);
+    }
+
+    public int Next(int lowerBound, int upperBound) {
+      return pcg.Next(lowerBound, upperBound);
+    }
+
+    public double NextDouble() {
+      return pcg.NextDouble();
+    }
+
+    public void Reinitialise(int seed) {
+      pcg = new Pcg(seed);
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/SystemRandom.cs
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/SystemRandom.cs	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Random/SystemRandom.cs	(revision 18027)
@@ -0,0 +1,41 @@
+﻿#region License Information
+/*
+ * This file is part of SimSharp which is licensed under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+#endregion
+
+using System;
+
+namespace SimSharp {
+  public class SystemRandom : IRandom {
+    private Random random;
+
+    public SystemRandom() {
+      random = new Random();
+    }
+
+    public SystemRandom(int seed) {
+      random = new Random(seed);
+    }
+    public int Next() {
+      return random.Next();
+    }
+
+    public int Next(int upperBound) {
+      return random.Next(upperBound);
+    }
+
+    public int Next(int lowerBound, int upperBound) {
+      return random.Next(lowerBound, upperBound);
+    }
+
+    public double NextDouble() {
+      return random.NextDouble();
+    }
+
+    public void Reinitialise(int seed) {
+      random = new Random(seed);
+    }
+  }
+}
Index: branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/SimSharp-3.3.2.csproj
===================================================================
--- branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/SimSharp-3.3.2.csproj	(revision 18027)
+++ branches/3026_IntegrationIntoSymSpace/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/SimSharp-3.3.2.csproj	(revision 18027)
@@ -0,0 +1,62 @@
+﻿<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>net461</TargetFrameworks>
+    <SignAssembly>True</SignAssembly>
+    <AssemblyOriginatorKeyFile>SimSharp.snk</AssemblyOriginatorKeyFile>
+    <DelaySign>False</DelaySign>
+    <AssemblyName>SimSharp-3.3.2</AssemblyName>
+    <RootNamespace>SimSharp</RootNamespace>
+    <Version>3.3.2</Version>
+    <Authors>Andreas Beham</Authors>
+    <Description>Sim# aims to port the concepts used in SimPy (https://pypi.python.org/pypi/simpy) to the .NET world. It is implemented in C# and builds on the .NET Framework 4.5 / .NET Standard 2.0. Sim# uses an efficient event queue (adapted from https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp). The MachineShop benchmark comes close to 3.5 million events per second on a Core i7-7 2.7Ghz.
+
+Sim# allows modeling processes easily and with little boiler plate code. A process is described as a method that yields events. When an event is yielded, the process waits on it. Processes are themselves events and so it is convenient to spawn sub-processes that can either be waited upon or that run next to each other. There is no need to inherit from classes or understand a complex object oriented design.</Description>
+    <Product>Sim#</Product>
+    <Company>HEAL, FH Upper Austria</Company>
+    <Copyright>Andreas Beham</Copyright>
+    <PackageProjectUrl>https://github.com/abeham/SimSharp</PackageProjectUrl>
+    <PackageReleaseNotes>Sim# 3.3.2 contains one enhancement and two bug fixes.
+    
+    Enhancement
+    1) Adds method to obtain samples to the ISampleMonitor interface.
+    
+    Bug fixes - it fixes two bugs in PseudoRealtimeEnvironment when using it in
+    Realtime mode with a scaling factor other than 1.
+    1) Getting the value of Now returned the elapsed time without scaling.
+    2) When the simulation is stopped, the already elapsed time was wrongly scaled.
+    </PackageReleaseNotes>
+    <NeutralLanguage />
+    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
+    <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
+    <PackageLicenseExpression>MIT</PackageLicenseExpression>
+    <IncludeSource>True</IncludeSource>
+    <IncludeSymbols>True</IncludeSymbols>
+    <NoWarn>$(NoWarn);1591;1734</NoWarn>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+  </PropertyGroup>
+  
+  <PropertyGroup Condition="'$(Configuration)'=='Release'">
+    <DefineConstants></DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\SimSharp.xml</DocumentationFile>
+    <RepositoryUrl>https://github.com/abeham/SimSharp.git</RepositoryUrl>
+    <RepositoryType>git</RepositoryType>
+    <PackageTags>discrete-event simulation</PackageTags>
+  </PropertyGroup>
+
+  <Target Name="CopyDocumentationFile" AfterTargets="ComputeFilesToPublish">
+    <ItemGroup>
+      <ResolvedFileToPublish Include="@(FinalDocFile)" RelativePath="@(FinalDocFile->'%(Filename)%(Extension)')" />
+    </ItemGroup>
+  </Target>
+
+  <ItemGroup>
+    <Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
+  </ItemGroup>
+</Project>
