Sim# (SimSharp) Discrete Event Simulation Framework

repository·master·Indexed 41 days ago

https://github.com/heal-research/simsharp

A high-performance, process-based discrete event simulation framework for .NET, ported from the Python SimPy library. It utilizes C# iterators (yield) for process modeling and supports .NET Standard 2.0 and .NET Framework 4.5. Features include a high-efficiency event queue, TimeSeriesMonitor for statistical tracking, and dual time APIs (TimeSpan-based and Double-based) for flexible temporal modeling.

Tokens
1.5K
Snippets
3
Records
6
Agent score
1%

What's inside SimSharp

  1. What is Sim# (SimSharp)?

    master

    Sim# (SimSharp) is a process-based discrete event simulation framework for .NET. It is a port and extension of the Python SimPy library.

    Key characteristics:

    • Process-based modeling: Processes are described as methods that yield events. When an event is yielded, the process waits.
    • High Performance: Uses an efficient event queue capable of processing millions of events quickly (e.g., 5 million events in < 1.5s on modern hardware).
    • Compatibility: Available via NuGet for .NET Framework 4.5 and is .NET Standard 2.0 compliant.
    • Low Boilerplate: Does not require complex object-oriented hierarchies; processes can be simple methods.
  2. Monitoring variables with `TimeSeriesMonitor`

    master

    Sim# includes monitoring capabilities to track statistical properties of variables (e.g., queue length, resource utilization).

    • Assignment: Monitors can be assigned to variables exposed by resources or used to track other variables.
    • Capabilities: Monitors maintain statistical properties like mean and standard deviation, and can print histograms.
    • Usage: When creating a monitor, set collect: true to enable data collection.

    Example assignment to a resource's QueueLength:

    var server = new Resource(env, capacity: 1) {
      QueueLength = new TimeSeriesMonitor(env, collect: true)
    };
  3. Handling process interruptions in Sim#

    master

    Unlike Python's SimPy, .NET does not allow injecting exceptions into an iterator via a try-except block around a yield statement. Therefore, if you are modeling a process that can be interrupted, you must manually check for faults.

    After every yield where an interruption might occur, call Environment.ActiveProcess.HandleFault() before continuing the logic.

    if (Environment.ActiveProcess.HandleFault()) {
        // Handle the interruption/fault here
    }
  4. Using the TimeSpan API vs the D-API (Double API)

    master

    Sim# provides two ways to handle time and delays:

    1. Standard API (TimeSpan-based): Uses DateTime for the simulation clock and TimeSpan for delays. This is the preferred API as it is explicit about time units.

      • env.Now returns DateTime.
      • env.Timeout(TimeSpan delay)
    2. D-API (Double API): Uses double values, similar to SimPy. This is useful for mathematical modeling but relies on a defaultStep if you want to use relative units.

      • env.NowD returns double.
      • env.TimeoutD(double delay)

    Configuring a default step

    You can initialize the Simulation environment with a defaultStep. When using the D-API, the double values will be interpreted in terms of this step.

    // Setting a default step of 1 minute
    var env = new Simulation(defaultStep: TimeSpan.FromMinutes(1));
    
    // Using D-API: 1 unit = 1 minute
    yield return env.TimeoutD(1);
    
    // Using Standard API: Explicitly 1 minute
    yield return env.Timeout(TimeSpan.FromMinutes(1));

    Warning on Distributions: When using distributions with the D-API, be careful. env.TimeoutD(new Exponential(2)) will use 2 units of the defaultStep, whereas env.Timeout(new Exponential(TimeSpan.FromMinutes(2))) will always use 2 minutes regardless of the defaultStep configuration.

  5. How to model processes in Sim#

    master

    In Sim#, a process is a method that returns an IEnumerable<Event>. You use the yield return keyword to trigger events like timeouts or resource requests. This allows you to model complex logic with minimal boilerplate.

    Example of an m/m/1 queuing system:

    using static SimSharp.Distributions;
    
    ExponentialTime ARRIVAL = EXP(TimeSpan.FromSeconds(...));
    ExponentialTime PROCESSING = EXP(TimeSpan.FromSeconds(...));
    TimeSpan SIMULATION_TIME = TimeSpan.FromHours(...);
    
    // A process that generates items
    IEnumerable<Event> MM1Q(Simulation env, Resource server) {
      while (true) {
        yield return env.Timeout(ARRIVAL);
        env.Process(Item(env, server));
      }
    }
    
    // A process representing an item moving through the system
    IEnumerable<Event> Item(Simulation env, Resource server) {
      using (var s = server.Request()) {
        yield return s; // Wait for resource
        yield return env.Timeout(PROCESSING); // Wait for processing time
        Console.WriteLine("Duration {0}", env.Now - s.Time);
      }
    }
    
    void RunSimulation() {
      var env = new Simulation(randomSeed: 42);
      var server = new Resource(env, capacity: 1) {
        QueueLength = new TimeSeriesMonitor(env, collect: true)
      };
      env.Process(MM1Q(env, server));
      env.Run(SIMULATION_TIME);
      Console.WriteLine(server.QueueLength.Summarize());
    }
    using static SimSharp.Distributions;
    
    ExponentialTime ARRIVAL = EXP(TimeSpan.FromSeconds(...));
    ExponentialTime PROCESSING = EXP(TimeSpan.FromSeconds(...));
    TimeSpan SIMULATION_TIME = TimeSpan.FromHours(...);
    
    IEnumerable<Event> MM1Q(Simulation env, Resource server) {
      while (true) {
        yield return env.Timeout(ARRIVAL);
        env.Process(Item(env, server));
      }
    }
    
    IEnumerable<Event> Item(Simulation env, Resource server) {
      using (var s = server.Request()) {
        yield return s;
        yield return env.Timeout(PROCESSING);
        Console.WriteLine("Duration {0}", env.Now - s.Time);
      }
    }
    
    void RunSimulation() {
      var env = new Simulation(randomSeed: 42);
      var server = new Resource(env, capacity: 1) {
        QueueLength = new TimeSeriesMonitor(env, collect: true)
      };
      env.Process(MM1Q(env, server));
      env.Run(SIMULATION_TIME);
      Console.WriteLine(server.QueueLength.Summarize());
    }
  6. Using distribution shortcuts via `SimSharp.Distributions`

    master

    To simplify code, you can use the static SimSharp.Distributions class. By adding using static SimSharp.Distributions; to your file, you can call distribution functions like EXP, UNIF, etc., directly without a class qualifier.

    using static SimSharp.Distributions;
    
    // ... inside a process
    yield return env.TimeoutD(UNIF(10, 20));
    using static SimSharp.Distributions;
    // ... additional code excluded
    yield return env.TimeoutD(UNIF(10, 20));