void Poisson(Texture2D worldMap) { Debug.Log("Starting Poisson..."); long time = DateTime.Now.Ticks; PoissonDistribution dist = new PoissonDistribution((x, z, v) => worldMap.GetPixel((int)x, (int)z).r < 0.22f ? ( v < 0.95f ? v * 0.01f : 0.4f ) : 1.0f ); List <Vector3> poissonMap = PoissonGenerator.GeneratePoisson(Constants.WorldSize, Constants.WorldSize, 4.25f, 30.0f, dist, 90); Color[] colorSet = new Color[(Constants.WorldSize) * (Constants.WorldSize)]; for (int i = 0; i < colorSet.Length; i++) { colorSet[i] = new Color(0, 0, 0, 0); } detailMap.SetPixels(colorSet); detailMap.Apply(); foreach (Vector3 v in poissonMap) { float val = (v.z - 3.0f) / 30.0f; detailMap.SetPixel((int)v.x, (int)v.y, new Color(val, 0, val)); } detailMap.Apply(); byte[] bytes = detailMap.EncodeToPNG(); File.WriteAllBytes(Application.dataPath + "/../Poisson.png", bytes); float seconds = (DateTime.Now.Ticks - time) / 10000000.0f; Debug.Log("Poisson finished in " + seconds + " seconds!"); }
public void CalculatePoissonExpectedStandardDeviation() { //Arrange PoissonGenerator poissonGenerator = new PoissonGenerator(4, 10); //Act double number = poissonGenerator.CalculateExpectedStandardDeviation(); //Assert Assert.AreEqual(2, number); }
public void CalculatePoissonExpectedVarianceTest() { //Arrange PoissonGenerator poissonGenerator = new PoissonGenerator(4, 10); //Act double number = poissonGenerator.CalculateExpectedVariance(); //Assert Assert.AreEqual(4, number); }
public void CalculatePoissonTest() { //Arrange PoissonGenerator poissonGenerator = new PoissonGenerator(4, 10000); //Act double probability = poissonGenerator.CalculatePoisson(1); //Assert Assert.AreEqual(0.0733, Math.Round(probability, 4)); }
public void GeneratePoissonExperimentsTest() { //Arrange PoissonGenerator poissonGenerator = new PoissonGenerator(4, 10000); //Act int[] values = poissonGenerator.GeneratePoissonExperiments(); int count1 = ArrayHelper.CountNumber(values, 1); int count2 = ArrayHelper.CountNumber(values, 4); int count3 = ArrayHelper.CountNumber(values, 5); //Assert Assert.IsTrue(count1 < count2); Assert.IsTrue(count2 > count3); }
/// <summary> /// Constructor that generates default values for all fields. /// </summary> /// <param name="param">Not used.</param> public PoissonInventoryConfiguration(DefaultConstructorIdentificationClass param) : this() { TimeDependentOrderWeights = new List <Skvp <double, double> >() { new Skvp <double, double>() { Key = TimeSpan.FromHours(0).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 20) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(1).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 10) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(2).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 5) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(3).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 5) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(4).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 10) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(5).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 10) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(6).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 20) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(7).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 20) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(8).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 40) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(9).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 80) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(10).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 80) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(11).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 90) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(12).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 110) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(13).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 80) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(14).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 90) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(15).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 130) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(16).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 180) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(17).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 120) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(18).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 190) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(19).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 250) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(20).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 220) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(21).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 150) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(22).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 110) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(23).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 50) }, }; TimeDependentBundleWeights = new List <Skvp <double, double> >() { new Skvp <double, double>() { Key = TimeSpan.FromHours(0).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 20) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(1).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 40) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(2).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 80) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(3).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 80) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(4).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 90) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(5).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 110) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(6).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 80) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(7).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 90) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(8).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 130) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(9).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 180) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(10).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 120) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(11).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 190) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(12).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 250) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(13).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 220) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(14).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 150) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(15).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 110) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(16).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 50) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(17).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 20) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(18).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 10) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(19).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 5) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(20).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 5) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(21).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 10) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(22).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 10) }, new Skvp <double, double>() { Key = TimeSpan.FromHours(23).TotalSeconds, Value = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 20) }, }; }
public static void TestInputTranslationTimeDependentPoisson() { SettingConfiguration config = new SettingConfiguration(); config.InventoryConfiguration.PoissonInventoryConfiguration = new PoissonInventoryConfiguration(new DefaultConstructorIdentificationClass()); IRandomizer randomizer = new RandomizerSimple(0); int oStationCount = 3; int iStationCount = 3; // --> Instantiate poisson generator for orders // Calculate instance-specific factor to adapt the rates List <KeyValuePair <double, double> > relativeOrderWeights = new List <KeyValuePair <double, double> >(); for (int i = 0; i < config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentOrderWeights.Count; i++) { relativeOrderWeights.Add(new KeyValuePair <double, double>( i > 0 ? config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentOrderWeights[i].Key - config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentOrderWeights[i - 1].Key : config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentOrderWeights[i].Key, config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentOrderWeights[i].Value )); } double unadjustedAverageOrderFrequency = relativeOrderWeights.Sum(w => w.Key * w.Value) / config.InventoryConfiguration.PoissonInventoryConfiguration.MaxTimeForTimeDependentOrderRates; double aimedAverageOrderFrequency = TimeSpan.FromSeconds(config.InventoryConfiguration.PoissonInventoryConfiguration.MaxTimeForTimeDependentOrderRates).TotalHours * config.InventoryConfiguration.PoissonInventoryConfiguration.AverageOrdersPerHourAndStation *oStationCount / config.InventoryConfiguration.PoissonInventoryConfiguration.MaxTimeForTimeDependentOrderRates; double orderSteerFactor = aimedAverageOrderFrequency / unadjustedAverageOrderFrequency; // Initiate order poisson generator PoissonGenerator TimeDependentOrderPoissonGenerator = new PoissonGenerator( randomizer, config.InventoryConfiguration.PoissonInventoryConfiguration.MaxTimeForTimeDependentOrderRates, config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentOrderWeights.Select(w => new KeyValuePair <double, double>(w.Key, orderSteerFactor * w.Value))); // --> Instantiate poisson generator for bundles // Calculate instance-specific factor to adapt the rates List <KeyValuePair <double, double> > relativeBundleWeights = new List <KeyValuePair <double, double> >(); for (int i = 0; i < config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentBundleWeights.Count; i++) { relativeBundleWeights.Add(new KeyValuePair <double, double>( i > 0 ? config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentBundleWeights[i].Key - config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentBundleWeights[i - 1].Key : config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentBundleWeights[i].Key, config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentBundleWeights[i].Value )); } double unadjustedAverageBundleFrequency = relativeBundleWeights.Sum(w => w.Key * w.Value) / config.InventoryConfiguration.PoissonInventoryConfiguration.MaxTimeForTimeDependentBundleRates; double aimedAverageBundleFrequency = TimeSpan.FromSeconds(config.InventoryConfiguration.PoissonInventoryConfiguration.MaxTimeForTimeDependentBundleRates).TotalHours * config.InventoryConfiguration.PoissonInventoryConfiguration.AverageBundlesPerHourAndStation *iStationCount / config.InventoryConfiguration.PoissonInventoryConfiguration.MaxTimeForTimeDependentBundleRates; double bundleSteerFactor = aimedAverageBundleFrequency / unadjustedAverageBundleFrequency; // Initiate bundle poisson generator PoissonGenerator TimeDependentBundlePoissonGenerator = new PoissonGenerator( randomizer, config.InventoryConfiguration.PoissonInventoryConfiguration.MaxTimeForTimeDependentBundleRates, config.InventoryConfiguration.PoissonInventoryConfiguration.TimeDependentBundleWeights.Select(w => new KeyValuePair <double, double>(w.Key, bundleSteerFactor * w.Value))); // Initiate time-independent order poisson generator double orderRate = PoissonGenerator.TranslateIntoRateParameter( TimeSpan.FromHours(1), config.InventoryConfiguration.PoissonInventoryConfiguration.AverageOrdersPerHourAndStation * oStationCount); PoissonGenerator TimeIndependentOrderPoissonGenerator = new PoissonGenerator(randomizer, orderRate); // Initiate time-independent bundle poisson generator double bundleRate = PoissonGenerator.TranslateIntoRateParameter( TimeSpan.FromHours(1), config.InventoryConfiguration.PoissonInventoryConfiguration.AverageBundlesPerHourAndStation * iStationCount); PoissonGenerator TimeIndependentBundlePoissonGenerator = new PoissonGenerator(randomizer, bundleRate); // --> Test int simulationHours = 2 * 24; double simulationTime = simulationHours * 60 * 60; double currentTime = 0; List <double> timeDependentBundleSteps = new List <double>(); while (currentTime < simulationTime) { currentTime += TimeDependentBundlePoissonGenerator.Next(currentTime); timeDependentBundleSteps.Add(currentTime); } currentTime = 0; List <double> timeDependentOrderSteps = new List <double>(); while (currentTime < simulationTime) { currentTime += TimeDependentOrderPoissonGenerator.Next(currentTime); timeDependentOrderSteps.Add(currentTime); } currentTime = 0; List <double> timeIndependentBundleSteps = new List <double>(); while (currentTime < simulationTime) { currentTime += TimeIndependentBundlePoissonGenerator.Next(currentTime); timeIndependentBundleSteps.Add(currentTime); } currentTime = 0; List <double> timeIndependentOrderSteps = new List <double>(); while (currentTime < simulationTime) { currentTime += TimeIndependentOrderPoissonGenerator.Next(currentTime); timeIndependentOrderSteps.Add(currentTime); } // Output graph WriteHourBasedGraph( new List <List <double> >() { timeDependentBundleSteps, timeDependentOrderSteps, timeIndependentBundleSteps, timeIndependentOrderSteps }, new List <string>() { "Bundles (time-dependent)", "Orders (time-dependent)", "Bundles (time-independent)", "Orders (time-independent)" }, simulationHours); }
public static void TestBasicPoisson() { int hours = 48; double currentTime = 0; double dueTime = TimeSpan.FromHours(hours).TotalSeconds; // Test inhomogeneous poisson generator double rate = PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromHours(1), 100); PoissonGenerator generator = new PoissonGenerator(new RandomizerSimple(0), rate); List <double> homogeneousSteps = new List <double>(); while (currentTime < dueTime) { currentTime += generator.Next(currentTime); homogeneousSteps.Add(currentTime); } Console.WriteLine("Homogeneous Poisson generated " + homogeneousSteps.Count + " in " + hours + " seconds with a rate of " + rate); // Test inhomogeneous poisson generator currentTime = 0; PoissonGenerator inhomogeneousGenerator = new PoissonGenerator( new RandomizerSimple(0), TimeSpan.FromHours(24).TotalSeconds, new KeyValuePair <double, double>[] { new KeyValuePair <double, double>(TimeSpan.FromHours(0).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 20)), new KeyValuePair <double, double>(TimeSpan.FromHours(1).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 10)), new KeyValuePair <double, double>(TimeSpan.FromHours(2).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 5)), new KeyValuePair <double, double>(TimeSpan.FromHours(3).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 5)), new KeyValuePair <double, double>(TimeSpan.FromHours(4).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 10)), new KeyValuePair <double, double>(TimeSpan.FromHours(5).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 10)), new KeyValuePair <double, double>(TimeSpan.FromHours(6).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 20)), new KeyValuePair <double, double>(TimeSpan.FromHours(7).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 20)), new KeyValuePair <double, double>(TimeSpan.FromHours(8).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 40)), new KeyValuePair <double, double>(TimeSpan.FromHours(9).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 80)), new KeyValuePair <double, double>(TimeSpan.FromHours(10).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 80)), new KeyValuePair <double, double>(TimeSpan.FromHours(11).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 90)), new KeyValuePair <double, double>(TimeSpan.FromHours(12).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 110)), new KeyValuePair <double, double>(TimeSpan.FromHours(13).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 80)), new KeyValuePair <double, double>(TimeSpan.FromHours(14).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 90)), new KeyValuePair <double, double>(TimeSpan.FromHours(15).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 130)), new KeyValuePair <double, double>(TimeSpan.FromHours(16).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 180)), new KeyValuePair <double, double>(TimeSpan.FromHours(17).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 120)), new KeyValuePair <double, double>(TimeSpan.FromHours(18).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 190)), new KeyValuePair <double, double>(TimeSpan.FromHours(19).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 250)), new KeyValuePair <double, double>(TimeSpan.FromHours(20).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 220)), new KeyValuePair <double, double>(TimeSpan.FromHours(21).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 150)), new KeyValuePair <double, double>(TimeSpan.FromHours(22).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 110)), new KeyValuePair <double, double>(TimeSpan.FromHours(23).TotalSeconds, PoissonGenerator.TranslateIntoRateParameter(TimeSpan.FromMinutes(60), 50)) }); List <double> inhomogeneousSteps = new List <double>(); while (currentTime < dueTime) { currentTime += inhomogeneousGenerator.Next(currentTime); inhomogeneousSteps.Add(currentTime); } Console.WriteLine("Homogeneous Poisson generated " + inhomogeneousSteps.Count + " in " + hours + " with rates " + string.Join(",", inhomogeneousGenerator.TimeDependentRates.Select(r => "(" + r.Key + "/" + r.Value + ")"))); // Output graph WriteHourBasedGraph(new List <List <double> >() { homogeneousSteps, inhomogeneousSteps }, new List <string>() { "Homogeneous", "Inhomogeneous" }, hours); }