#region License Information
/* SimSharp - A .NET port of SimPy, discrete event simulation framework
Copyright (C) 2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
This program 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.
This program 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 this program. If not, see .*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
namespace SimSharp {
///
/// Simulation hold the event queues, schedule and process events.
///
///
/// This class is not thread-safe against manipulation of the event queue. If you supply a termination
/// event that is set outside the simulation, please use the environment.
///
/// For most purposes is however the better and faster choice.
///
public class Simulation {
private const int InitialMaxEvents = 1024;
///
/// Describes the number of seconds that a logical step of 1 in the *D-API takes.
///
protected double DefaultTimeStepSeconds { get; private set; }
///
/// Calculates the logical date of the simulation by the amount of default steps
/// that have passed.
///
public double NowD {
get { return (Now - StartDate).TotalSeconds / DefaultTimeStepSeconds; }
}
///
/// The current simulation time as a calendar date.
///
public DateTime Now { get; protected set; }
///
/// The calendar date when the simulation started. This defaults to 1970-1-1 if
/// no other date has been specified in the overloaded constructor.
///
public DateTime StartDate { get; protected set; }
///
/// The random number generator that is to be used in all events in
/// order to produce reproducible results.
///
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);
}
///
/// Creates a new process from an event generator. The process is automatically
/// scheduled to be started at the current simulation time.
///
/// The generator function that represents the process.
/// The priority to rank events at the same time (smaller value = higher priority).
/// The scheduled process that was created.
public Process Process(IEnumerable generator, int priority = 0) {
return new Process(this, generator, priority);
}
///
/// Creates and returns a new timeout.
///
/// The time after which the timeout is fired.
/// The priority to rank events at the same time (smaller value = higher priority).
/// The scheduled timeout event that was created.
public Timeout TimeoutD(double delay, int priority = 0) {
return Timeout(TimeSpan.FromSeconds(DefaultTimeStepSeconds * delay), priority);
}
///
/// Creates and returns a new timeout.
///
/// The time after which the timeout is fired.
/// The priority to rank events at the same time (smaller value = higher priority).
/// The scheduled timeout event that was created.
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);
}
///
/// Schedules an event to occur at the same simulation time as the call was made.
///
/// The event that should be scheduled.
/// The priority to rank events at the same time (smaller value = higher priority).
public virtual void Schedule(Event @event, int priority = 0) {
DoSchedule(Now, @event, priority);
}
///
/// Schedules an event to occur after a certain (positive) delay.
///
///
/// Thrown when is negative.
///
/// The (positive) delay after which the event should be fired.
/// The event that should be scheduled.
/// The priority to rank events at the same time (smaller value = higher priority).
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 bool _stopRequested = false;
///
/// Run until a certain event is processed.
///
///
/// This simulation environment is not thread-safe, thus triggering this event outside the environment
/// leads to potential race conditions. Please use the environment in case you
/// require this functionality. Note that the performance of is lower due to locking.
///
/// For real-time based termination, you can also call which sets a flag indicating the simulation
/// to stop before processing the next event.
///
/// The event that stops the simulation.
///
public virtual object Run(Event stopEvent = null) {
_stopRequested = false;
if (stopEvent != null) {
if (stopEvent.IsProcessed) return stopEvent.Value;
stopEvent.AddCallback(StopSimulation);
}
try {
var stop = ScheduleQ.Count == 0 || _stopRequested;
while (!stop) {
Step();
ProcessedEvents++;
stop = ScheduleQ.Count == 0 || _stopRequested;
}
} catch (StopSimulationException e) { return e.Value; }
if (stopEvent == null) return null;
if (!stopEvent.IsTriggered) throw new InvalidOperationException("No scheduled events left but \"until\" event was not triggered.");
return stopEvent.Value;
}
public virtual void StopAsync() {
_stopRequested = true;
}
///
/// Performs a single step of the simulation, i.e. process a single event
///
///
/// This method is not thread-safe
///
public virtual void Step() {
Event evt;
var next = ScheduleQ.Dequeue();
Now = next.PrimaryPriority;
evt = next.Event;
evt.Process();
}
///
/// Peeks at the time of the next event in terms of the defined step
///
///
/// This method is not thread-safe
///
public virtual double PeekD() {
if (ScheduleQ.Count == 0) return double.MaxValue;
return (Peek() - StartDate).TotalSeconds / DefaultTimeStepSeconds;
}
///
/// Peeks at the time of the next event
///
///
/// This method is not thread-safe
///
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);
}
///
/// Returns a number that is exponentially distributed given a certain mean.
///
///
/// Unlike in other APIs here the mean should be given and not the lambda parameter.
///
/// The random number generator to use.
/// The mean(!) of the distribution is 1 / lambda.
/// A number that is exponentially distributed
public double RandExponential(IRandom random, double mean) {
return -Math.Log(1 - random.NextDouble()) * mean;
}
///
/// Returns a number that is exponentially distributed given a certain mean.
///
///
/// Unlike in other APIs here the mean should be given and not the lambda parameter.
///
/// The mean(!) of the distribution is 1 / lambda.
/// A number that is exponentially distributed
public double RandExponential(double mean) {
return RandExponential(Random, mean);
}
///
/// Returns a timespan that is exponentially distributed given a certain mean.
///
///
/// Unlike in other APIs here the mean should be given and not the lambda parameter.
///
/// The random number generator to use.
/// The mean(!) of the distribution is 1 / lambda.
/// A number that is exponentially distributed
public TimeSpan RandExponential(IRandom random, TimeSpan mean) {
return TimeSpan.FromSeconds(RandExponential(random, mean.TotalSeconds));
}
///
/// Returns a timespan that is exponentially distributed given a certain mean.
///
///
/// Unlike in other APIs here the mean should be given and not the lambda parameter.
///
/// The mean(!) of the distribution is 1 / lambda.
/// A number that is exponentially distributed
public TimeSpan RandExponential(TimeSpan mean) {
return RandExponential(Random, mean);
}
private bool useSpareNormal = false;
private double spareNormal = double.NaN;
///
/// Uses the Marsaglia polar method to generate a random variable
/// from two uniform random distributed values.
///
///
/// 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.
///
/// The random number generator to use.
/// The mean of the normal distribution.
/// The standard deviation of the normal distribution.
/// A number that is normal distributed.
public virtual double RandNormal(IRandom random, double mu, double sigma) {
if (useSpareNormal) {
useSpareNormal = false;
return spareNormal * sigma + mu;
} else {
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);
spareNormal = v * mul;
useSpareNormal = true;
return mu + sigma * u * mul;
}
}
///
/// Uses the Marsaglia polar method to generate a random variable
/// from two uniform random distributed values.
///
///
/// 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.
///
/// The mean of the normal distribution.
/// The standard deviation of the normal distribution.
/// A number that is normal distributed.
public double RandNormal(double mu, double sigma) {
return RandNormal(Random, mu, sigma);
}
///
/// Uses the Marsaglia polar method to generate a random variable
/// from two uniform random distributed values.
///
///
/// 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.
///
/// The random number generator to use.
/// The mean of the normal distribution.
/// The standard deviation of the normal distribution.
/// A number that is normal distributed.
public TimeSpan RandNormal(IRandom random, TimeSpan mu, TimeSpan sigma) {
return TimeSpan.FromSeconds(RandNormal(random, mu.TotalSeconds, sigma.TotalSeconds));
}
///
/// Uses the Marsaglia polar method to generate a random variable
/// from two uniform random distributed values.
///
///
/// 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.
///
/// The mean of the normal distribution.
/// The standard deviation of the normal distribution.
/// A number that is normal distributed.
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);
}
///
/// 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))
///
/// The random number generator to use.
/// The mu parameter of the log-normal distribution (not the mean).
/// The sigma parameter of the log-normal distribution (not the standard deviation).
/// A log-normal distributed random value.
public double RandLogNormal(IRandom random, double mu, double sigma) {
return Math.Exp(RandNormal(random, mu, sigma));
}
///
/// 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))
///
/// The mu parameter of the log-normal distribution (not the mean).
/// The sigma parameter of the log-normal distribution (not the standard deviation).
/// A log-normal distributed random value.
public double RandLogNormal(double mu, double sigma) {
return RandLogNormal(Random, mu, sigma);
}
///
/// Returns values from a log-normal distribution with
/// the mean and standard deviation .
///
/// The random number generator to use.
/// The distribution mean.
/// The distribution standard deviation.
/// A log-normal distributed random value.
public double RandLogNormal2(IRandom random, double mean, double stdev) {
if (stdev == 0) return mean;
var alpha = Math.Sqrt(mean * stdev) / mean;
var sigma = Math.Sqrt(Math.Log(1 + (alpha * alpha)));
var mu = Math.Log(mean) - 0.5 * sigma * sigma;
return Math.Exp(RandNormal(random, mu, sigma));
}
///
/// Returns values from a log-normal distribution with
/// the mean and standard deviation .
///
/// The distribution mean.
/// The distribution standard deviation.
/// A log-normal distributed random value.
public double RandLogNormal2(double mean, double stdev) {
return RandLogNormal2(Random, mean, stdev);
}
///
/// 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))
///
/// The random number generator to use.
/// The mu parameter of the log-normal distribution (not the mean).
/// The sigma parameter of the log-normal distribution (not the standard deviation).
/// A log-normal distributed random timespan.
public TimeSpan RandLogNormal(IRandom random, TimeSpan mu, TimeSpan sigma) {
return TimeSpan.FromSeconds(RandLogNormal(random, mu.TotalSeconds, sigma.TotalSeconds));
}
///
/// 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))
///
/// The mu parameter of the log-normal distribution (not the mean).
/// The sigma parameter of the log-normal distribution (not the standard deviation).
/// A log-normal distributed random timespan.
public TimeSpan RandLogNormal(TimeSpan mu, TimeSpan sigma) {
return RandLogNormal(Random, mu, sigma);
}
///
/// Returns a timespan value from a log-normal distribution with
/// the mean and standard deviation .
///
/// The random number generator to use.
/// The distribution mean.
/// The distribution standard deviation.
/// A log-normal distributed random timespan.
public TimeSpan RandLogNormal2(IRandom random, TimeSpan mean, TimeSpan stdev) {
return TimeSpan.FromSeconds(RandLogNormal2(random, mean.TotalSeconds, stdev.TotalSeconds));
}
///
/// Returns a timespan value from a log-normal distribution with
/// the mean and standard deviation .
///
/// The distribution mean.
/// The distribution standard deviation.
/// A log-normal distributed random timespan.
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);
}
#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
}
///
/// 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. )
/// due to the locking involved.
///
///
/// Please carefully consider if you must really schedule the stop event in a separate thread. You can also
/// call to request termination after the current event has been processed.
///
/// The simulation will still run in only one thread and execute all events sequentially.
///
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();
}
///
/// Schedules an event to occur at the same simulation time as the call was made.
///
///
/// This method is thread-safe against manipulations of the event queue
///
/// The event that should be scheduled.
/// The priority to rank events at the same time (smaller value = higher priority).
public override void Schedule(Event @event, int priority = 0) {
lock (_locker) {
DoSchedule(Now, @event, priority);
}
}
///
/// Schedules an event to occur after a certain (positive) delay.
///
///
/// This method is thread-safe against manipulations of the event queue
///
///
/// Thrown when is negative.
///
/// The (positive) delay after which the event should be fired.
/// The event that should be scheduled.
/// The priority to rank events at the same time (smaller value = higher priority).
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);
}
}
///
/// Run until a certain event is processed.
///
///
/// This method is thread-safe against manipulations of the event queue
///
/// The event that stops the simulation.
///
public override object Run(Event stopEvent = null) {
_stopRequested = false;
if (stopEvent != null) {
if (stopEvent.IsProcessed) return stopEvent.Value;
stopEvent.AddCallback(StopSimulation);
}
try {
var stop = false;
lock (_locker) {
stop = ScheduleQ.Count == 0 || _stopRequested;
}
while (!stop) {
Step();
ProcessedEvents++;
lock (_locker) {
stop = ScheduleQ.Count == 0 || _stopRequested;
}
}
} catch (StopSimulationException e) { return e.Value; }
if (stopEvent == null) return null;
if (!stopEvent.IsTriggered) throw new InvalidOperationException("No scheduled events left but \"until\" event was not triggered.");
return stopEvent.Value;
}
///
/// Performs a single step of the simulation, i.e. process a single event
///
///
/// This method is thread-safe against manipulations of the event queue
///
public override void Step() {
Event evt;
lock (_locker) {
var next = ScheduleQ.Dequeue();
Now = next.PrimaryPriority;
evt = next.Event;
}
evt.Process();
}
///
/// Peeks at the time of the next event in terms of the defined step
///
///
/// This method is thread-safe against manipulations of the event queue
///
public override double PeekD() {
lock (_locker) {
if (ScheduleQ.Count == 0) return double.MaxValue;
return (Peek() - StartDate).TotalSeconds / DefaultTimeStepSeconds;
}
}
///
/// Peeks at the time of the next event
///
///
/// This method is thread-safe against manipulations of the event queue
///
public override DateTime Peek() {
lock (_locker) {
return ScheduleQ.Count > 0 ? ScheduleQ.First.PrimaryPriority : DateTime.MaxValue;
}
}
}
///
/// Environments hold the event queues, schedule and process events.
///
[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(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;
}
}
}