source: trunk/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.1.1/SimSharp-3.1.1/Core/Environment.cs @ 16779

Last change on this file since 16779 was 16779, checked in by abeham, 10 months ago

#2975: Updated Sim# to 3.1.1

File size: 37.6 KB
Line 
1#region License Information
2/* SimSharp - A .NET port of SimPy, discrete event simulation framework
3Copyright (C) 2019  Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
17#endregion
18
19using System;
20using System.Collections.Generic;
21using System.IO;
22
23namespace SimSharp {
24  /// <summary>
25  /// Simulation hold the event queues, schedule and process events.
26  /// </summary>
27  /// <remarks>
28  /// This class is not thread-safe against manipulation of the event queue. If you supply a termination
29  /// event that is set outside the simulation, please use the <see cref="ThreadSafeSimulation"/> environment.
30  ///
31  /// For most purposes <see cref="Simulation"/> is however the better and faster choice.
32  /// </remarks>
33  public class Simulation {
34    private const int InitialMaxEvents = 1024;
35
36    /// <summary>
37    /// Describes the number of seconds that a logical step of 1 in the *D-API takes.
38    /// </summary>
39    protected double DefaultTimeStepSeconds { get; private set; }
40
41    /// <summary>
42    /// Calculates the logical date of the simulation by the amount of default steps
43    /// that have passed.
44    /// </summary>
45    public double NowD {
46      get { return (Now - StartDate).TotalSeconds / DefaultTimeStepSeconds; }
47    }
48
49    /// <summary>
50    /// The current simulation time as a calendar date.
51    /// </summary>
52    public DateTime Now { get; protected set; }
53
54    /// <summary>
55    /// The calendar date when the simulation started. This defaults to 1970-1-1 if
56    /// no other date has been specified in the overloaded constructor.
57    /// </summary>
58    public DateTime StartDate { get; protected set; }
59
60    /// <summary>
61    /// The random number generator that is to be used in all events in
62    /// order to produce reproducible results.
63    /// </summary>
64    protected IRandom Random { get; set; }
65
66    protected EventQueue ScheduleQ;
67    public Process ActiveProcess { get; set; }
68
69    public TextWriter Logger { get; set; }
70    public int ProcessedEvents { get; protected set; }
71
72    public Simulation() : this(new DateTime(1970, 1, 1)) { }
73    public Simulation(TimeSpan? defaultStep) : this(new DateTime(1970, 1, 1), defaultStep) { }
74    public Simulation(int randomSeed, TimeSpan? defaultStep = null) : this(new DateTime(1970, 1, 1), randomSeed, defaultStep) { }
75    public Simulation(DateTime initialDateTime, TimeSpan? defaultStep = null) : this(new PcgRandom(), initialDateTime, defaultStep) { }
76    public Simulation(DateTime initialDateTime, int randomSeed, TimeSpan? defaultStep = null) : this(new PcgRandom(randomSeed), initialDateTime, defaultStep) { }
77    public Simulation(IRandom random, DateTime initialDateTime, TimeSpan? defaultStep = null) {
78      DefaultTimeStepSeconds = (defaultStep ?? TimeSpan.FromSeconds(1)).Duration().TotalSeconds;
79      StartDate = initialDateTime;
80      Now = initialDateTime;
81      Random = random;
82      ScheduleQ = new EventQueue(InitialMaxEvents);
83      Logger = Console.Out;
84    }
85
86    public double ToDouble(TimeSpan span) {
87      return span.TotalSeconds / DefaultTimeStepSeconds;
88    }
89
90    public TimeSpan ToTimeSpan(double span) {
91      return TimeSpan.FromSeconds(DefaultTimeStepSeconds * span);
92    }
93
94    /// <summary>
95    /// Creates a new process from an event generator. The process is automatically
96    /// scheduled to be started at the current simulation time.
97    /// </summary>
98    /// <param name="generator">The generator function that represents the process.</param>
99    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
100    /// <returns>The scheduled process that was created.</returns>
101    public Process Process(IEnumerable<Event> generator, int priority = 0) {
102      return new Process(this, generator, priority);
103    }
104
105    /// <summary>
106    /// Creates and returns a new timeout.
107    /// </summary>
108    /// <param name="delay">The time after which the timeout is fired.</param>
109    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
110    /// <returns>The scheduled timeout event that was created.</returns>
111    public Timeout TimeoutD(double delay, int priority = 0) {
112      return Timeout(TimeSpan.FromSeconds(DefaultTimeStepSeconds * delay), priority);
113    }
114
115    /// <summary>
116    /// Creates and returns a new timeout.
117    /// </summary>
118    /// <param name="delay">The time after which the timeout is fired.</param>
119    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
120    /// <returns>The scheduled timeout event that was created.</returns>
121    public Timeout Timeout(TimeSpan delay, int priority = 0) {
122      return new Timeout(this, delay, priority: priority);
123    }
124
125    public virtual void Reset(int randomSeed) {
126      ProcessedEvents = 0;
127      Now = StartDate;
128      Random = new PcgRandom(randomSeed);
129      ScheduleQ = new EventQueue(InitialMaxEvents);
130      useSpareNormal = false;
131    }
132
133    public virtual void ScheduleD(double delay, Event @event) {
134      Schedule(TimeSpan.FromSeconds(DefaultTimeStepSeconds * delay), @event);
135    }
136
137    /// <summary>
138    /// Schedules an event to occur at the same simulation time as the call was made.
139    /// </summary>
140    /// <param name="event">The event that should be scheduled.</param>
141    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
142    public virtual void Schedule(Event @event, int priority = 0) {
143      DoSchedule(Now, @event, priority);
144    }
145
146    /// <summary>
147    /// Schedules an event to occur after a certain (positive) delay.
148    /// </summary>
149    /// <exception cref="ArgumentException">
150    /// Thrown when <paramref name="delay"/> is negative.
151    /// </exception>
152    /// <param name="delay">The (positive) delay after which the event should be fired.</param>
153    /// <param name="event">The event that should be scheduled.</param>
154    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
155    public virtual void Schedule(TimeSpan delay, Event @event, int priority = 0) {
156      if (delay < TimeSpan.Zero)
157        throw new ArgumentException("Negative delays are not allowed in Schedule(TimeSpan, Event).");
158      var eventTime = Now + delay;
159      DoSchedule(eventTime, @event, priority);
160    }
161
162    protected virtual EventQueueNode DoSchedule(DateTime date, Event @event, int priority = 0) {
163      if (ScheduleQ.MaxSize == ScheduleQ.Count) {
164        // the capacity has to be adjusted, there are more events in the queue than anticipated
165        var oldSchedule = ScheduleQ;
166        ScheduleQ = new EventQueue(ScheduleQ.MaxSize * 2);
167        foreach (var e in oldSchedule) ScheduleQ.Enqueue(e.PrimaryPriority, e.Event, e.SecondaryPriority);
168      }
169      return ScheduleQ.Enqueue(date, @event, priority);
170    }
171
172    public virtual object RunD(double? until = null) {
173      if (!until.HasValue) return Run();
174      return Run(Now + TimeSpan.FromSeconds(DefaultTimeStepSeconds * until.Value));
175    }
176
177    public virtual object Run(TimeSpan span) {
178      return Run(Now + span);
179    }
180
181    public virtual object Run(DateTime until) {
182      if (until <= Now) throw new InvalidOperationException("Simulation end date must lie in the future.");
183      var stopEvent = new Event(this);
184      var node = DoSchedule(until, stopEvent);
185      // stop event is always the first to execute at the given time
186      node.InsertionIndex = -1;
187      ScheduleQ.OnNodeUpdated(node);
188      return Run(stopEvent);
189    }
190
191    protected bool _stopRequested = false;
192    /// <summary>
193    /// Run until a certain event is processed.
194    /// </summary>
195    /// <remarks>
196    /// This simulation environment is not thread-safe, thus triggering this event outside the environment
197    /// leads to potential race conditions. Please use the <see cref="ThreadSafeSimulation"/> environment in case you
198    /// require this functionality. Note that the performance of <see cref="ThreadSafeSimulation"/> is lower due to locking.
199    ///
200    /// For real-time based termination, you can also call <see cref="StopAsync"/> which sets a flag indicating the simulation
201    /// to stop before processing the next event.
202    /// </remarks>
203    /// <param name="stopEvent">The event that stops the simulation.</param>
204    /// <returns></returns>
205    public virtual object Run(Event stopEvent = null) {
206      _stopRequested = false;
207      if (stopEvent != null) {
208        if (stopEvent.IsProcessed) return stopEvent.Value;
209        stopEvent.AddCallback(StopSimulation);
210      }
211
212      try {
213        var stop = ScheduleQ.Count == 0 || _stopRequested;
214        while (!stop) {
215          Step();
216          ProcessedEvents++;
217          stop = ScheduleQ.Count == 0 || _stopRequested;
218        }
219      } catch (StopSimulationException e) { return e.Value; }
220      if (stopEvent == null) return null;
221      if (!stopEvent.IsTriggered) throw new InvalidOperationException("No scheduled events left but \"until\" event was not triggered.");
222      return stopEvent.Value;
223    }
224
225    public virtual void StopAsync() {
226      _stopRequested = true;
227    }
228
229    /// <summary>
230    /// Performs a single step of the simulation, i.e. process a single event
231    /// </summary>
232    /// <remarks>
233    /// This method is not thread-safe
234    /// </remarks>
235    public virtual void Step() {
236      Event evt;
237      var next = ScheduleQ.Dequeue();
238      Now = next.PrimaryPriority;
239      evt = next.Event;
240      evt.Process();
241    }
242
243    /// <summary>
244    /// Peeks at the time of the next event in terms of the defined step
245    /// </summary>
246    /// <remarks>
247    /// This method is not thread-safe
248    /// </remarks>
249    public virtual double PeekD() {
250      if (ScheduleQ.Count == 0) return double.MaxValue;
251      return (Peek() - StartDate).TotalSeconds / DefaultTimeStepSeconds;
252    }
253
254    /// <summary>
255    /// Peeks at the time of the next event
256    /// </summary>
257    /// <remarks>
258    /// This method is not thread-safe
259    /// </remarks>
260    public virtual DateTime Peek() {
261      return ScheduleQ.Count > 0 ? ScheduleQ.First.PrimaryPriority : DateTime.MaxValue;
262    }
263
264    protected virtual void StopSimulation(Event @event) {
265      throw new StopSimulationException(@event.Value);
266    }
267
268    public virtual void Log(string message, params object[] args) {
269      if (Logger != null)
270        Logger.WriteLine(message, args);
271    }
272
273    #region Random number distributions
274    public double RandUniform(IRandom random, double a, double b) {
275      return a + (b - a) * random.NextDouble();
276    }
277    public double RandUniform(double a, double b) {
278      return RandUniform(Random, a, b);
279    }
280
281    public TimeSpan RandUniform(IRandom random, TimeSpan a, TimeSpan b) {
282      return TimeSpan.FromSeconds(RandUniform(random, a.TotalSeconds, b.TotalSeconds));
283    }
284    public TimeSpan RandUniform(TimeSpan a, TimeSpan b) {
285      return RandUniform(Random, a, b);
286    }
287    public double RandTriangular(IRandom random, double low, double high) {
288      var u = random.NextDouble();
289      if (u > 0.5)
290        return high + (low - high) * Math.Sqrt(((1.0 - u) / 2));
291      return low + (high - low) * Math.Sqrt(u / 2);
292    }
293    public double RandTriangular(double low, double high) {
294      return RandTriangular(Random, low, high);
295    }
296
297    public TimeSpan RandTriangular(IRandom random, TimeSpan low, TimeSpan high) {
298      return TimeSpan.FromSeconds(RandTriangular(random, low.TotalSeconds, high.TotalSeconds));
299    }
300    public TimeSpan RandTriangular(TimeSpan low, TimeSpan high) {
301      return RandTriangular(Random, low, high);
302    }
303
304    public double RandTriangular(IRandom random, double low, double high, double mode) {
305      var u = random.NextDouble();
306      var c = (mode - low) / (high - low);
307      if (u > c)
308        return high + (low - high) * Math.Sqrt(((1.0 - u) * (1.0 - c)));
309      return low + (high - low) * Math.Sqrt(u * c);
310    }
311    public double RandTriangular(double low, double high, double mode) {
312      return RandTriangular(Random, low, high, mode);
313    }
314
315    public TimeSpan RandTriangular(IRandom random, TimeSpan low, TimeSpan high, TimeSpan mode) {
316      return TimeSpan.FromSeconds(RandTriangular(random, low.TotalSeconds, high.TotalSeconds, mode.TotalSeconds));
317    }
318    public TimeSpan RandTriangular(TimeSpan low, TimeSpan high, TimeSpan mode) {
319      return RandTriangular(Random, low, high, mode);
320    }
321
322    /// <summary>
323    /// Returns a number that is exponentially distributed given a certain mean.
324    /// </summary>
325    /// <remarks>
326    /// Unlike in other APIs here the mean should be given and not the lambda parameter.
327    /// </remarks>
328    /// <param name="random">The random number generator to use.</param>
329    /// <param name="mean">The mean(!) of the distribution is 1 / lambda.</param>
330    /// <returns>A number that is exponentially distributed</returns>
331    public double RandExponential(IRandom random, double mean) {
332      return -Math.Log(1 - random.NextDouble()) * mean;
333    }
334    /// <summary>
335    /// Returns a number that is exponentially distributed given a certain mean.
336    /// </summary>
337    /// <remarks>
338    /// Unlike in other APIs here the mean should be given and not the lambda parameter.
339    /// </remarks>
340    /// <param name="mean">The mean(!) of the distribution is 1 / lambda.</param>
341    /// <returns>A number that is exponentially distributed</returns>
342    public double RandExponential(double mean) {
343      return RandExponential(Random, mean);
344    }
345
346    /// <summary>
347    /// Returns a timespan that is exponentially distributed given a certain mean.
348    /// </summary>
349    /// <remarks>
350    /// Unlike in other APIs here the mean should be given and not the lambda parameter.
351    /// </remarks>
352    /// <param name="random">The random number generator to use.</param>
353    /// <param name="mean">The mean(!) of the distribution is 1 / lambda.</param>
354    /// <returns>A number that is exponentially distributed</returns>
355    public TimeSpan RandExponential(IRandom random, TimeSpan mean) {
356      return TimeSpan.FromSeconds(RandExponential(random, mean.TotalSeconds));
357    }
358    /// <summary>
359    /// Returns a timespan that is exponentially distributed given a certain mean.
360    /// </summary>
361    /// <remarks>
362    /// Unlike in other APIs here the mean should be given and not the lambda parameter.
363    /// </remarks>
364    /// <param name="mean">The mean(!) of the distribution is 1 / lambda.</param>
365    /// <returns>A number that is exponentially distributed</returns>
366    public TimeSpan RandExponential(TimeSpan mean) {
367      return RandExponential(Random, mean);
368    }
369
370    private bool useSpareNormal = false;
371    private double spareNormal = double.NaN;
372    /// <summary>
373    /// Uses the Marsaglia polar method to generate a random variable
374    /// from two uniform random distributed values.
375    /// </summary>
376    /// <remarks>
377    /// A spare random variable is generated from the second uniformly
378    /// distributed value. Thus, the two calls to the uniform random number
379    /// generator will be made only every second call.
380    /// </remarks>
381    /// <param name="random">The random number generator to use.</param>
382    /// <param name="mu">The mean of the normal distribution.</param>
383    /// <param name="sigma">The standard deviation of the normal distribution.</param>
384    /// <returns>A number that is normal distributed.</returns>
385    public virtual double RandNormal(IRandom random, double mu, double sigma) {
386      if (useSpareNormal) {
387        useSpareNormal = false;
388        return spareNormal * sigma + mu;
389      } else {
390        double u, v, s;
391        do {
392          u = random.NextDouble() * 2 - 1;
393          v = random.NextDouble() * 2 - 1;
394          s = u * u + v * v;
395        } while (s >= 1 || s == 0);
396        var mul = Math.Sqrt(-2.0 * Math.Log(s) / s);
397        spareNormal = v * mul;
398        useSpareNormal = true;
399        return mu + sigma * u * mul;
400      }
401    }
402    /// <summary>
403    /// Uses the Marsaglia polar method to generate a random variable
404    /// from two uniform random distributed values.
405    /// </summary>
406    /// <remarks>
407    /// A spare random variable is generated from the second uniformly
408    /// distributed value. Thus, the two calls to the uniform random number
409    /// generator will be made only every second call.
410    /// </remarks>
411    /// <param name="mu">The mean of the normal distribution.</param>
412    /// <param name="sigma">The standard deviation of the normal distribution.</param>
413    /// <returns>A number that is normal distributed.</returns>
414    public double RandNormal(double mu, double sigma) {
415      return RandNormal(Random, mu, sigma);
416    }
417
418    /// <summary>
419    /// Uses the Marsaglia polar method to generate a random variable
420    /// from two uniform random distributed values.
421    /// </summary>
422    /// <remarks>
423    /// A spare random variable is generated from the second uniformly
424    /// distributed value. Thus, the two calls to the uniform random number
425    /// generator will be made only every second call.
426    /// </remarks>
427    /// <param name="random">The random number generator to use.</param>
428    /// <param name="mu">The mean of the normal distribution.</param>
429    /// <param name="sigma">The standard deviation of the normal distribution.</param>
430    /// <returns>A number that is normal distributed.</returns>
431    public TimeSpan RandNormal(IRandom random, TimeSpan mu, TimeSpan sigma) {
432      return TimeSpan.FromSeconds(RandNormal(random, mu.TotalSeconds, sigma.TotalSeconds));
433    }
434    /// <summary>
435    /// Uses the Marsaglia polar method to generate a random variable
436    /// from two uniform random distributed values.
437    /// </summary>
438    /// <remarks>
439    /// A spare random variable is generated from the second uniformly
440    /// distributed value. Thus, the two calls to the uniform random number
441    /// generator will be made only every second call.
442    /// </remarks>
443    /// <param name="mu">The mean of the normal distribution.</param>
444    /// <param name="sigma">The standard deviation of the normal distribution.</param>
445    /// <returns>A number that is normal distributed.</returns>
446    public TimeSpan RandNormal(TimeSpan mu, TimeSpan sigma) {
447      return RandNormal(Random, mu, sigma);
448    }
449
450    public double RandNormalPositive(IRandom random, double mu, double sigma) {
451      double val;
452      do {
453        val = RandNormal(random, mu, sigma);
454      } while (val <= 0);
455      return val;
456    }
457    public double RandNormalPositive(double mu, double sigma) {
458      return RandNormalPositive(Random, mu, sigma);
459    }
460
461    public TimeSpan RandNormalPositive(IRandom random, TimeSpan mu, TimeSpan sigma) {
462      return TimeSpan.FromSeconds(RandNormalPositive(random, mu.TotalSeconds, sigma.TotalSeconds));
463    }
464    public TimeSpan RandNormalPositive(TimeSpan mu, TimeSpan sigma) {
465      return RandNormalPositive(Random, mu, sigma);
466    }
467
468    public double RandNormalNegative(IRandom random, double mu, double sigma) {
469      double val;
470      do {
471        val = RandNormal(random, mu, sigma);
472      } while (val >= 0);
473      return val;
474    }
475    public double RandNormalNegative(double mu, double sigma) {
476      return RandNormalNegative(Random, mu, sigma);
477    }
478
479    public TimeSpan RandNormalNegative(IRandom random, TimeSpan mu, TimeSpan sigma) {
480      return TimeSpan.FromSeconds(RandNormalNegative(random, mu.TotalSeconds, sigma.TotalSeconds));
481    }
482    public TimeSpan RandNormalNegative(TimeSpan mu, TimeSpan sigma) {
483      return RandNormalNegative(Random, mu, sigma);
484    }
485
486    /// <summary>
487    /// Returns values from a log-normal distribution with the mean
488    /// exp(mu + sigma^2 / 2)
489    /// and the standard deviation
490    /// sqrt([exp(sigma^2)-1] * exp(2 * mu + sigma^2))
491    /// </summary>
492    /// <param name="random">The random number generator to use.</param>
493    /// <param name="mu">The mu parameter of the log-normal distribution (not the mean).</param>
494    /// <param name="sigma">The sigma parameter of the log-normal distribution (not the standard deviation).</param>
495    /// <returns>A log-normal distributed random value.</returns>
496    public double RandLogNormal(IRandom random, double mu, double sigma) {
497      return Math.Exp(RandNormal(random, mu, sigma));
498    }
499    /// <summary>
500    /// Returns values from a log-normal distribution with the mean
501    /// exp(mu + sigma^2 / 2)
502    /// and the standard deviation
503    /// sqrt([exp(sigma^2)-1] * exp(2 * mu + sigma^2))
504    /// </summary>
505    /// <param name="mu">The mu parameter of the log-normal distribution (not the mean).</param>
506    /// <param name="sigma">The sigma parameter of the log-normal distribution (not the standard deviation).</param>
507    /// <returns>A log-normal distributed random value.</returns>
508    public double RandLogNormal(double mu, double sigma) {
509      return RandLogNormal(Random, mu, sigma);
510    }
511
512    /// <summary>
513    /// Returns values from a log-normal distribution with
514    /// the mean <paramref name="mean"/> and standard deviation <paramref name="stdev"/>.
515    /// </summary>
516    /// <param name="random">The random number generator to use.</param>
517    /// <param name="mean">The distribution mean.</param>
518    /// <param name="stdev">The distribution standard deviation.</param>
519    /// <returns>A log-normal distributed random value.</returns>
520    public double RandLogNormal2(IRandom random, double mean, double stdev) {
521      if (stdev == 0) return mean;
522      var alpha = Math.Sqrt(mean * stdev) / mean;
523      var sigma = Math.Sqrt(Math.Log(1 + (alpha * alpha)));
524      var mu = Math.Log(mean) - 0.5 * sigma * sigma;
525      return Math.Exp(RandNormal(random, mu, sigma));
526    }
527    /// <summary>
528    /// Returns values from a log-normal distribution with
529    /// the mean <paramref name="mean"/> and standard deviation <paramref name="stdev"/>.
530    /// </summary>
531    /// <param name="mean">The distribution mean.</param>
532    /// <param name="stdev">The distribution standard deviation.</param>
533    /// <returns>A log-normal distributed random value.</returns>
534    public double RandLogNormal2(double mean, double stdev) {
535      return RandLogNormal2(Random, mean, stdev);
536    }
537
538    /// <summary>
539    /// Returns a timespan value from a log-normal distribution with the mean
540    /// exp(mu + sigma^2 / 2)
541    /// and the standard deviation
542    /// sqrt([exp(sigma^2)-1] * exp(2 * mu + sigma^2))
543    /// </summary>
544    /// <param name="random">The random number generator to use.</param>
545    /// <param name="mu">The mu parameter of the log-normal distribution (not the mean).</param>
546    /// <param name="sigma">The sigma parameter of the log-normal distribution (not the standard deviation).</param>
547    /// <returns>A log-normal distributed random timespan.</returns>
548    public TimeSpan RandLogNormal(IRandom random, TimeSpan mu, TimeSpan sigma) {
549      return TimeSpan.FromSeconds(RandLogNormal(random, mu.TotalSeconds, sigma.TotalSeconds));
550    }
551    /// <summary>
552    /// Returns a timespan value from a log-normal distribution with the mean
553    /// exp(mu + sigma^2 / 2)
554    /// and the standard deviation
555    /// sqrt([exp(sigma^2)-1] * exp(2 * mu + sigma^2))
556    /// </summary>
557    /// <param name="mu">The mu parameter of the log-normal distribution (not the mean).</param>
558    /// <param name="sigma">The sigma parameter of the log-normal distribution (not the standard deviation).</param>
559    /// <returns>A log-normal distributed random timespan.</returns>
560    public TimeSpan RandLogNormal(TimeSpan mu, TimeSpan sigma) {
561      return RandLogNormal(Random, mu, sigma);
562    }
563
564    /// <summary>
565    /// Returns a timespan value from a log-normal distribution with
566    /// the mean <paramref name="mean"/> and standard deviation <paramref name="stdev"/>.
567    /// </summary>
568    /// <param name="random">The random number generator to use.</param>
569    /// <param name="mean">The distribution mean.</param>
570    /// <param name="stdev">The distribution standard deviation.</param>
571    /// <returns>A log-normal distributed random timespan.</returns>
572    public TimeSpan RandLogNormal2(IRandom random, TimeSpan mean, TimeSpan stdev) {
573      return TimeSpan.FromSeconds(RandLogNormal2(random, mean.TotalSeconds, stdev.TotalSeconds));
574    }
575    /// <summary>
576    /// Returns a timespan value from a log-normal distribution with
577    /// the mean <paramref name="mean"/> and standard deviation <paramref name="stdev"/>.
578    /// </summary>
579    /// <param name="mean">The distribution mean.</param>
580    /// <param name="stdev">The distribution standard deviation.</param>
581    /// <returns>A log-normal distributed random timespan.</returns>
582    public TimeSpan RandLogNormal2(TimeSpan mean, TimeSpan stdev) {
583      return RandLogNormal2(Random, mean, stdev);
584    }
585
586    public double RandCauchy(IRandom random, double x0, double gamma) {
587      return x0 + gamma * Math.Tan(Math.PI * (random.NextDouble() - 0.5));
588    }
589    public double RandCauchy(double x0, double gamma) {
590      return RandCauchy(Random, x0, gamma);
591    }
592
593    public TimeSpan RandCauchy(IRandom random, TimeSpan x0, TimeSpan gamma) {
594      return TimeSpan.FromSeconds(RandCauchy(random, x0.TotalSeconds, gamma.TotalSeconds));
595    }
596    public TimeSpan RandCauchy(TimeSpan x0, TimeSpan gamma) {
597      return RandCauchy(Random, x0, gamma);
598    }
599
600    public double RandWeibull(IRandom random, double alpha, double beta) {
601      return alpha * Math.Pow(-Math.Log(1 - random.NextDouble()), 1 / beta);
602    }
603    public double RandWeibull(double alpha, double beta) {
604      return RandWeibull(Random, alpha, beta);
605    }
606
607    public TimeSpan RandWeibull(IRandom random, TimeSpan alpha, TimeSpan beta) {
608      return TimeSpan.FromSeconds(RandWeibull(random, alpha.TotalSeconds, beta.TotalSeconds));
609    }
610    public TimeSpan RandWeibull(TimeSpan alpha, TimeSpan beta) {
611      return RandWeibull(Random, alpha, beta);
612    }
613    #endregion
614
615    #region Random timeouts
616    public Timeout TimeoutUniformD(IRandom random, double a, double b) {
617      return new Timeout(this, ToTimeSpan(RandUniform(random, a, b)));
618    }
619    public Timeout TimeoutUniformD(double a, double b) {
620      return TimeoutUniformD(Random, a, b);
621    }
622
623    public Timeout TimeoutUniform(IRandom random, TimeSpan a, TimeSpan b) {
624      return new Timeout(this, RandUniform(random, a, b));
625    }
626    public Timeout TimeoutUniform(TimeSpan a, TimeSpan b) {
627      return TimeoutUniform(Random, a, b);
628    }
629
630    public Timeout TimeoutTriangularD(IRandom random, double low, double high) {
631      return new Timeout(this, ToTimeSpan(RandTriangular(random, low, high)));
632    }
633    public Timeout TimeoutTriangularD(double low, double high) {
634      return TimeoutTriangularD(Random, low, high);
635    }
636
637    public Timeout TimeoutTriangular(IRandom random, TimeSpan low, TimeSpan high) {
638      return new Timeout(this, RandTriangular(random, low, high));
639    }
640    public Timeout TimeoutTriangular(TimeSpan low, TimeSpan high) {
641      return TimeoutTriangular(Random, low, high);
642    }
643
644    public Timeout TimeoutTriangularD(IRandom random, double low, double high, double mode) {
645      return new Timeout(this, ToTimeSpan(RandTriangular(random, low, high, mode)));
646    }
647    public Timeout TimeoutTriangularD(double low, double high, double mode) {
648      return TimeoutTriangularD(Random, low, high, mode);
649    }
650
651    public Timeout TimeoutTriangular(IRandom random, TimeSpan low, TimeSpan high, TimeSpan mode) {
652      return new Timeout(this, RandTriangular(random, low, high, mode));
653    }
654    public Timeout TimeoutTriangular(TimeSpan low, TimeSpan high, TimeSpan mode) {
655      return TimeoutTriangular(Random, low, high, mode);
656    }
657
658    public Timeout TimeoutExponentialD(IRandom random, double mean) {
659      return new Timeout(this, ToTimeSpan(RandExponential(random, mean)));
660    }
661    public Timeout TimeoutExponentialD(double mean) {
662      return TimeoutExponentialD(Random, mean);
663    }
664
665    public Timeout TimeoutExponential(IRandom random, TimeSpan mean) {
666      return new Timeout(this, RandExponential(random, mean));
667    }
668    public Timeout TimeoutExponential(TimeSpan mean) {
669      return TimeoutExponential(Random, mean);
670    }
671
672    public Timeout TimeoutNormalPositiveD(IRandom random, double mu, double sigma) {
673      return new Timeout(this, ToTimeSpan(RandNormalPositive(random, mu, sigma)));
674    }
675    public Timeout TimeoutNormalPositiveD(double mu, double sigma) {
676      return TimeoutNormalPositiveD(Random, mu, sigma);
677    }
678
679    public Timeout TimeoutNormalPositive(IRandom random, TimeSpan mu, TimeSpan sigma) {
680      return new Timeout(this, RandNormalPositive(random, mu, sigma));
681    }
682    public Timeout TimeoutNormalPositive(TimeSpan mu, TimeSpan sigma) {
683      return TimeoutNormalPositive(Random, mu, sigma);
684    }
685
686    public Timeout TimeoutLogNormalD(IRandom random, double mu, double sigma) {
687      return new Timeout(this, ToTimeSpan(RandLogNormal(random, mu, sigma)));
688    }
689    public Timeout TimeoutLogNormalD(double mu, double sigma) {
690      return TimeoutLogNormalD(Random, mu, sigma);
691    }
692
693    public Timeout TimeoutLogNormal2D(IRandom random, double mean, double stdev) {
694      return new Timeout(this, ToTimeSpan(RandLogNormal2(random, mean, stdev)));
695    }
696    public Timeout TimeoutLogNormal2D(double mean, double stdev) {
697      return TimeoutLogNormal2D(Random, mean, stdev);
698    }
699
700    public Timeout TimeoutLogNormal(IRandom random, TimeSpan mu, TimeSpan sigma) {
701      return new Timeout(this, RandLogNormal(random, mu, sigma));
702    }
703    public Timeout TimeoutLogNormal(TimeSpan mu, TimeSpan sigma) {
704      return TimeoutLogNormal(Random, mu, sigma);
705    }
706
707    public Timeout TimeoutLogNormal2(IRandom random, TimeSpan mean, TimeSpan stdev) {
708      return new Timeout(this, RandLogNormal2(random, mean, stdev));
709    }
710    public Timeout TimeoutLogNormal2(TimeSpan mean, TimeSpan stdev) {
711      return TimeoutLogNormal2(Random, mean, stdev);
712    }
713    #endregion
714  }
715
716  /// <summary>
717  /// Provides a simulation environment that is thread-safe against manipulations of the event queue.
718  /// Its performance is somewhat lower than the non-thread-safe environment (cf. <see cref="Simulation"/>)
719  /// due to the locking involved.
720  /// </summary>
721  /// <remarks>
722  /// Please carefully consider if you must really schedule the stop event in a separate thread. You can also
723  /// call <see cref="Simulation.StopAsync"/> to request termination after the current event has been processed.
724  ///
725  /// The simulation will still run in only one thread and execute all events sequentially.
726  /// </remarks>
727  public class ThreadSafeSimulation : Simulation {
728    protected object _locker;
729
730    public ThreadSafeSimulation() : this(new DateTime(1970, 1, 1)) { }
731    public ThreadSafeSimulation(TimeSpan? defaultStep) : this(new DateTime(1970, 1, 1), defaultStep) { }
732    public ThreadSafeSimulation(DateTime initialDateTime, TimeSpan? defaultStep = null) : this(new PcgRandom(), initialDateTime, defaultStep) { }
733    public ThreadSafeSimulation(int randomSeed, TimeSpan? defaultStep = null) : this(new DateTime(1970, 1, 1), randomSeed, defaultStep) { }
734    public ThreadSafeSimulation(DateTime initialDateTime, int randomSeed, TimeSpan? defaultStep = null) : this(new PcgRandom(randomSeed), initialDateTime, defaultStep) { }
735    public ThreadSafeSimulation(IRandom random, DateTime initialDateTime, TimeSpan? defaultStep = null) : base(random, initialDateTime, defaultStep) {
736      _locker = new object();
737    }
738
739
740    /// <summary>
741    /// Schedules an event to occur at the same simulation time as the call was made.
742    /// </summary>
743    /// <remarks>
744    /// This method is thread-safe against manipulations of the event queue
745    /// </remarks>
746    /// <param name="event">The event that should be scheduled.</param>
747    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
748    public override void Schedule(Event @event, int priority = 0) {
749      lock (_locker) {
750        DoSchedule(Now, @event, priority);
751      }
752    }
753
754    /// <summary>
755    /// Schedules an event to occur after a certain (positive) delay.
756    /// </summary>
757    /// <remarks>
758    /// This method is thread-safe against manipulations of the event queue
759    /// </remarks>
760    /// <exception cref="ArgumentException">
761    /// Thrown when <paramref name="delay"/> is negative.
762    /// </exception>
763    /// <param name="delay">The (positive) delay after which the event should be fired.</param>
764    /// <param name="event">The event that should be scheduled.</param>
765    /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
766    public override void Schedule(TimeSpan delay, Event @event, int priority = 0) {
767      if (delay < TimeSpan.Zero)
768        throw new ArgumentException("Negative delays are not allowed in Schedule(TimeSpan, Event).");
769      lock (_locker) {
770        var eventTime = Now + delay;
771        DoSchedule(eventTime, @event, priority);
772      }
773    }
774
775    /// <summary>
776    /// Run until a certain event is processed.
777    /// </summary>
778    /// <remarks>
779    /// This method is thread-safe against manipulations of the event queue
780    /// </remarks>
781    /// <param name="stopEvent">The event that stops the simulation.</param>
782    /// <returns></returns>
783    public override object Run(Event stopEvent = null) {
784      _stopRequested = false;
785      if (stopEvent != null) {
786        if (stopEvent.IsProcessed) return stopEvent.Value;
787        stopEvent.AddCallback(StopSimulation);
788      }
789
790      try {
791        var stop = false;
792        lock (_locker) {
793          stop = ScheduleQ.Count == 0 || _stopRequested;
794        }
795        while (!stop) {
796          Step();
797          ProcessedEvents++;
798          lock (_locker) {
799            stop = ScheduleQ.Count == 0 || _stopRequested;
800          }
801        }
802      } catch (StopSimulationException e) { return e.Value; }
803      if (stopEvent == null) return null;
804      if (!stopEvent.IsTriggered) throw new InvalidOperationException("No scheduled events left but \"until\" event was not triggered.");
805      return stopEvent.Value;
806    }
807
808    /// <summary>
809    /// Performs a single step of the simulation, i.e. process a single event
810    /// </summary>
811    /// <remarks>
812    /// This method is thread-safe against manipulations of the event queue
813    /// </remarks>
814    public override void Step() {
815      Event evt;
816      lock (_locker) {
817        var next = ScheduleQ.Dequeue();
818        Now = next.PrimaryPriority;
819        evt = next.Event;
820      }
821      evt.Process();
822    }
823
824    /// <summary>
825    /// Peeks at the time of the next event in terms of the defined step
826    /// </summary>
827    /// <remarks>
828    /// This method is thread-safe against manipulations of the event queue
829    /// </remarks>
830    public override double PeekD() {
831      lock (_locker) {
832        if (ScheduleQ.Count == 0) return double.MaxValue;
833        return (Peek() - StartDate).TotalSeconds / DefaultTimeStepSeconds;
834      }
835    }
836
837    /// <summary>
838    /// Peeks at the time of the next event
839    /// </summary>
840    /// <remarks>
841    /// This method is thread-safe against manipulations of the event queue
842    /// </remarks>
843    public override DateTime Peek() {
844      lock (_locker) {
845        return ScheduleQ.Count > 0 ? ScheduleQ.First.PrimaryPriority : DateTime.MaxValue;
846      }
847    }
848  }
849
850  /// <summary>
851  /// Environments hold the event queues, schedule and process events.
852  /// </summary>
853  [Obsolete("Use class Simulation or ThreadSafeSimulation instead. Due to name clashes with System.Environment the class SimSharp.Environment is being outphased.")]
854  public class Environment : ThreadSafeSimulation {
855    public Environment()
856      : base() {
857      Random = new SystemRandom();
858    }
859    public Environment(TimeSpan? defaultStep)
860      : base(defaultStep) {
861      Random = new SystemRandom();
862    }
863    public Environment(int randomSeed, TimeSpan? defaultStep = null)
864      : base(randomSeed, defaultStep) {
865      Random = new SystemRandom(randomSeed);
866    }
867    public Environment(DateTime initialDateTime, TimeSpan? defaultStep = null)
868      : base(initialDateTime, defaultStep) {
869      Random = new SystemRandom();
870    }
871    public Environment(DateTime initialDateTime, int randomSeed, TimeSpan? defaultStep = null)
872      : base(initialDateTime, randomSeed, defaultStep) {
873      Random = new SystemRandom(randomSeed);
874    }
875
876    protected static readonly double NormalMagicConst = 4 * Math.Exp(-0.5) / Math.Sqrt(2.0);
877    public override double RandNormal(IRandom random, double mu, double sigma) {
878      double z, zz, u1, u2;
879      do {
880        u1 = random.NextDouble();
881        u2 = 1 - random.NextDouble();
882        z = NormalMagicConst * (u1 - 0.5) / u2;
883        zz = z * z / 4.0;
884      } while (zz > -Math.Log(u2));
885      return mu + z * sigma;
886    }
887  }
888}
Note: See TracBrowser for help on using the repository browser.