#region License Information /* SimSharp - A .NET port of SimPy, discrete event simulation framework Copyright (C) 2002-2018 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 { /// /// Environments hold the event queues, schedule and process events. /// public class Environment { private const int InitialMaxEvents = 1024; private object locker = new object(); /// /// 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; protected Queue Queue; public Process ActiveProcess { get; set; } public TextWriter Logger { get; set; } public int ProcessedEvents { get; protected set; } public Environment() : this(new DateTime(1970, 1, 1)) { } public Environment(TimeSpan? defaultStep) : this(new DateTime(1970, 1, 1), defaultStep) { } public Environment(int randomSeed, TimeSpan? defaultStep = null) : this(new DateTime(1970, 1, 1), randomSeed, defaultStep) { } public Environment(DateTime initialDateTime, TimeSpan? defaultStep = null) { DefaultTimeStepSeconds = (defaultStep ?? TimeSpan.FromSeconds(1)).Duration().TotalSeconds; StartDate = initialDateTime; Now = initialDateTime; Random = new SystemRandom(); ScheduleQ = new EventQueue(InitialMaxEvents); Queue = new Queue(); Logger = Console.Out; } public Environment(DateTime initialDateTime, int randomSeed, TimeSpan? defaultStep = null) { DefaultTimeStepSeconds = (defaultStep ?? TimeSpan.FromSeconds(1)).Duration().TotalSeconds; StartDate = initialDateTime; Now = initialDateTime; Random = new SystemRandom(randomSeed); ScheduleQ = new EventQueue(InitialMaxEvents); Queue = new Queue(); Logger = Console.Out; } public double ToDouble(TimeSpan span) { return span.TotalSeconds / DefaultTimeStepSeconds; } public TimeSpan ToTimeSpan(double span) { return TimeSpan.FromSeconds(DefaultTimeStepSeconds * span); } public Process Process(IEnumerable generator) { return new Process(this, generator); } public Timeout TimeoutD(double delay) { return Timeout(TimeSpan.FromSeconds(DefaultTimeStepSeconds * delay)); } public Timeout Timeout(TimeSpan delay) { return new Timeout(this, delay); } public virtual void Reset(int randomSeed) { ProcessedEvents = 0; Now = StartDate; Random = new SystemRandom(randomSeed); ScheduleQ = new EventQueue(InitialMaxEvents); Queue = new Queue(); } public virtual void ScheduleD(double delay, Event @event) { Schedule(TimeSpan.FromSeconds(DefaultTimeStepSeconds * delay), @event); } public virtual void Schedule(Event @event) { lock (locker) { Queue.Enqueue(@event); } } public virtual void Schedule(TimeSpan delay, Event @event) { if (delay < TimeSpan.Zero) throw new ArgumentException("Negative delays are not allowed in Schedule(TimeSpan, Event)."); lock (locker) { if (delay == TimeSpan.Zero) { Queue.Enqueue(@event); return; } var eventTime = Now + delay; DoSchedule(eventTime, @event); } } protected virtual EventQueueNode DoSchedule(DateTime date, Event @event) { 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.Priority, e.Event); } return ScheduleQ.Enqueue(date, @event); } 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); } public virtual object Run(Event stopEvent = null) { if (stopEvent != null) { if (stopEvent.IsProcessed) return stopEvent.Value; stopEvent.AddCallback(StopSimulation); } try { var stop = Queue.Count == 0 && ScheduleQ.Count == 0; while (!stop) { Step(); ProcessedEvents++; lock (locker) { stop = Queue.Count == 0 && ScheduleQ.Count == 0; } } } 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 Step() { Event evt; lock (locker) { if (Queue.Count == 0) { var next = ScheduleQ.Dequeue(); Now = next.Priority; evt = next.Event; } else evt = Queue.Dequeue(); } evt.Process(); } public virtual double PeekD() { lock (locker) { if (Queue.Count == 0 && ScheduleQ.Count == 0) return double.MaxValue; return (Peek() - StartDate).TotalSeconds / DefaultTimeStepSeconds; } } public virtual DateTime Peek() { lock (locker) { return Queue.Count > 0 ? Now : (ScheduleQ.Count > 0 ? ScheduleQ.First.Priority : 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 protected static readonly double NormalMagicConst = 4 * Math.Exp(-0.5) / Math.Sqrt(2.0); public double RandUniform(double a, double b) { return a + (b - a) * Random.NextDouble(); } public TimeSpan RandUniform(TimeSpan a, TimeSpan b) { return TimeSpan.FromSeconds(RandUniform(a.TotalSeconds, b.TotalSeconds)); } public double RandTriangular(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 TimeSpan RandTriangular(TimeSpan low, TimeSpan high) { return TimeSpan.FromSeconds(RandTriangular(low.TotalSeconds, high.TotalSeconds)); } public double RandTriangular(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 TimeSpan RandTriangular(TimeSpan low, TimeSpan high, TimeSpan mode) { return TimeSpan.FromSeconds(RandTriangular(low.TotalSeconds, high.TotalSeconds, mode.TotalSeconds)); } /// /// 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 -Math.Log(1 - Random.NextDouble()) * 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 mean(!) of the distribution is 1 / lambda. /// A number that is exponentially distributed public TimeSpan RandExponential(TimeSpan mean) { return TimeSpan.FromSeconds(RandExponential(mean.TotalSeconds)); } public double RandNormal(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; } public TimeSpan RandNormal(TimeSpan mu, TimeSpan sigma) { return TimeSpan.FromSeconds(RandNormal(mu.TotalSeconds, sigma.TotalSeconds)); } public double RandNormalPositive(double mu, double sigma) { double val; do { val = RandNormal(mu, sigma); } while (val <= 0); return val; } public TimeSpan RandNormalPositive(TimeSpan mu, TimeSpan sigma) { return TimeSpan.FromSeconds(RandNormalPositive(mu.TotalSeconds, sigma.TotalSeconds)); } public double RandNormalNegative(double mu, double sigma) { double val; do { val = RandNormal(mu, sigma); } while (val >= 0); return val; } public TimeSpan RandNormalNegative(TimeSpan mu, TimeSpan sigma) { return TimeSpan.FromSeconds(RandNormalNegative(mu.TotalSeconds, sigma.TotalSeconds)); } public double RandLogNormal(double mu, double sigma) { return Math.Exp(RandNormal(mu, sigma)); } public TimeSpan RandLogNormal(TimeSpan mu, TimeSpan sigma) { return TimeSpan.FromSeconds(RandLogNormal(mu.TotalSeconds, sigma.TotalSeconds)); } public double RandCauchy(double x0, double gamma) { return x0 + gamma * Math.Tan(Math.PI * (Random.NextDouble() - 0.5)); } public TimeSpan RandCauchy(TimeSpan x0, TimeSpan gamma) { return TimeSpan.FromSeconds(RandCauchy(x0.TotalSeconds, gamma.TotalSeconds)); } public double RandWeibull(double alpha, double beta) { return alpha * Math.Pow(-Math.Log(1 - Random.NextDouble()), 1 / beta); } public TimeSpan RandWeibull(TimeSpan mu, TimeSpan sigma) { return TimeSpan.FromSeconds(RandWeibull(mu.TotalSeconds, sigma.TotalSeconds)); } #endregion #region Random timeouts public Timeout TimeoutUniformD(double a, double b) { return new Timeout(this, ToTimeSpan(RandUniform(a, b))); } public Timeout TimeoutUniform(TimeSpan a, TimeSpan b) { return new Timeout(this, RandUniform(a, b)); } public Timeout TimeoutTriangularD(double low, double high) { return new Timeout(this, ToTimeSpan(RandTriangular(low, high))); } public Timeout TimeoutTriangular(TimeSpan low, TimeSpan high) { return new Timeout(this, RandTriangular(low, high)); } public Timeout TimeoutTriangularD(double low, double high, double mode) { return new Timeout(this, ToTimeSpan(RandTriangular(low, high, mode))); } public Timeout TimeoutTriangular(TimeSpan low, TimeSpan high, TimeSpan mode) { return new Timeout(this, RandTriangular(low, high, mode)); } public Timeout TimeoutExponentialD(double mean) { return new Timeout(this, ToTimeSpan(RandExponential(mean))); } public Timeout TimeoutExponential(TimeSpan mean) { return new Timeout(this, RandExponential(mean)); } public Timeout TimeoutNormalPositiveD(double mu, double sigma) { return new Timeout(this, ToTimeSpan(RandNormalPositive(mu, sigma))); } public Timeout TimeoutNormalPositive(TimeSpan mu, TimeSpan sigma) { return new Timeout(this, RandNormalPositive(mu, sigma)); } public Timeout TimeoutLogNormalD(double mu, double sigma) { return new Timeout(this, ToTimeSpan(RandLogNormal(mu, sigma))); } public Timeout TimeoutLogNormal(TimeSpan mu, TimeSpan sigma) { return new Timeout(this, RandLogNormal(mu, sigma)); } #endregion } }