Skip to content

Commit a02e6d2

Browse files
authored
Add distribution classes (#39)
* Add Distribution classes and refactor Rand* methods * Add static class for easier creation of distributions - Adapt samples - Adapt documentation
1 parent 06e46f9 commit a02e6d2

24 files changed

+1440
-222
lines changed

README.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,34 @@ Sim# aims to port the concepts used in SimPy [1] to the .NET world. Sim# is impl
1616

1717
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.
1818

19-
To demonstrate how simple models can be expressed with little code, consider a model of an m/m/1 queuing system as expressed in Sim#:
19+
To demonstrate how simple models can be expressed with little code, consider a model of an m/m/1 queuing system as expressed in the current version of Sim#:
2020

2121
```csharp
22-
TimeSpan ARRIVAL_TIME = TimeSpan.FromSeconds(...);
23-
TimeSpan PROCESSING_TIME = TimeSpan.FromSeconds(...);
22+
using static SimSharp.Distributions;
23+
24+
ExponentialTime ARRIVAL = EXP(TimeSpan.FromSeconds(...));
25+
ExponentialTime PROCESSING = EXP(TimeSpan.FromSeconds(...));
2426
TimeSpan SIMULATION_TIME = TimeSpan.FromHours(...);
2527

2628
IEnumerable<Event> MM1Q(Simulation env, Resource server) {
2729
while (true) {
28-
yield return env.TimeoutExponential(ARRIVAL_TIME);
30+
yield return env.Timeout(ARRIVAL);
2931
env.Process(Item(env, server));
3032
}
3133
}
3234

3335
IEnumerable<Event> Item(Simulation env, Resource server) {
3436
using (var s = server.Request()) {
3537
yield return s;
36-
yield return env.TimeoutExponential(PROCESSING_TIME);
38+
yield return env.Timeout(PROCESSING);
3739
Console.WriteLine("Duration {0}", env.Now - s.Time);
3840
}
3941
}
4042

4143
void RunSimulation() {
4244
var env = new Simulation(randomSeed: 42);
4345
var server = new Resource(env, capacity: 1) {
44-
QueueLength = new TimeSeriesMonitor(env, collect: true)
46+
QueueLength = new TimeSeriesMonitor(env, collect: true)
4547
};
4648
env.Process(MM1Q(env, server));
4749
env.Run(SIMULATION_TIME);
@@ -65,7 +67,15 @@ Also in Sim# it was decided to base the unit for current time and delays on `Dat
6567
var env = new Simulation(defaultStep: TimeSpan.FromMinutes(1));
6668
```
6769

68-
In that environment, calling `env.TimeoutD(1)` would be equal to calling the more elaborate standard API `env.Timeout(TimeSpan.FromMinutes(1))`.
70+
In that environment, calling `env.TimeoutD(1)` would be equal to calling the more elaborate standard API `env.Timeout(TimeSpan.FromMinutes(1))`. In case timeouts are sampled from a distribution, it is important to distinguish the `TimeoutD(IDistribution<double>)`and `Timeout(IDistribution<TimeSpan>)` methods. Again, the former assumes the unit that is given in `defaultStep`, e.g., minutes as in the case above. For instance, `env.TimeoutD(new Exponential(2))` would indicate a mean of 2 minutes in the above environment, while `env.Timeout(new Exponential(TimeSpan.FromMinutes(2))` would always mean two minutes, regardless of the `defaultStep`. In generally, the `TimeSpan` API is preferred as it already expresses time in the appropriate units.
71+
72+
For shortcuts of the distribution classes a static class `Distributions` exists. You can put `using static SimSharp.Distributions;` in the using declarations and then use those methods without a qualifier. The following code snippet shows this feature.
73+
74+
```csharp
75+
using static SimSharp.Distributions;
76+
// ... additional code excluded
77+
yield return env.TimeoutD(UNIF(10, 20));
78+
```
6979

7080
## References
7181

docs/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The documentation covers the following aspects:
88
+ [Resources](#resources)
99
+ [Samples](#samples)
1010
+ [Environments](#environments)
11+
+ [Distributions](#distributions)
1112
* [Monitoring](#monitoring)
1213
+ [Reports](#reports)
1314

@@ -100,6 +101,31 @@ These methods are thread safe and may be called from different threads.
100101

101102
Obtaining the current simulation time using `Now` in realtime mode will account for the duration that the simulation is sleeping. Thus, calling this property from a different thread will always receive the actual simulation time, i.e., the time of the last event plus the elapsed time.
102103

104+
### Distributions
105+
Starting from Sim# 3.4, distributions are included as classes. This allows parameterizing processes with the actual distributions instead of having to predetermine the distribution within the process. For instance, a process that describes a customer arrival can now be specified and instantiated with different distribution types, e.g., exponential, triangular, and others. Creating a distribution is easy and cheap. For best readability, it is advised to include `SimSharp.Distributions` in the using section as a `using static`.
106+
107+
```csharp
108+
using SimSharp;
109+
using static SimSharp.Distributions;
110+
111+
namespace MyApplication {
112+
public class MyModel {
113+
IEnumerable<Event> CustomerArrival(Simulation env, IDistribution<TimeSpan> arrivalDist) {
114+
while (true) {
115+
yield return env.Timeout(arrivalDist);
116+
// Logic of creating a customer
117+
}
118+
}
119+
120+
public void Run() {
121+
var env = new Simulation();
122+
// Exponential distribution with a mean of 5
123+
env.Process(CustomerArrival(env, EXP(TimeSpan.FromMinutes(5))));
124+
env.Run(TimeSpan.FromHours(24));
125+
}
126+
}
127+
}
128+
103129
### Samples
104130

105131
Processes that interact with common resources may create highly dynamic behavior which may not be analytically tractable and thus have to be simulated. Sim# includes a number of samples that, while being easy to understand and simple, show how to model certain processes such as preemption, interruption, handover of resource requests and more. A short summary of the provided samples together with the highlights are given in the following:

src/.vscode/launch.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
"name": "Benchmark MachineShop",
99
"type": "coreclr",
1010
"request": "launch",
11-
"preLaunchTask": "build",
11+
"preLaunchTask": "releasebuild",
1212
// If you have changed target frameworks, make sure to update the program path.
13-
"program": "${workspaceFolder}/Benchmark/bin/Debug/netcoreapp2.0/Benchmark.dll",
13+
"program": "${workspaceFolder}/Benchmark/bin/Release/netcoreapp2.1/Benchmark.dll",
1414
"args": ["machineshop"],
1515
"cwd": "${workspaceFolder}/Benchmark",
1616
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
@@ -22,9 +22,9 @@
2222
"name": "Benchmark Synthetic",
2323
"type": "coreclr",
2424
"request": "launch",
25-
"preLaunchTask": "build",
25+
"preLaunchTask": "releasebuild",
2626
// If you have changed target frameworks, make sure to update the program path.
27-
"program": "${workspaceFolder}/Benchmark/bin/Debug/netcoreapp2.0/Benchmark.dll",
27+
"program": "${workspaceFolder}/Benchmark/bin/Release/netcoreapp2.1/Benchmark.dll",
2828
"args": ["synthetic", "--repetitions", "3", "--time", "60", "--cpufreq", "2.9"],
2929
"cwd": "${workspaceFolder}/Benchmark",
3030
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
@@ -38,7 +38,7 @@
3838
"request": "launch",
3939
"preLaunchTask": "build",
4040
// If you have changed target frameworks, make sure to update the program path.
41-
"program": "${workspaceFolder}/Samples/bin/Debug/netcoreapp2.0/Samples.dll",
41+
"program": "${workspaceFolder}/Samples/bin/Debug/netcoreapp2.1/Samples.dll",
4242
"args": [],
4343
"cwd": "${workspaceFolder}/Samples",
4444
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window

src/.vscode/tasks.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@
1717
"panel": "dedicated"
1818
}
1919
},
20+
{
21+
"label": "releasebuild",
22+
"command": "dotnet",
23+
"type": "process",
24+
"args": [
25+
"build",
26+
"-c",
27+
"Release",
28+
"${workspaceFolder}/SimSharp.sln"
29+
],
30+
"problemMatcher": "$msCompile",
31+
"presentation": {
32+
"echo": true,
33+
"reveal": "always",
34+
"focus": false,
35+
"panel": "dedicated"
36+
}
37+
},
2038
{
2139
"label": "test",
2240
"command": "dotnet",

src/Benchmark/Benchmark.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
<OutputType>Exe</OutputType>
55
<TargetFramework>netcoreapp2.1</TargetFramework>
66
<Authors>Andreas Beham</Authors>
7-
<Version>3.3</Version>
7+
<Version>3.4</Version>
88
<Company>HEAL, FH Upper Austria</Company>
99
<Product>Sim# (Benchmarks)</Product>
1010
<Description />
1111
<Copyright>Andreas Beham</Copyright>
12-
<PackageLicenseUrl>https://raw.githubusercontent.com/abeham/SimSharp/master/LICENSE.txt</PackageLicenseUrl>
13-
<PackageProjectUrl>https://github.com/abeham/SimSharp</PackageProjectUrl>
12+
<PackageLicenseUrl>https://raw.githubusercontent.com/heal-research/SimSharp/master/LICENSE.txt</PackageLicenseUrl>
13+
<PackageProjectUrl>https://github.com/heal-research/SimSharp</PackageProjectUrl>
1414
</PropertyGroup>
1515

1616
<ItemGroup>

src/Benchmark/MachineShopBenchmark.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System;
99
using System.Collections.Generic;
1010
using System.Linq;
11+
using static SimSharp.Distributions;
1112

1213
namespace SimSharp.Benchmarks {
1314
public class MachineShopBenchmark {
@@ -36,9 +37,8 @@ public static int Run(MachineShopOptions opts) {
3637
* with the machine repair. The workshop works continuously.
3738
*/
3839
private const int RandomSeed = 42;
39-
private const double PtMean = 10.0; // Avg. processing time in minutes
40-
private const double PtSigma = 2.0; // Sigma of processing time
41-
private const double Mttf = 300.0; // Mean time to failure in minutes
40+
private static readonly NormalTime ProcessingTime = N(TimeSpan.FromMinutes(10.0), TimeSpan.FromMinutes(2.0)); // Processing time distribution
41+
private static readonly ExponentialTime Failure = EXP(TimeSpan.FromMinutes(300.0)); // Failure distribution
4242
private const double RepairTime = 30.0; // Time it takes to repair a machine in minutes
4343
private const double JobDuration = 30.0; // Duration of other jobs in minutes
4444
private const int NumMachines = 10; // Number of machines in the machine shop
@@ -77,7 +77,7 @@ private IEnumerable<Event> Working(PreemptiveResource repairman) {
7777
*/
7878
while (true) {
7979
// Start making a new part
80-
var doneIn = TimeSpan.FromMinutes(Environment.RandNormal(PtMean, PtSigma));
80+
var doneIn = Environment.Rand(ProcessingTime);
8181
while (doneIn > TimeSpan.Zero) {
8282
// Working on the part
8383
var start = Environment.Now;
@@ -104,7 +104,7 @@ private IEnumerable<Event> Working(PreemptiveResource repairman) {
104104
private IEnumerable<Event> BreakMachine() {
105105
// Break the machine every now and then.
106106
while (true) {
107-
yield return Environment.Timeout(TimeSpan.FromMinutes(Environment.RandExponential(Mttf)));
107+
yield return Environment.Timeout(Failure);
108108
if (!Broken) {
109109
// Only break the machine if it is currently working.
110110
Process.Interrupt();

src/Benchmark/SyntheticBenchmark.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Collections.Generic;
1010
using System.Diagnostics;
1111
using System.Timers;
12+
using static SimSharp.Distributions;
1213

1314
namespace SimSharp.Benchmarks {
1415
public class SyntheticBenchmark {
@@ -106,8 +107,9 @@ static long Benchmark1(Simulation env, int n) {
106107
}
107108

108109
static IEnumerable<Event> Benchmark1Proc(Simulation env, int n) {
110+
var dist = UNIF(TimeSpan.Zero, TimeSpan.FromSeconds(2 * n));
109111
while (true) {
110-
yield return env.TimeoutUniform(TimeSpan.Zero, TimeSpan.FromSeconds(2 * n));
112+
yield return env.Timeout(dist);
111113
perf++;
112114
}
113115
}

src/Samples/BankRenege.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,31 @@
77

88
using System;
99
using System.Collections.Generic;
10+
using static SimSharp.Distributions;
1011

1112
namespace SimSharp.Samples {
1213
public class BankRenege {
1314

1415
private const int NewCustomers = 10; // Total number of customers
15-
private static readonly TimeSpan IntervalCustomers = TimeSpan.FromMinutes(10.0); // Generate new customers roughly every x minutes
16-
private static readonly TimeSpan MinPatience = TimeSpan.FromMinutes(1); // Min. customer patience
17-
private static readonly TimeSpan MaxPatience = TimeSpan.FromMinutes(3); // Max. customer patience
16+
private static readonly IDistribution<TimeSpan> Arrival = EXP(TimeSpan.FromMinutes(10.0)); // Generate new customers roughly every x minutes
17+
private static readonly IDistribution<TimeSpan> Patience = UNIF(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(3)); // Customer patience
1818

1919
private IEnumerable<Event> Source(Simulation env, Resource counter) {
2020
for (int i = 0; i < NewCustomers; i++) {
21-
var c = Customer(env, "Customer " + i, counter, TimeSpan.FromMinutes(12.0));
21+
var c = Customer(env, "Customer " + i, counter, EXP(TimeSpan.FromMinutes(12.0)));
2222
env.Process(c);
23-
yield return env.TimeoutExponential(IntervalCustomers);
23+
yield return env.Timeout(Arrival);
2424
}
2525
}
2626

27-
private IEnumerable<Event> Customer(Simulation env, string name, Resource counter, TimeSpan meanTimeInBank) {
27+
private IEnumerable<Event> Customer(Simulation env, string name, Resource counter, IDistribution<TimeSpan> meanTimeInBank) {
2828
var arrive = env.Now;
2929

3030
env.Log("{0} {1}: Here I am", arrive, name);
3131

3232
using (var req = counter.Request()) {
3333
// Wait for the counter or abort at the end of our tether
34-
var timeout = env.TimeoutUniform(MinPatience, MaxPatience);
34+
var timeout = env.Timeout(Patience);
3535
yield return req | timeout;
3636

3737
var wait = env.Now - arrive;
@@ -40,7 +40,7 @@ private IEnumerable<Event> Customer(Simulation env, string name, Resource counte
4040
// We got the counter
4141
env.Log("{0} {1}: waited {2}", env.Now, name, wait);
4242

43-
yield return env.TimeoutExponential(meanTimeInBank);
43+
yield return env.Timeout(meanTimeInBank);
4444
env.Log("{0} {1}: Finished", env.Now, name);
4545
} else {
4646
// We reneged

src/Samples/GasStationRefueling.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
using System;
99
using System.Collections.Generic;
10+
using static SimSharp.Distributions;
1011

1112
namespace SimSharp.Samples {
1213
public class GasStationRefueling {
@@ -31,12 +32,10 @@ public class GasStationRefueling {
3132
private const int GasStationSize = 200; // liters
3233
private const int Threshold = 10; // Threshold for calling the tank truck (in %)
3334
private const int FuelTankSize = 50; // liters
34-
private const int MinFuelTankLevel = 5; // Min levels of fuel tanks (in liters)
35-
private const int MaxFuelTankLevel = 25; // Max levels of fuel tanks (in liters)
3635
private const int RefuelingSpeed = 2; // liters / second
36+
private static readonly Uniform InitialFuelLevel = UNIF(5, 26); // Level of fuel tanks (in liters)
3737
private static readonly TimeSpan TankTruckTime = TimeSpan.FromMinutes(10); // Minutes it takes the tank truck to arrive
38-
private static readonly TimeSpan MinTInter = TimeSpan.FromMinutes(30); // Create a car every min seconds
39-
private static readonly TimeSpan MaxTInter = TimeSpan.FromMinutes(300); // Create a car every max seconds
38+
private static readonly UniformTime CarArrival = UNIF(TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(300)); // Arrival distribution for cars
4039
private static readonly TimeSpan SimTime = TimeSpan.FromMinutes(3000); // Simulation time
4140

4241
private IEnumerable<Event> Car(string name, Simulation env, Resource gasStation, Container fuelPump) {
@@ -47,7 +46,7 @@ private IEnumerable<Event> Car(string name, Simulation env, Resource gasStation,
4746
* desired amount of gas from it. If the stations reservoir is
4847
* depleted, the car has to wait for the tank truck to arrive.
4948
*/
50-
var fuelTankLevel = env.RandUniform(MinFuelTankLevel, MaxFuelTankLevel + 1);
49+
var fuelTankLevel = env.Rand(InitialFuelLevel);
5150
env.Log("{0} arriving at gas station at {1}", name, env.Now);
5251
using (var req = gasStation.Request()) {
5352
var start = env.Now;
@@ -98,7 +97,7 @@ private IEnumerable<Event> CarGenerator(Simulation env, Resource gasStation, Con
9897
var i = 0;
9998
while (true) {
10099
i++;
101-
yield return env.Timeout(env.RandUniform(MinTInter, MaxTInter));
100+
yield return env.Timeout(CarArrival);
102101
env.Process(Car("Car " + i, env, gasStation, fuelPump));
103102
}
104103
}

src/Samples/KanbanControl.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@
77

88
using System;
99
using System.Collections.Generic;
10+
using static SimSharp.Distributions;
1011

1112
namespace SimSharp.Samples {
1213
public class KanbanControl {
1314
private Simulation env;
1415
private Resource kanban;
1516
private Resource server;
1617
private TimeSeriesMonitor stockStat;
17-
private static readonly TimeSpan OrderArrivalTime = TimeSpan.FromMinutes(3.33);
18-
private static readonly TimeSpan ProcessingTime = TimeSpan.FromMinutes(2.5);
18+
private static readonly ExponentialTime OrderArrival = EXP(TimeSpan.FromMinutes(3.33));
19+
private static readonly ExponentialTime ProcessingTime = EXP(TimeSpan.FromMinutes(2.5));
1920
private int completedOrders;
2021

2122
private IEnumerable<Event> Source() {
2223
while (true) {
23-
yield return env.TimeoutExponential(OrderArrivalTime);
24+
yield return env.Timeout(OrderArrival);
2425
env.Process(Order());
2526
}
2627
}
@@ -36,7 +37,7 @@ private IEnumerable<Event> Order() {
3637
private IEnumerable<Event> Produce(Request kb) {
3738
using (var srv = server.Request()) {
3839
yield return srv;
39-
yield return env.TimeoutExponential(ProcessingTime);
40+
yield return env.Timeout(ProcessingTime);
4041
kanban.Release(kb);
4142
stockStat.UpdateTo(kanban.Remaining);
4243
}

0 commit comments

Comments
 (0)