Skip to content

Commit 7299100

Browse files
committed
Improve statistics calculation
- Add discrete statistics calculator (no time-based weighting) - Add Reset() method - Add auto-updating to current simulation time - Add unit tests - Add report class and updated a sample to make use of it
1 parent 5125b11 commit 7299100

File tree

11 files changed

+829
-135
lines changed

11 files changed

+829
-135
lines changed

Samples/KanbanControl.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ private IEnumerable<Event> Order() {
4040
var kb = kanban.Request();
4141
yield return kb;
4242
env.Process(Produce(kb));
43-
stockStat.Update(kanban.Remaining);
43+
stockStat.UpdateTo(kanban.Remaining);
4444
completedOrders++;
4545
}
4646

@@ -49,7 +49,7 @@ private IEnumerable<Event> Produce(Request kb) {
4949
yield return srv;
5050
yield return env.TimeoutExponential(ProcessingTime);
5151
kanban.Release(kb);
52-
stockStat.Update(kanban.Remaining);
52+
stockStat.UpdateTo(kanban.Remaining);
5353
}
5454
}
5555

Samples/MM1Queueing.cs

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ namespace SimSharp.Samples {
2323
public class MM1Queueing {
2424
private Simulation env;
2525
private Resource server;
26-
private ContinuousStatistics statistics;
26+
private ContinuousStatistics wip, utilization;
27+
private DiscreteStatistics waitingtime, leadtime;
2728
private static readonly TimeSpan OrderArrivalTime = TimeSpan.FromMinutes(3.33);
2829
private static readonly TimeSpan ProcessingTime = TimeSpan.FromMinutes(2.5);
29-
private int queueSize;
30-
30+
3131
private IEnumerable<Event> Source() {
3232
while (true) {
3333
yield return env.TimeoutExponential(OrderArrivalTime);
@@ -36,28 +36,76 @@ private IEnumerable<Event> Source() {
3636
}
3737

3838
private IEnumerable<Event> Order() {
39-
statistics.Update(++queueSize);
39+
var start = env.Now;
40+
wip.Increase();
4041
var req = server.Request();
4142
yield return req;
42-
env.Process(Produce(req));
43-
statistics.Update(--queueSize);
43+
utilization.UpdateTo(server.InUse / (double)server.Capacity);
44+
waitingtime.Add((env.Now - start).TotalMinutes);
45+
yield return env.Process(Produce(req));
46+
wip.Decrease();
47+
leadtime.Add((env.Now - start).TotalMinutes);
4448
}
4549

4650
private IEnumerable<Event> Produce(Request req) {
4751
yield return env.TimeoutExponential(ProcessingTime);
48-
server.Release(req);
52+
yield return server.Release(req);
53+
utilization.UpdateTo(server.InUse / (double)server.Capacity);
4954
}
5055

5156
public void Simulate() {
52-
queueSize = 0;
53-
env = new Simulation();
57+
var lambda = 1 / OrderArrivalTime.TotalMinutes;
58+
var mu = 1 / ProcessingTime.TotalMinutes;
59+
var rho = lambda / mu;
60+
var analyticWIP = rho / (1 - rho);
61+
var analyticLeadtime = 1 / (mu - lambda);
62+
var analyticWaitingtime = rho / (mu - lambda);
63+
64+
env = new Simulation(randomSeed: 1);
5465
server = new Resource(env, capacity: 1);
55-
statistics = new ContinuousStatistics(env);
56-
env.Log("== m/m/1 queuing system ==");
66+
wip = new ContinuousStatistics(env);
67+
utilization = new ContinuousStatistics(env);
68+
leadtime = new DiscreteStatistics();
69+
waitingtime = new DiscreteStatistics();
70+
71+
env.Log("Analytical results of this system:");
72+
env.Log("\tUtilization.Mean\tWIP.Mean\tLeadtime.Mean\tWaitingTime.Mean");
73+
env.Log("\t{0}\t{1}\t{2}\t{3}", rho, analyticWIP, analyticLeadtime, analyticWaitingtime);
74+
75+
// example to create a running report of these measures every simulated week
76+
//var report = Report.CreateBuilder(env)
77+
// .Add("Utilization", utilization, Report.Measures.Mean | Report.Measures.StdDev)
78+
// .Add("WIP", wip, Report.Measures.Min | Report.Measures.Mean | Report.Measures.Max)
79+
// .Add("Leadtime", leadtime, Report.Measures.Min | Report.Measures.Mean | Report.Measures.Max)
80+
// .Add("WaitingTime", waitingtime, Report.Measures.Min | Report.Measures.Mean | Report.Measures.Max)
81+
// .SetOutput(env.Logger) // use a "new StreamWriter("report.csv")" to direct to a file
82+
// .SetSeparator("\t")
83+
// .SetPeriodicUpdate(TimeSpan.FromDays(7), withHeaders: true)
84+
// .Build();
85+
86+
var summary = Report.CreateBuilder(env)
87+
.Add("Utilization", utilization, Report.Measures.Mean)
88+
.Add("WIP", wip, Report.Measures.Mean)
89+
.Add("Leadtime", leadtime, Report.Measures.Mean)
90+
.Add("WaitingTime", waitingtime, Report.Measures.Mean)
91+
.SetOutput(env.Logger)
92+
.SetSeparator("\t")
93+
.SetFinalUpdate(withHeaders: true) // creates a summary of the means at the end
94+
.Build();
95+
96+
env.Log("== m/m/1 queuing system (run 1) ==");
97+
env.Process(Source());
98+
env.Run(TimeSpan.FromDays(365));
99+
100+
env.Reset(2); // reset environment
101+
server = new Resource(env, capacity: 1); // reset resources
102+
wip.Reset(); // reset statistics
103+
utilization.Reset();
104+
leadtime.Reset();
105+
106+
env.Log("== m/m/1 queuing system (run 2) ==");
57107
env.Process(Source());
58-
env.Run(TimeSpan.FromDays(180));
59-
Console.WriteLine("QueueSize Statistics:");
60-
Console.WriteLine("Min: {0}; Max: {1}; Mean: {2:F2}; StdDev: {3:F2}", statistics.Min, statistics.Max, statistics.Mean, statistics.StdDev);
108+
env.Run(TimeSpan.FromDays(365));
61109
}
62110
}
63111
}

Samples/RunAllSamples.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@ namespace SimSharp.Samples {
2222
class RunAllSamples {
2323
public static void Main(string[] args) {
2424
// Run all samples one after another
25-
new BankRenege().Simulate();
26-
Console.WriteLine();
27-
new GasStationRefueling().Simulate();
28-
Console.WriteLine();
29-
new MachineShop().Simulate();
30-
Console.WriteLine();
31-
new ProcessCommunication().Simulate();
32-
Console.WriteLine();
33-
new SteelFactory().Simulate();
34-
Console.WriteLine();
35-
new MachineShopSpecialist().Simulate();
36-
Console.WriteLine();
37-
new SimpleShop().Simulate();
38-
Console.WriteLine();
39-
new KanbanControl().Simulate();
40-
Console.WriteLine();
25+
//new BankRenege().Simulate();
26+
//Console.WriteLine();
27+
//new GasStationRefueling().Simulate();
28+
//Console.WriteLine();
29+
//new MachineShop().Simulate();
30+
//Console.WriteLine();
31+
//new ProcessCommunication().Simulate();
32+
//Console.WriteLine();
33+
//new SteelFactory().Simulate();
34+
//Console.WriteLine();
35+
//new MachineShopSpecialist().Simulate();
36+
//Console.WriteLine();
37+
//new SimpleShop().Simulate();
38+
//Console.WriteLine();
39+
//new KanbanControl().Simulate();
40+
//Console.WriteLine();
4141
new MM1Queueing().Simulate();
4242
}
4343
}

SimSharp/Analysis/ContinuousStatistics.cs

Lines changed: 93 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,15 @@ You should have received a copy of the GNU General Public License
1919
using System;
2020

2121
namespace SimSharp {
22-
public sealed class ContinuousStatistics {
22+
/// <summary>
23+
/// This class calculates some descriptive statistics online without
24+
/// remembering all data. It takes into account the amount of time
25+
/// that has passed since the last update.
26+
///
27+
/// It can be used to calculate e.g. utilization of some resource or
28+
/// inventory levels.
29+
/// </summary>
30+
public sealed class ContinuousStatistics : IStatistics {
2331
private readonly Simulation env;
2432

2533
public int Count { get; private set; }
@@ -28,45 +36,113 @@ public sealed class ContinuousStatistics {
2836

2937
public double Min { get; private set; }
3038
public double Max { get; private set; }
31-
public double Area { get; private set; }
32-
public double Mean { get; private set; }
39+
public double Area {
40+
get {
41+
if (!UpToDate) OnlineUpdate();
42+
return area;
43+
}
44+
private set => area = value;
45+
}
46+
double IStatistics.Sum { get { return Area; } }
47+
public double Mean {
48+
get {
49+
if (!UpToDate) OnlineUpdate();
50+
return mean;
51+
}
52+
private set => mean = value;
53+
}
3354
public double StdDev { get { return Math.Sqrt(Variance); } }
34-
public double Variance { get { return (TotalTimeD > 0) ? variance / TotalTimeD : 0.0; } }
55+
public double Variance {
56+
get {
57+
if (!UpToDate) OnlineUpdate();
58+
return (TotalTimeD > 0) ? variance / TotalTimeD : 0.0;
59+
}
60+
}
61+
public double Current { get; private set; }
62+
double IStatistics.Last { get { return Current; } }
63+
64+
private bool UpToDate { get { return env.NowD == lastUpdateTime; } }
3565

3666
private double lastUpdateTime;
37-
private double lastValue;
3867
private double variance;
3968

4069
private bool firstSample;
41-
70+
private double area;
71+
private double mean;
4272

4373
public ContinuousStatistics(Simulation env) {
4474
this.env = env;
4575
lastUpdateTime = env.NowD;
4676
}
77+
public ContinuousStatistics(Simulation env, double initial) {
78+
this.env = env;
79+
lastUpdateTime = env.NowD;
80+
firstSample = true;
81+
Current = Min = Max = mean = initial;
82+
}
83+
84+
public void Reset() {
85+
Count = 0;
86+
TotalTimeD = 0;
87+
Current = Min = Max = area = mean = 0;
88+
variance = 0;
89+
firstSample = false;
90+
lastUpdateTime = env.NowD;
91+
}
92+
93+
public void Reset(double initial) {
94+
Count = 0;
95+
TotalTimeD = 0;
96+
Current = Min = Max = mean = initial;
97+
area = 0;
98+
variance = 0;
99+
firstSample = true;
100+
lastUpdateTime = env.NowD;
101+
}
47102

48-
public void Update(double value) {
103+
public void Increase(double value = 1) {
104+
UpdateTo(Current + value);
105+
}
106+
107+
public void Decrease(double value = 1) {
108+
UpdateTo(Current - value);
109+
}
110+
111+
[Obsolete("Use UpdateTo instead")]
112+
public void Update(double value) { UpdateTo(value); }
113+
public void UpdateTo(double value) {
49114
Count++;
50115

51116
if (!firstSample) {
52-
Min = Max = Mean = value;
117+
Min = Max = mean = value;
53118
firstSample = true;
119+
lastUpdateTime = env.NowD;
54120
} else {
55121
if (value < Min) Min = value;
56122
if (value > Max) Max = value;
57123

58-
var duration = env.NowD - lastUpdateTime;
59-
if (duration > 0) {
60-
Area += (lastValue * duration);
61-
var oldMean = Mean;
62-
Mean = oldMean + (lastValue - oldMean) * duration / (duration + TotalTimeD);
63-
variance = variance + (lastValue - oldMean) * (lastValue - Mean) * duration;
64-
TotalTimeD += duration;
65-
}
124+
OnlineUpdate();
66125
}
67126

127+
Current = value;
128+
OnUpdated();
129+
}
130+
131+
private void OnlineUpdate() {
132+
var duration = env.NowD - lastUpdateTime;
133+
if (duration > 0) {
134+
area += (Current * duration);
135+
var oldMean = mean;
136+
mean = oldMean + (Current - oldMean) * duration / (duration + TotalTimeD);
137+
variance = variance + (Current - oldMean) * (Current - mean) * duration;
138+
TotalTimeD += duration;
139+
}
68140
lastUpdateTime = env.NowD;
69-
lastValue = value;
141+
}
142+
143+
public event EventHandler Updated;
144+
private void OnUpdated() {
145+
Updated?.Invoke(this, EventArgs.Empty);
70146
}
71147
}
72148
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#region License Information
2+
/* SimSharp - A .NET port of SimPy, discrete event simulation framework
3+
Copyright (C) 2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.*/
17+
#endregion
18+
19+
using System;
20+
21+
namespace SimSharp {
22+
/// <summary>
23+
/// This class calculates some descriptive statistics online without
24+
/// remembering all data. All observed values are equally weighed.
25+
///
26+
/// It can be used to calculate e.g. lead times of processes.
27+
/// </summary>
28+
public sealed class DiscreteStatistics : IStatistics {
29+
public int Count { get; private set; }
30+
31+
public double Min { get; private set; }
32+
public double Max { get; private set; }
33+
public double Total { get; private set; }
34+
double IStatistics.Sum { get { return Total; } }
35+
public double Mean { get; private set; }
36+
public double StdDev { get { return Math.Sqrt(Variance); } }
37+
public double Variance { get { return (Count > 0) ? variance / Count : 0.0; } }
38+
private double variance;
39+
public double Last { get; private set; }
40+
41+
public DiscreteStatistics() {
42+
}
43+
44+
public void Reset() {
45+
Count = 0;
46+
Min = Max = Total = Mean = 0;
47+
variance = 0;
48+
Last = 0;
49+
}
50+
51+
public void Add(double value) {
52+
if (double.IsNaN(value) || double.IsInfinity(value))
53+
throw new ArgumentException("Not a valid double", "value");
54+
Count++;
55+
Total += value;
56+
Last = value;
57+
58+
if (Count == 1) {
59+
Min = Max = Mean = value;
60+
} else {
61+
if (value < Min) Min = value;
62+
if (value > Max) Max = value;
63+
64+
var oldMean = Mean;
65+
Mean = oldMean + (value - oldMean) / Count;
66+
variance = variance + (value - oldMean) * (value - Mean);
67+
}
68+
69+
OnUpdated();
70+
}
71+
72+
public event EventHandler Updated;
73+
private void OnUpdated() {
74+
Updated?.Invoke(this, EventArgs.Empty);
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)