diff --git a/Banchmarking/Benchmarking.csproj b/Banchmarking/Benchmarking.csproj index f36995f..b8c69e8 100644 --- a/Banchmarking/Benchmarking.csproj +++ b/Banchmarking/Benchmarking.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/GeneticAlgorithm.UnitTests/ChildrenGeneratorTests.cs b/GeneticAlgorithm.UnitTests/ChildrenGeneratorTests.cs index 27a899e..478db01 100644 --- a/GeneticAlgorithm.UnitTests/ChildrenGeneratorTests.cs +++ b/GeneticAlgorithm.UnitTests/ChildrenGeneratorTests.cs @@ -27,7 +27,7 @@ public void TestMutationsHappen(double mutationProbability) var crossoverManager = A.Fake(); A.CallTo(() => crossoverManager.Crossover(A._, A._)) .ReturnsLazily((IChromosome c1, IChromosome c2) => c1); - var childrenGenerator = new ChildrenGenerator(crossoverManager, new BassicMutationProbabilityManager(mutationProbability), new RouletteWheelSelection()); + var childrenGenerator = new ChildrenGenerator(crossoverManager, new BasicMutationProbabilityManager(mutationProbability), new RouletteWheelSelection()); childrenGenerator.GenerateChildren(population, populationSize, 0, null); const double errorMargin = 0.05; @@ -44,7 +44,7 @@ public void RetusnRightNumberOfChromosomes(int childrenCount) { const int count = 1500; var crossoverManager = A.Fake(); - var childrenGenerator = new ChildrenGenerator(crossoverManager, new BassicMutationProbabilityManager(0), new RouletteWheelSelection()); + var childrenGenerator = new ChildrenGenerator(crossoverManager, new BasicMutationProbabilityManager(0), new RouletteWheelSelection()); var children = childrenGenerator.GenerateChildren(GetPopulation(count), childrenCount, 0, null); Assert.AreEqual(childrenCount, children.Length, "Didn't get enough children"); foreach (var chromosome in children) @@ -71,7 +71,7 @@ public void BadMutationProbability_ThrowException(double probability) [DataRow(-1)] public void RequestBadNumberOfChildren_ThrowsException(int childrenCount) { - var childrenGenerator = new ChildrenGenerator(A.Fake(), new BassicMutationProbabilityManager(0), + var childrenGenerator = new ChildrenGenerator(A.Fake(), new BasicMutationProbabilityManager(0), new RouletteWheelSelection()); childrenGenerator.GenerateChildren(GetPopulation(1), childrenCount, 0, null); } @@ -83,7 +83,7 @@ public void SelectionStrategyReturnsNull_ThrowsException() { var selectionStrategy = A.Fake(); A.CallTo(() => selectionStrategy.SelectChromosome()).Returns(null); - var childrenGenerator = new ChildrenGenerator(A.Fake(), new BassicMutationProbabilityManager(0), + var childrenGenerator = new ChildrenGenerator(A.Fake(), new BasicMutationProbabilityManager(0), selectionStrategy); childrenGenerator.GenerateChildren(GetPopulation(1), 1, 0, null); }, typeof(GeneticAlgorithmException)); diff --git a/GeneticAlgorithm.UnitTests/Components/CrossoverManagers/Utilities/NonReapetingAdjacencyMatrixTests.cs b/GeneticAlgorithm.UnitTests/Components/CrossoverManagers/Utilities/NonReapetingAdjacencyMatrixTests.cs index 097ca90..5dbfc64 100644 --- a/GeneticAlgorithm.UnitTests/Components/CrossoverManagers/Utilities/NonReapetingAdjacencyMatrixTests.cs +++ b/GeneticAlgorithm.UnitTests/Components/CrossoverManagers/Utilities/NonReapetingAdjacencyMatrixTests.cs @@ -103,10 +103,10 @@ public void GetNeigbor_GetRandomNeigbor(bool selectNeighborWithLeastNeighbors) } var excpectedToGetAtLeast = selectNeighborWithLeastNeighbors ? 35 : 25; - Assert.IsTrue(excpectedToGetAtLeast < got2); - Assert.IsTrue(excpectedToGetAtLeast < got5); + Assert.IsTrue(excpectedToGetAtLeast < got2, $"Got 2 only {got2} times"); + Assert.IsTrue(excpectedToGetAtLeast < got5, $"Got 5 only {got5} times"); if (!selectNeighborWithLeastNeighbors) - Assert.IsTrue(excpectedToGetAtLeast < got4); + Assert.IsTrue(excpectedToGetAtLeast < got4, $"Got 4 only {got4} times"); } [TestMethod] diff --git a/GeneticAlgorithm.UnitTests/ExceptionTests.cs b/GeneticAlgorithm.UnitTests/ExceptionTests.cs index 7f904bb..25ec6d7 100644 --- a/GeneticAlgorithm.UnitTests/ExceptionTests.cs +++ b/GeneticAlgorithm.UnitTests/ExceptionTests.cs @@ -4,6 +4,8 @@ using FakeItEasy; using GeneticAlgorithm.Exceptions; using GeneticAlgorithm.Interfaces; +using GeneticAlgorithm.PopulationRenwalManagers; +using GeneticAlgorithm.StopManagers; using GeneticAlgorithm.UnitTests.TestUtils; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -21,7 +23,7 @@ public void SetNegativePopulationSize_ThrowsException(int size) => .Build(); [TestMethod] - [ExpectedException(typeof(GeneticAlgorithmException))] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] [DataRow(1)] [DataRow(-2)] public void BadNumberOfGenerations_ThrowsException(int size) => @@ -107,30 +109,95 @@ public void GetCurrentPopulation_EngineRunning_ThrowException() [ExpectedException(typeof(EngineAlreadyRunningException))] public void SetCurrentPopulation_EngineRunning_ThrowException() { - var engine = - new TestGeneticSearchEngineBuilder(2, int.MaxValue, new TestPopulationManager(new double[] { 2, 2 })).Build(); + Utils.RunTimedTest(() => + { + var engine = + new TestGeneticSearchEngineBuilder(2, int.MaxValue, new TestPopulationManager(new double[] { 2, 2 })).Build(); - Task.Run(() => engine.Run()); - while (!engine.IsRunning) ; + Task.Run(() => engine.Run()); + while (!engine.IsRunning) ; - engine.SetCurrentPopulation(new double[] { 3 ,3 }.ToChromosomes("Converted")); + engine.SetCurrentPopulation(new double[] { 3, 3 }.ToChromosomes("Converted")); - Assert.Fail("Should have thrown an exception by now"); + Assert.Fail("Should have thrown an exception by now"); + }); } [TestMethod] [ExpectedException(typeof(GeneticAlgorithmException))] public void SetCurrentPopulation_WrongNumberOfChromosomes_ThrowException() { - var engine = + Utils.RunTimedTest(() => + { + var engine = new TestGeneticSearchEngineBuilder(2, int.MaxValue, new TestPopulationManager(new double[] { 2, 2 })).Build(); - Task.Run(() => engine.Run()); - while (!engine.IsRunning) ; + Task.Run(() => engine.Run()); + while (!engine.IsRunning) ; - engine.SetCurrentPopulation(new double[] { 3, 3 ,3 }.ToChromosomes("Converted")); + engine.SetCurrentPopulation(new double[] { 3, 3, 3 }.ToChromosomes("Converted")); - Assert.Fail("Should have thrown an exception by now"); + Assert.Fail("Should have thrown an exception by now"); + }); } + + [TestMethod] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void StopAtConvergence_DiffNegative_ThrowException() => + new StopAtConvergence(-0.1); + + [TestMethod] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void StopAtEvaluation_EvaluationNegative_ThrowException() => + new StopAtEvaluation(-0.1); + + [TestMethod] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void StopAtGeneration_BadGeneration_ThrowException() => + new StopAtGeneration(1); + + [TestMethod] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void StopIfNoImprovment_BadGenerationsToConsider_ThrowException() => + new StopIfNoImprovment(0, 0.5); + + [TestMethod] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void RenewIfNoImprovment_BadGenerationsToConsider_ThrowException() => + new RenewIfNoImprovment(0, 1, 0.5); + + [TestMethod] + [DataRow(-0.1)] + [DataRow(0)] + [DataRow(1.1)] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void RenewIfNoImprovment_BadPrecentageToRenew_ThrowException(double precentageToRenew) => + new RenewIfNoImprovment(1, 0.5, precentageToRenew); + + [TestMethod] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void RenewAtConvergence_BadGenerationsToConsider_ThrowException() => + new RenewAtConvergence(-0.1, 0.5); + + [TestMethod] + [DataRow(-0.1)] + [DataRow(0)] + [DataRow(1.1)] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void RenewAtConvergence_BadPrecentageToRenew_ThrowException(double precentageToRenew) => + new RenewAtConvergence(1, precentageToRenew); + + [TestMethod] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void RenewAtDifferenceBetweenAverageAndMaximumFitness_NegativeDiff_ThrowException() => + new RenewAtDifferenceBetweenAverageAndMaximumFitness(-0.1, 0.5); + + [TestMethod] + [DataRow(-0.1)] + [DataRow(0)] + [DataRow(1.1)] + [ExpectedException(typeof(GeneticAlgorithmArgumentException))] + public void RenewAtDifferenceBetweenAverageAndMaximumFitness_BadPrecentageToRenew_ThrowException(double precentageToRenew) => + new RenewAtDifferenceBetweenAverageAndMaximumFitness(1, precentageToRenew); } } diff --git a/GeneticAlgorithm.UnitTests/GeneticAlgorithm.UnitTests.csproj b/GeneticAlgorithm.UnitTests/GeneticAlgorithm.UnitTests.csproj index db3da38..7594503 100644 --- a/GeneticAlgorithm.UnitTests/GeneticAlgorithm.UnitTests.csproj +++ b/GeneticAlgorithm.UnitTests/GeneticAlgorithm.UnitTests.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/GeneticAlgorithm.UnitTests/NumberVectorTests.cs b/GeneticAlgorithm.UnitTests/NumberVectorTests.cs index 57f998c..6320cb0 100644 --- a/GeneticAlgorithm.UnitTests/NumberVectorTests.cs +++ b/GeneticAlgorithm.UnitTests/NumberVectorTests.cs @@ -29,7 +29,7 @@ public NumberVectorTests() [TestMethod] [DataRow(RunType.Run)] [DataRow(RunType.Next)] - public void BassicTest(RunType runType) + public void BasicTest(RunType runType) { var searchEngine = new GeneticSearchEngineBuilder(POPULATION_SIZE, 50, crossoverManager, populationGenerator) diff --git a/GeneticAlgorithm.UnitTests/PopulationRenwalManagerTests.cs b/GeneticAlgorithm.UnitTests/PopulationRenwalManagerTests.cs index 3092325..206c58d 100644 --- a/GeneticAlgorithm.UnitTests/PopulationRenwalManagerTests.cs +++ b/GeneticAlgorithm.UnitTests/PopulationRenwalManagerTests.cs @@ -18,14 +18,14 @@ public void RenewAtConvergenceTest(RunType runType) { var populationManager = new TestPopulationManager(new double[] {1, 1, 1}); populationManager.SetPopulationGenerated(new[] - {new double[] {2, 2, 2}, new double[] {3, 3, 3}, new double[] {4, 4, 4}}); + {new double[] {2, 3, 2}, new double[] {4, 4, 4}, new double[] {5, 5, 5}}); var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, 4, populationManager) .AddPopulationRenwalManager(new RenewAtConvergence(0.9, 1)) .IncludeAllHistory().Build(); var result = engine.Run(runType); - Assert.AreEqual(4, result.BestChromosome.Evaluate()); + Assert.AreEqual(3, result.BestChromosome.Evaluate()); } [TestMethod] @@ -76,6 +76,23 @@ public void RenewIfNoImprovmentTest2(RunType runType) Assert.AreEqual(2, result.BestChromosome.Evaluate()); } + [TestMethod] + [DataRow(RunType.Run)] + [DataRow(RunType.Next)] + public void RenewAtDifferenceBetweenAverageAndMaximumFitnessTest(RunType runType) + { + var populationManager = new TestPopulationManager(new double[] { 1, 2, 1 }); + populationManager.SetPopulationGenerated(new[] + {new double[] {2, 5, 2}, new double[] {6, 6, 6}, new double[] {7, 7, 7}}); + var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, 4, populationManager) + .AddPopulationRenwalManager(new RenewAtDifferenceBetweenAverageAndMaximumFitness(1, 1)) + .IncludeAllHistory().Build(); + + var result = engine.Run(runType); + + Assert.AreEqual(5, result.BestChromosome.Evaluate()); + } + [TestMethod] [DataRow(RunType.Run)] [DataRow(RunType.Next)] diff --git a/GeneticAlgorithm.UnitTests/ProbabilityUtilsTests.cs b/GeneticAlgorithm.UnitTests/ProbabilityUtilsTests.cs index 1f0c049..21de6c8 100644 --- a/GeneticAlgorithm.UnitTests/ProbabilityUtilsTests.cs +++ b/GeneticAlgorithm.UnitTests/ProbabilityUtilsTests.cs @@ -20,7 +20,7 @@ public void P_Test(double probability) var trueCount = 0; var falseCount = 0; var attempts = 1000; - var margernOnError = attempts * 0.1; + var marginOfError = attempts * 0.1; for (int i = 0; i < attempts; i++) { if (ProbabilityUtils.P(probability)) @@ -29,8 +29,8 @@ public void P_Test(double probability) falseCount++; } - trueCount.AssertIsWithinRange(attempts * probability, margernOnError); - falseCount.AssertIsWithinRange(attempts * (1 - probability), margernOnError); + trueCount.AssertIsWithinRange(attempts * probability, marginOfError); + falseCount.AssertIsWithinRange(attempts * (1 - probability), marginOfError); } [TestMethod] @@ -103,7 +103,10 @@ public void SelectKRandomElements_ElementsAreRandom() // No element is generated more than 20% of the time foreach (var element in slelectedElements) - Assert.IsTrue(slelectedElements.Count(e => e.Equals(element)) < length * 5 * 2 * 0.2, $"{nameof(element)} {element} selected too many times"); + { + var timesSelected = slelectedElements.Count(e => e.Equals(element)); + Assert.IsTrue(timesSelected < length * 5 * 2 * 0.2, $"{nameof(element)} {element} selected too many times ({timesSelected})."); + } } [TestMethod] diff --git a/GeneticAlgorithm.UnitTests/SearchTests.cs b/GeneticAlgorithm.UnitTests/SearchTests.cs index 92c7b31..0a30b54 100644 --- a/GeneticAlgorithm.UnitTests/SearchTests.cs +++ b/GeneticAlgorithm.UnitTests/SearchTests.cs @@ -86,24 +86,35 @@ public void BestChromosome() [TestMethod] public void SearchTimeWithNextTest() { - var sleepTime = 10; var generations = 50; - var populationManager = new TestPopulationManager(new double[] { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 }); + var populationManager = new TestPopulationManager(new double[] { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 }, millisecondsPerGeneration: 10); var engine = new TestGeneticSearchEngineBuilder(10, generations, populationManager).Build(); var stopwatch = new Stopwatch(); stopwatch.Start(); GeneticSearchResult result = null; while (result == null || !result.IsCompleted) - { result = engine.Next(); - Thread.Sleep(sleepTime); - } + + stopwatch.Stop(); + + Assert.IsTrue(stopwatch.Elapsed.TotalMilliseconds * 0.90 < stopwatch.Elapsed.TotalMilliseconds, $"time is too short. {nameof(stopwatch.Elapsed)} = {stopwatch.Elapsed}; {nameof(result.SearchTime)} = {result.SearchTime.TotalMilliseconds}"); + Assert.IsTrue(stopwatch.Elapsed.TotalMilliseconds * 1.1 > stopwatch.Elapsed.TotalMilliseconds, $"time is too long. {nameof(stopwatch.Elapsed)} = {stopwatch.Elapsed}; {nameof(result.SearchTime)} = {result.SearchTime.TotalMilliseconds}"); + } + + [TestMethod] + public void SearchTimeRunTest() + { + var populationManager = new TestPopulationManager(new double[] { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 }); + var engine = new TestGeneticSearchEngineBuilder(10, 50, populationManager).Build(); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + var result = engine.Run(); stopwatch.Stop(); - var time = stopwatch.Elapsed.TotalMilliseconds - sleepTime * generations - - result.SearchTime.TotalMilliseconds; - Assert.IsTrue(time < 0.2 * stopwatch.Elapsed.TotalMilliseconds); + Assert.IsTrue(stopwatch.Elapsed.TotalMilliseconds * 0.90 < stopwatch.Elapsed.TotalMilliseconds, $"time is too short. {nameof(stopwatch.Elapsed)} = {stopwatch.Elapsed}; {nameof(result.SearchTime)} = {result.SearchTime.TotalMilliseconds}"); + Assert.IsTrue(stopwatch.Elapsed.TotalMilliseconds * 1.1> stopwatch.Elapsed.TotalMilliseconds, $"time is too long. {nameof(stopwatch.Elapsed)} = {stopwatch.Elapsed}; {nameof(result.SearchTime)} = {result.SearchTime.TotalMilliseconds}"); } [DataRow(false, RunType.Run)] diff --git a/GeneticAlgorithm.UnitTests/SearchUtilsTests.cs b/GeneticAlgorithm.UnitTests/SearchUtilsTests.cs index 2e719aa..5f6ad92 100644 --- a/GeneticAlgorithm.UnitTests/SearchUtilsTests.cs +++ b/GeneticAlgorithm.UnitTests/SearchUtilsTests.cs @@ -47,7 +47,7 @@ public void CombineTest_BothArraysContainElements() } [TestMethod] - public void CombineTest_FirstArrayEmty() + public void CombineTest_FirstArrayEmpty() { var population1 = new IChromosome[] {}; var population2 = new[] { chromosome1, chromosome2 , chromosome3 }; diff --git a/GeneticAlgorithm.UnitTests/SetAndGetPopulationTests.cs b/GeneticAlgorithm.UnitTests/SetAndGetPopulationTests.cs index c542251..e9e9ef4 100644 --- a/GeneticAlgorithm.UnitTests/SetAndGetPopulationTests.cs +++ b/GeneticAlgorithm.UnitTests/SetAndGetPopulationTests.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using GeneticAlgorithm.Exceptions; using GeneticAlgorithm.UnitTests.TestUtils; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -26,30 +27,39 @@ public void GetCurrentPopulation_EngineRunning_ThrowException() [ExpectedException(typeof(EngineAlreadyRunningException))] public void SetCurrentPopulation_EngineRunning_ThrowException() { - var engine = - new TestGeneticSearchEngineBuilder(2, int.MaxValue, new TestPopulationManager(new double[] { 2, 2 })).Build(); - - Task.Run(() => engine.Run()); - while (!engine.IsRunning) ; - - engine.SetCurrentPopulation(new double[] { 3, 3 }.ToChromosomes("Converted")); - - Assert.Fail("Should have thrown an exception by now"); + Utils.RunTimedTest(() => + { + var engine = + new TestGeneticSearchEngineBuilder(2, int.MaxValue, new TestPopulationManager(new double[] { 2, 2 })).Build(); + + Task.Run(() => engine.Run()); + while (!engine.IsRunning) ; + + engine.SetCurrentPopulation(new double[] { 3, 3 }.ToChromosomes("Converted")); + + if (engine.IsRunning) + Assert.Fail("Should have thrown an exception."); + else + Assert.Fail("For some reason, the engine is no longer running."); + }); } [TestMethod] [ExpectedException(typeof(GeneticAlgorithmException))] public void SetCurrentPopulation_WrongNumberOfChromosomes_ThrowException() { - var engine = + Utils.RunTimedTest(() => + { + var engine = new TestGeneticSearchEngineBuilder(2, int.MaxValue, new TestPopulationManager(new double[] { 2, 2 })).Build(); - Task.Run(() => engine.Run()); - while (!engine.IsRunning) ; + Task.Run(() => engine.Run()); + while (!engine.IsRunning) ; - engine.SetCurrentPopulation(new double[] { 3, 3, 3 }.ToChromosomes("Converted")); + engine.SetCurrentPopulation(new double[] { 3, 3, 3 }.ToChromosomes("Converted")); - Assert.Fail("Should have thrown an exception by now"); + Assert.Fail("Should have thrown an exception by now"); + }); } [TestMethod] diff --git a/GeneticAlgorithm.UnitTests/StartAndPauseTests.cs b/GeneticAlgorithm.UnitTests/StartAndPauseTests.cs index 654dae5..17b694d 100644 --- a/GeneticAlgorithm.UnitTests/StartAndPauseTests.cs +++ b/GeneticAlgorithm.UnitTests/StartAndPauseTests.cs @@ -38,7 +38,7 @@ public void IsRunningTest(bool engineRunning) if (engineRunning) Task.Run(() => engine.Run()); - Thread.Sleep(10); + Thread.Sleep(100); Assert.AreEqual(engineRunning, engine.IsRunning); } diff --git a/GeneticAlgorithm.UnitTests/StopManagerTests.cs b/GeneticAlgorithm.UnitTests/StopManagerTests.cs index 1864669..9ffe346 100644 --- a/GeneticAlgorithm.UnitTests/StopManagerTests.cs +++ b/GeneticAlgorithm.UnitTests/StopManagerTests.cs @@ -47,14 +47,17 @@ public void StopAtConvergenceTest(RunType runType) [DataRow(RunType.Next)] public void StopIfNoImprovmentTest1(RunType runType) { - var populationManager = new TestPopulationManager(new[] - {new double[] {1, 1, 1}, new double[] {2, 2, 2}, new double[] {3, 3, 3}, new double[] {4, 4, 4}}); - var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, MAX_GENERATIONS, populationManager) - .AddStopManager(new StopIfNoImprovment(3, 3)).IncludeAllHistory().Build(); + Utils.RunTimedTest(() => + { + var populationManager = new TestPopulationManager(new[] + {new double[] {1, 1, 1}, new double[] {2, 2, 2}, new double[] {3, 3, 3}, new double[] {4, 4, 4}}); + var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, MAX_GENERATIONS, populationManager) + .AddStopManager(new StopIfNoImprovment(3, 3)).IncludeAllHistory().Build(); - var result = engine.Run(runType); + var result = engine.Run(runType); - Assert.AreEqual(4, result.Generations); + Assert.AreEqual(4, result.Generations); + }); } [TestMethod] @@ -62,14 +65,17 @@ public void StopIfNoImprovmentTest1(RunType runType) [DataRow(RunType.Next)] public void StopIfNoImprovmentTest2(RunType runType) { - var populationManager = new TestPopulationManager(new[] - {new double[] {1, 1, 1}, new double[] {2, 2.5, 2}, new double[] {3, 3, 3}}); - var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, MAX_GENERATIONS, populationManager) - .AddStopManager(new StopIfNoImprovment(1, 0.9)).IncludeAllHistory().Build(); + Utils.RunTimedTest(() => + { + var populationManager = new TestPopulationManager(new[] + {new double[] {1, 1, 1}, new double[] {2, 2.5, 2}, new double[] {3, 3, 3}}); + var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, MAX_GENERATIONS, populationManager) + .AddStopManager(new StopIfNoImprovment(1, 0.9)).IncludeAllHistory().Build(); - var result = engine.Run(runType); + var result = engine.Run(runType); - Assert.AreEqual(3, result.Generations); + Assert.AreEqual(3, result.Generations); + }); } [TestMethod] @@ -77,17 +83,20 @@ public void StopIfNoImprovmentTest2(RunType runType) [DataRow(RunType.Next)] public void StopIfNoImprovmentTest3(RunType runType) { - var populationManager = new TestPopulationManager(new[] - { + Utils.RunTimedTest(() => + { + var populationManager = new TestPopulationManager(new[] + { new double[] {1, 1, 1}, new double[] {2, 2, 2}, new double[] {3, 3, 3}, new double[] {4, 4, 4}, new double[] {4, 4, 4}, new double[] {4, 4, 4} - }); - var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, MAX_GENERATIONS, populationManager) - .AddStopManager(new StopIfNoImprovment(2, 0.9)).IncludeAllHistory().Build(); + }); + var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, MAX_GENERATIONS, populationManager) + .AddStopManager(new StopIfNoImprovment(2, 0.9)).IncludeAllHistory().Build(); - var result = engine.Run(runType); + var result = engine.Run(runType); - Assert.AreEqual(6, result.Generations); + Assert.AreEqual(6, result.Generations); + }); } [TestMethod] @@ -95,46 +104,52 @@ public void StopIfNoImprovmentTest3(RunType runType) [DataRow(RunType.Next)] public void StopManagerGetsRightInfoTest(RunType runType) { - var generation = 0; - var stopManager = A.Fake(); - A.CallTo(() => stopManager.ShouldStop(A._, A._, A._)).Invokes( - (Population p, IEnvironment e, int g) => - { - Assert.AreEqual(generation, g, "Wrong generation"); - foreach (var chromosome in p.GetChromosomes()) - Assert.AreEqual(generation + 1, chromosome.Evaluate(), "Wrong chromosome"); - foreach (var evaluation in p.GetEvaluations()) - Assert.AreEqual(generation + 1, evaluation, "Wrong evaluation"); - generation++; - - }); - var populationManager = new TestPopulationManager(new[] - {new double[] {1, 1, 1}, new double[] {2, 2, 2}, new double[] {3, 3, 3}}); - var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, 3, populationManager) - .AddStopManager(stopManager).IncludeAllHistory().Build(); - - engine.Run(runType); + Utils.RunTimedTest(() => + { + var generation = 0; + var stopManager = A.Fake(); + A.CallTo(() => stopManager.ShouldStop(A._, A._, A._)).Invokes( + (Population p, IEnvironment e, int g) => + { + Assert.AreEqual(generation, g, "Wrong generation"); + foreach (var chromosome in p.GetChromosomes()) + Assert.AreEqual(generation + 1, chromosome.Evaluate(), "Wrong chromosome"); + foreach (var evaluation in p.GetEvaluations()) + Assert.AreEqual(generation + 1, evaluation, "Wrong evaluation"); + generation++; + + }); + var populationManager = new TestPopulationManager(new[] + {new double[] {1, 1, 1}, new double[] {2, 2, 2}, new double[] {3, 3, 3}}); + var engine = new TestGeneticSearchEngineBuilder(POPULATION_SIZE, 3, populationManager) + .AddStopManager(stopManager).IncludeAllHistory().Build(); + + engine.Run(runType); + }); } [TestMethod] public void AddMultipleStopManagers_AllManagersAreCalled() { - bool manager1Called = false, manager2Called = false; - var testPopulationManager = new TestPopulationManager(new double[] { 1, 1, 1, 1, 1 }); - var managers = new[] { A.Fake(), A.Fake() }; - A.CallTo(() => managers[0].ShouldStop(A._, A._, A._)) - .Invokes((Population p, IEnvironment e, int g) => manager1Called = true); - A.CallTo(() => managers[1].ShouldStop(A._, A._, A._)) - .Invokes((Population p, IEnvironment e, int g) => manager2Called = true); - - var engine = new TestGeneticSearchEngineBuilder(5, 10, testPopulationManager) - .AddStopManagers(managers) - .Build(); - - engine.Next(); - - Assert.IsTrue(manager1Called); - Assert.IsTrue(manager2Called); + Utils.RunTimedTest(() => + { + bool manager1Called = false, manager2Called = false; + var testPopulationManager = new TestPopulationManager(new double[] { 1, 1, 1, 1, 1 }); + var managers = new[] { A.Fake(), A.Fake() }; + A.CallTo(() => managers[0].ShouldStop(A._, A._, A._)) + .Invokes((Population p, IEnvironment e, int g) => manager1Called = true); + A.CallTo(() => managers[1].ShouldStop(A._, A._, A._)) + .Invokes((Population p, IEnvironment e, int g) => manager2Called = true); + + var engine = new TestGeneticSearchEngineBuilder(5, 10, testPopulationManager) + .AddStopManagers(managers) + .Build(); + + engine.Next(); + + Assert.IsTrue(manager1Called); + Assert.IsTrue(manager2Called); + }); } } } diff --git a/GeneticAlgorithm.UnitTests/TestUtils/Assertions.cs b/GeneticAlgorithm.UnitTests/TestUtils/Assertions.cs index 8568e64..7e9afaf 100644 --- a/GeneticAlgorithm.UnitTests/TestUtils/Assertions.cs +++ b/GeneticAlgorithm.UnitTests/TestUtils/Assertions.cs @@ -92,10 +92,10 @@ public static void AssertThrowAggretateExceptionOfType(Action action, Type excep } } - public static void AssertIsWithinRange(this int value, double expactedValue, double margenOfError) + public static void AssertIsWithinRange(this int value, double expactedValue, double marginOfError) { - Assert.IsTrue(value >= expactedValue - margenOfError, $"Value ({value}) is too small (expacted value = {expactedValue})"); - Assert.IsTrue(value <= expactedValue + margenOfError, $"Value ({value}) is too big (expacted value = {expactedValue})"); + Assert.IsTrue(value >= expactedValue - marginOfError, $"Value ({value}) is too small (expacted value = {expactedValue})"); + Assert.IsTrue(value <= expactedValue + marginOfError, $"Value ({value}) is too big (expacted value = {expactedValue})"); } } } diff --git a/GeneticAlgorithm.UnitTests/TestUtils/TestPopulationManager.cs b/GeneticAlgorithm.UnitTests/TestUtils/TestPopulationManager.cs index 678240b..05bfe43 100644 --- a/GeneticAlgorithm.UnitTests/TestUtils/TestPopulationManager.cs +++ b/GeneticAlgorithm.UnitTests/TestUtils/TestPopulationManager.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using FakeItEasy; using GeneticAlgorithm.Interfaces; @@ -23,7 +24,7 @@ public TestPopulationManager(double[][] populationEvaluation) }); } - public TestPopulationManager(double[] populationEvaluation, Func nextGenerationEvaluationFunc = null) + public TestPopulationManager(double[] populationEvaluation, Func nextGenerationEvaluationFunc = null, int millisecondsPerGeneration = 0) { GenerateInitailPopulation(populationEvaluation); @@ -33,6 +34,7 @@ public TestPopulationManager(double[] populationEvaluation, Func childrenGenerator.GenerateChildren(A._, A._, A._, A._)).ReturnsLazily( (Population p, int n, int g, IEnvironment e) => { + Thread.Sleep(millisecondsPerGeneration); index++; var newEvaluation = new double[populationEvaluation.Length]; diff --git a/GeneticAlgorithm.UnitTests/TestUtils/Utils.cs b/GeneticAlgorithm.UnitTests/TestUtils/Utils.cs index e663eb5..60d3713 100644 --- a/GeneticAlgorithm.UnitTests/TestUtils/Utils.cs +++ b/GeneticAlgorithm.UnitTests/TestUtils/Utils.cs @@ -1,5 +1,7 @@ using GeneticAlgorithm.Components.Chromosomes; using GeneticAlgorithm.Interfaces; +using System; +using System.Threading.Tasks; namespace GeneticAlgorithm.UnitTests.TestUtils { @@ -47,5 +49,26 @@ public static int ToInt(this T value) /// public static T[] ToArray(this IChromosome chromosome) => ((VectorChromosome)chromosome).GetVector(); + + /// + /// If the action dosn't finish in time, the test is terminated and failes. + /// + public static void RunTimedTest(Action action, TimeSpan? time = null) + { + if (time == null) + time = TimeSpan.FromSeconds(1); + + var task = Task.Run(action); + try + { + task.Wait(time.Value); + } + catch (AggregateException e) + { + throw e.InnerException; + } + if (!task.IsCompleted) + throw new Exception($"{nameof(action)} didn't finish in time."); + } } } diff --git a/GeneticAlgorithm.UnitTests/UpdatePopulationTests.cs b/GeneticAlgorithm.UnitTests/UpdatePopulationTests.cs index 6113af2..bbefece 100644 --- a/GeneticAlgorithm.UnitTests/UpdatePopulationTests.cs +++ b/GeneticAlgorithm.UnitTests/UpdatePopulationTests.cs @@ -110,18 +110,21 @@ public void RenewPopulationViaManager_RenewedPopulationUpdated() [TestMethod] public void ConvertPopulation_NewPopulationUpdated() { - var initialPopulation = new double[] { 2, 2 }; - var newPopulation = new double[] { 3, 3 }; - var populationManager = new TestPopulationManager(initialPopulation); + Utils.RunTimedTest(() => + { + var initialPopulation = new double[] { 2, 2 }; + var newPopulation = new double[] { 3, 3 }; + var populationManager = new TestPopulationManager(initialPopulation); - var engine = CreateEngineBuilder(populationManager).Build(); - engine.OnNewGeneration += populationUpdatedOnEvent.Save; + var engine = CreateEngineBuilder(populationManager).Build(); + engine.OnNewGeneration += populationUpdatedOnEvent.Save; - engine.Next(); + engine.Next(); - engine.SetCurrentPopulation(newPopulation.ToChromosomes("Converted")); + engine.SetCurrentPopulation(newPopulation.ToChromosomes("Converted")); - AssertManagersUpdated(new[] { initialPopulation, newPopulation }); + AssertManagersUpdated(new[] { initialPopulation, newPopulation }); + }); } [TestMethod] diff --git a/GeneticAlgorithm/ChildrenGenerator.cs b/GeneticAlgorithm/ChildrenGenerator.cs index 36b2d84..6314fd8 100644 --- a/GeneticAlgorithm/ChildrenGenerator.cs +++ b/GeneticAlgorithm/ChildrenGenerator.cs @@ -60,7 +60,7 @@ private void CheckMuationProbability(double probability) { if (probability >= 0 && probability <= 1) return; - if (mutationManager.GetType() == typeof(BassicMutationProbabilityManager)) + if (mutationManager.GetType() == typeof(BasicMutationProbabilityManager)) throw new InternalSearchException( $"Code 1004 (Bad mutation value for manager {mutationManager.GetType()})"); throw new BadMutationProbabilityException(probability); diff --git a/GeneticAlgorithm/Components/ComponetsUtils.cs b/GeneticAlgorithm/Components/ComponetsUtils.cs index cfcd27c..464cc73 100644 --- a/GeneticAlgorithm/Components/ComponetsUtils.cs +++ b/GeneticAlgorithm/Components/ComponetsUtils.cs @@ -4,10 +4,10 @@ namespace GeneticAlgorithm.Components { static class ComponetsUtils { - public static (int, int) GetTwoRandomNumbers(this Random random, int max) + public static (int, int) GetTwoRandomNumbers(int max) { - var num1 = random.Next(max); - var num2 = random.Next(max); + var num1 = ProbabilityUtils.GetRandomInt(0, max); + var num2 = ProbabilityUtils.GetRandomInt(0, max); return (Math.Min(num1, num2), Math.Max(num1, num2)); } diff --git a/GeneticAlgorithm/Components/CrossoverManagers/EdgeRecombinationCrossover.cs b/GeneticAlgorithm/Components/CrossoverManagers/EdgeRecombinationCrossover.cs index 79d67e0..997a1df 100644 --- a/GeneticAlgorithm/Components/CrossoverManagers/EdgeRecombinationCrossover.cs +++ b/GeneticAlgorithm/Components/CrossoverManagers/EdgeRecombinationCrossover.cs @@ -23,7 +23,6 @@ public class EdgeRecombinationCrossover : ICrossoverManager { private readonly IMutationManager mutationManager; private readonly IEvaluator evaluator; - private readonly Random random = new Random(); /// /// EdgeRecombinationCrossover Works on chromosomes of type VectorChromosome<T>. @@ -44,7 +43,7 @@ public IChromosome Crossover(IChromosome chromosome1, IChromosome chromosome2) var vector1 = ((VectorChromosome)chromosome1).GetVector(); var vector2 = ((VectorChromosome)chromosome2).GetVector(); var length = vector1.Length; - var firstElement = vector1[random.Next(0, vector1.Length)]; + var firstElement = vector1[ProbabilityUtils.GetRandomInt(0, vector1.Length)]; var childArray = new NonReapetingAdjacencyMatrix(vector1, vector2, true).Crossover(firstElement, length); return new VectorChromosome(childArray, mutationManager, evaluator); diff --git a/GeneticAlgorithm/Components/CrossoverManagers/GeneralEdgeRecombinationCrossover.cs b/GeneticAlgorithm/Components/CrossoverManagers/GeneralEdgeRecombinationCrossover.cs index 660950c..8df38da 100644 --- a/GeneticAlgorithm/Components/CrossoverManagers/GeneralEdgeRecombinationCrossover.cs +++ b/GeneticAlgorithm/Components/CrossoverManagers/GeneralEdgeRecombinationCrossover.cs @@ -16,7 +16,6 @@ public class GeneralEdgeRecombinationCrossover : ICrossoverManager { private readonly IMutationManager mutationManager; private readonly IEvaluator evaluator; - private readonly Random random = new Random(); public GeneralEdgeRecombinationCrossover(IMutationManager mutationManager, IEvaluator evaluator) { @@ -29,7 +28,7 @@ public IChromosome Crossover(IChromosome chromosome1, IChromosome chromosome2) var vector1 = ((VectorChromosome)chromosome1).GetVector(); var vector2 = ((VectorChromosome)chromosome2).GetVector(); var length = vector1.Length; - var firstElement = vector1[random.Next(0, vector1.Length)]; + var firstElement = vector1[ProbabilityUtils.GetRandomInt(0, vector1.Length)]; var childArray = new ReapetingAdjacencyMatrix(vector1, vector2).Crossover(firstElement, length); return new VectorChromosome(childArray, mutationManager, evaluator); diff --git a/GeneticAlgorithm/Components/CrossoverManagers/HeuristicCrossover.cs b/GeneticAlgorithm/Components/CrossoverManagers/HeuristicCrossover.cs index 8786dc2..bb4718f 100644 --- a/GeneticAlgorithm/Components/CrossoverManagers/HeuristicCrossover.cs +++ b/GeneticAlgorithm/Components/CrossoverManagers/HeuristicCrossover.cs @@ -23,7 +23,6 @@ public class HeuristicCrossover : ICrossoverManager { private readonly IMutationManager mutationManager; private readonly IEvaluator evaluator; - private readonly Random random = new Random(); /// /// HeuristicCrossover Works on chromosomes of type VectorChromosome<T>. @@ -44,7 +43,7 @@ public IChromosome Crossover(IChromosome chromosome1, IChromosome chromosome2) var vector1 = ((VectorChromosome)chromosome1).GetVector(); var vector2 = ((VectorChromosome)chromosome2).GetVector(); var length = vector1.Length; - var firstElement = vector1[random.Next(0, vector1.Length)]; + var firstElement = vector1[ProbabilityUtils.GetRandomInt(0, vector1.Length)]; var childArray = new NonReapetingAdjacencyMatrix(vector1, vector2, false).Crossover(firstElement, length); return new VectorChromosome(childArray, mutationManager, evaluator); diff --git a/GeneticAlgorithm/Components/CrossoverManagers/OrderBasedCrossover.cs b/GeneticAlgorithm/Components/CrossoverManagers/OrderBasedCrossover.cs index 85dd20e..5a6b4c1 100644 --- a/GeneticAlgorithm/Components/CrossoverManagers/OrderBasedCrossover.cs +++ b/GeneticAlgorithm/Components/CrossoverManagers/OrderBasedCrossover.cs @@ -22,7 +22,6 @@ namespace GeneticAlgorithm.Components.CrossoverManagers /// public class OrderBasedCrossover : ICrossoverManager { - private readonly Random random = new Random(); private readonly IMutationManager mutationManager; private readonly IEvaluator evaluator; @@ -45,7 +44,7 @@ public IChromosome Crossover(IChromosome chromosome1, IChromosome chromosome2) var vector1 = ((VectorChromosome)chromosome1).GetVector(); var vector2 = ((VectorChromosome)chromosome2).GetVector(); var length = vector1.Length; - var indexes = ProbabilityUtils.SelectKRandomNumbersNonRepeating(length, random.Next(length)); + var indexes = ProbabilityUtils.SelectKRandomNumbersNonRepeating(length, ProbabilityUtils.GetRandomInt(0, length)); var elementsFromParent2 = new HashSet(); var elementsFromParent2OrderedByIndex = new List(); diff --git a/GeneticAlgorithm/Components/CrossoverManagers/OrderCrossover.cs b/GeneticAlgorithm/Components/CrossoverManagers/OrderCrossover.cs index 26a57a9..d72e2c2 100644 --- a/GeneticAlgorithm/Components/CrossoverManagers/OrderCrossover.cs +++ b/GeneticAlgorithm/Components/CrossoverManagers/OrderCrossover.cs @@ -45,7 +45,7 @@ public IChromosome Crossover(IChromosome chromosome1, IChromosome chromosome2) var vector2 = ((VectorChromosome) chromosome2).GetVector(); var length = vector1.Length; - (var start, var end) = random.GetTwoRandomNumbers(length + 1); + (var start, var end) = ComponetsUtils.GetTwoRandomNumbers(length + 1); var genomesFromChromosome1 = new HashSet(); for (int i = start; i < end; i++) genomesFromChromosome1.Add(vector1[i]); diff --git a/GeneticAlgorithm/Components/CrossoverManagers/PartiallyMappedCrossover.cs b/GeneticAlgorithm/Components/CrossoverManagers/PartiallyMappedCrossover.cs index 1d71540..3a612b4 100644 --- a/GeneticAlgorithm/Components/CrossoverManagers/PartiallyMappedCrossover.cs +++ b/GeneticAlgorithm/Components/CrossoverManagers/PartiallyMappedCrossover.cs @@ -20,7 +20,6 @@ namespace GeneticAlgorithm.Components.CrossoverManagers /// public class PartiallyMappedCrossover : ICrossoverManager { - private readonly Random random = new Random(); private readonly IMutationManager mutationManager; private readonly IEvaluator evaluator; @@ -45,7 +44,7 @@ public IChromosome Crossover(IChromosome chromosome1, IChromosome chromosome2) var indexManager = new IndexManager(vector2); var length = vector1.Length; - (var start, var end) = random.GetTwoRandomNumbers(length + 1); + (var start, var end) = ComponetsUtils.GetTwoRandomNumbers(length + 1); var addedIndexes = new List(); var genomesFromChromosome1 = new List(); for (int i = start; i < end; i++) diff --git a/GeneticAlgorithm/Components/CrossoverManagers/PositionBasedCrossoverManager.cs b/GeneticAlgorithm/Components/CrossoverManagers/PositionBasedCrossoverManager.cs index 5195910..17a1522 100644 --- a/GeneticAlgorithm/Components/CrossoverManagers/PositionBasedCrossoverManager.cs +++ b/GeneticAlgorithm/Components/CrossoverManagers/PositionBasedCrossoverManager.cs @@ -21,7 +21,6 @@ namespace GeneticAlgorithm.Components.CrossoverManagers /// public class PositionBasedCrossoverManager : ICrossoverManager { - private readonly Random random = new Random(); private readonly IMutationManager mutationManager; private readonly IEvaluator evaluator; @@ -44,7 +43,7 @@ public IChromosome Crossover(IChromosome chromosome1, IChromosome chromosome2) var vector1 = ((VectorChromosome)chromosome1).GetVector(); var vector2 = ((VectorChromosome)chromosome2).GetVector(); var length = vector1.Length; - var indexesToTakeFromParent1 = ProbabilityUtils.SelectKRandomNumbersNonRepeating(length, random.Next(length)); + var indexesToTakeFromParent1 = ProbabilityUtils.SelectKRandomNumbersNonRepeating(length, ProbabilityUtils.GetRandomInt(0, length)); var genomesFromChromosome1 = new HashSet(); var newVector = new T[length]; diff --git a/GeneticAlgorithm/Components/CrossoverManagers/Utilities/NonReapetingAdjacencyMatrix.cs b/GeneticAlgorithm/Components/CrossoverManagers/Utilities/NonReapetingAdjacencyMatrix.cs index 527d5d3..75211b0 100644 --- a/GeneticAlgorithm/Components/CrossoverManagers/Utilities/NonReapetingAdjacencyMatrix.cs +++ b/GeneticAlgorithm/Components/CrossoverManagers/Utilities/NonReapetingAdjacencyMatrix.cs @@ -10,7 +10,6 @@ namespace GeneticAlgorithm.Components.CrossoverManagers.Utilities /// public class NonReapetingAdjacencyMatrix : IAdjacencyMatrix { - private readonly Random random = new Random(); private readonly Dictionary> adjacencyMatrix = new Dictionary>(); /// @@ -34,13 +33,13 @@ public T GetNeighbor(T element) var neighbors = adjacencyMatrix[element]; Remove(element, neighbors); if (neighbors.Count == 0) - return adjacencyMatrix.Keys.ElementAt(random.Next(adjacencyMatrix.Count)); + return adjacencyMatrix.Keys.ElementAt(ProbabilityUtils.GetRandomInt(adjacencyMatrix.Count)); if (!selectNeighborWithLeastNeighbors) - return neighbors.ElementAt(random.Next(neighbors.Count)); + return neighbors.ElementAt(ProbabilityUtils.GetRandomInt(neighbors.Count)); var elemenetsWithMinNeighbors = FindNeighborsWithLeaseNeighbors(neighbors); - return elemenetsWithMinNeighbors[random.Next(elemenetsWithMinNeighbors.Count)]; + return elemenetsWithMinNeighbors[ProbabilityUtils.GetRandomInt(elemenetsWithMinNeighbors.Count)]; } private List FindNeighborsWithLeaseNeighbors(LinkedList neighbors) diff --git a/GeneticAlgorithm/Components/CrossoverManagers/Utilities/ReapetingAdjacencyMatrix.cs b/GeneticAlgorithm/Components/CrossoverManagers/Utilities/ReapetingAdjacencyMatrix.cs index d163b7f..74c4e38 100644 --- a/GeneticAlgorithm/Components/CrossoverManagers/Utilities/ReapetingAdjacencyMatrix.cs +++ b/GeneticAlgorithm/Components/CrossoverManagers/Utilities/ReapetingAdjacencyMatrix.cs @@ -10,17 +10,15 @@ namespace GeneticAlgorithm.Components.CrossoverManagers.Utilities /// public class ReapetingAdjacencyMatrix : IAdjacencyMatrix { - private readonly Random random = new Random(); private readonly Dictionary> adjacencyMatrix = new Dictionary>(); - public T GetNeighbor(T element) { var neighbors = adjacencyMatrix[element]; if (neighbors.Count == 0) - return adjacencyMatrix.Keys.ElementAt(random.Next(adjacencyMatrix.Count)); + return adjacencyMatrix.Keys.ElementAt(ProbabilityUtils.GetRandomInt(adjacencyMatrix.Count)); - return neighbors.ElementAt(random.Next(neighbors.Count)); + return neighbors.ElementAt(ProbabilityUtils.GetRandomInt(neighbors.Count)); } public ReapetingAdjacencyMatrix(T[] vector1, T[] vector2) diff --git a/GeneticAlgorithm/Components/MutationManagers/DisplacementMutationBase.cs b/GeneticAlgorithm/Components/MutationManagers/DisplacementMutationBase.cs index e6ce34f..16c6c11 100644 --- a/GeneticAlgorithm/Components/MutationManagers/DisplacementMutationBase.cs +++ b/GeneticAlgorithm/Components/MutationManagers/DisplacementMutationBase.cs @@ -14,7 +14,6 @@ namespace GeneticAlgorithm.Components.MutationManagers /// class DisplacementMutationBase : IMutationManager { - private readonly Random random = new Random(); private readonly bool inversionSttretch; public DisplacementMutationBase(bool inversionSttretch) @@ -24,8 +23,8 @@ public DisplacementMutationBase(bool inversionSttretch) public T[] Mutate(T[] vector) { - (var start, var end) = random.GetTwoRandomNumbers(vector.Length + 1); - var insertionIndex = random.Next(vector.Length - (end - start)); + (var start, var end) = ComponetsUtils.GetTwoRandomNumbers(vector.Length + 1); + var insertionIndex = ProbabilityUtils.GetRandomInt(vector.Length - (end - start)); var vectorWithoutStretch = new T[vector.Length - (end - start)]; for (int i = 0; i < start; i++) vectorWithoutStretch[i] = vector[i]; diff --git a/GeneticAlgorithm/Components/MutationManagers/DoubleUniformMutationManager.cs b/GeneticAlgorithm/Components/MutationManagers/DoubleUniformMutationManager.cs index 946d207..82e6fe5 100644 --- a/GeneticAlgorithm/Components/MutationManagers/DoubleUniformMutationManager.cs +++ b/GeneticAlgorithm/Components/MutationManagers/DoubleUniformMutationManager.cs @@ -11,7 +11,6 @@ public class DoubleUniformMutationManager : IMutationManager { private readonly double minValue; private readonly double range; - private readonly Random random = new Random(); public DoubleUniformMutationManager(double minValue, double maxValue) { @@ -23,7 +22,7 @@ public double[] Mutate(double[] vector) { for (int i = 0; i < vector.Length; i++) if (ProbabilityUtils.P(1.0 / vector.Length)) - vector[i] = minValue + random.NextDouble() * range; + vector[i] = minValue + ProbabilityUtils.GetRandomDouble() * range; return vector; } diff --git a/GeneticAlgorithm/Components/MutationManagers/ExchangeMutationManager.cs b/GeneticAlgorithm/Components/MutationManagers/ExchangeMutationManager.cs index 618c0f9..f3d66c9 100644 --- a/GeneticAlgorithm/Components/MutationManagers/ExchangeMutationManager.cs +++ b/GeneticAlgorithm/Components/MutationManagers/ExchangeMutationManager.cs @@ -9,12 +9,10 @@ namespace GeneticAlgorithm.Components.MutationManagers /// public class ExchangeMutationManager : IMutationManager { - private readonly Random random = new Random(); - public T[] Mutate(T[] vector) { - int from = random.Next(vector.Length); - int to = random.Next(vector.Length); + int from = ProbabilityUtils.GetRandomInt(vector.Length); + int to = ProbabilityUtils.GetRandomInt(vector.Length); var temp = vector[to]; vector[to] = vector[from]; diff --git a/GeneticAlgorithm/Components/MutationManagers/InsertionMutationManager.cs b/GeneticAlgorithm/Components/MutationManagers/InsertionMutationManager.cs index 488b475..cbf0f58 100644 --- a/GeneticAlgorithm/Components/MutationManagers/InsertionMutationManager.cs +++ b/GeneticAlgorithm/Components/MutationManagers/InsertionMutationManager.cs @@ -9,12 +9,10 @@ namespace GeneticAlgorithm.Components.MutationManagers /// public class InsertionMutationManager : IMutationManager { - private readonly Random random = new Random(); - public T[] Mutate(T[] vector) { - var toRemove = random.Next(vector.Length); - var insertAt = random.Next(vector.Length); + var toRemove = ProbabilityUtils.GetRandomInt(vector.Length); + var insertAt = ProbabilityUtils.GetRandomInt(vector.Length); var newVector = new T[vector.Length]; if (toRemove < insertAt) diff --git a/GeneticAlgorithm/Components/MutationManagers/IntUniformMutationManager.cs b/GeneticAlgorithm/Components/MutationManagers/IntUniformMutationManager.cs index f0bbc9b..3165eaf 100644 --- a/GeneticAlgorithm/Components/MutationManagers/IntUniformMutationManager.cs +++ b/GeneticAlgorithm/Components/MutationManagers/IntUniformMutationManager.cs @@ -11,7 +11,6 @@ public class IntUniformMutationManager : IMutationManager { private readonly int minValue; private readonly int maxValue; - private readonly Random random = new Random(); public IntUniformMutationManager(int minValue, int maxValue) { @@ -23,7 +22,7 @@ public int[] Mutate(int[] vector) { for (int i = 0; i < vector.Length; i++) if (ProbabilityUtils.P(1.0 / vector.Length)) - vector[i] = random.Next(minValue, maxValue + 1); + vector[i] = ProbabilityUtils.GetRandomInt(minValue, maxValue + 1); return vector; } diff --git a/GeneticAlgorithm/Components/MutationManagers/ScrambleMutationManager.cs b/GeneticAlgorithm/Components/MutationManagers/ScrambleMutationManager.cs index 8bd4c1f..81b7dea 100644 --- a/GeneticAlgorithm/Components/MutationManagers/ScrambleMutationManager.cs +++ b/GeneticAlgorithm/Components/MutationManagers/ScrambleMutationManager.cs @@ -10,12 +10,10 @@ namespace GeneticAlgorithm.Components.MutationManagers /// public class ScrambleMutationManager : IMutationManager { - private readonly Random random = new Random(); - public T[] Mutate(T[] vector) { - (var start, var end) = random.GetTwoRandomNumbers(vector.Length + 1); - var scrambledGenomes = vector.Skip(start).Take(end - start).ToArray().Shuffle(random); + (var start, var end) = ComponetsUtils.GetTwoRandomNumbers(vector.Length + 1); + var scrambledGenomes = vector.Skip(start).Take(end - start).ToArray().Shuffle(); var newVector = new T[vector.Length]; for (int i = 0; i < start; i++) diff --git a/GeneticAlgorithm/Components/MutationManagers/SimpleInversionMutationManager.cs b/GeneticAlgorithm/Components/MutationManagers/SimpleInversionMutationManager.cs index c73005f..95691a0 100644 --- a/GeneticAlgorithm/Components/MutationManagers/SimpleInversionMutationManager.cs +++ b/GeneticAlgorithm/Components/MutationManagers/SimpleInversionMutationManager.cs @@ -9,11 +9,9 @@ namespace GeneticAlgorithm.Components.MutationManagers /// public class SimpleInversionMutationManager : IMutationManager { - private readonly Random random = new Random(); - public T[] Mutate(T[] vector) { - (var start, var end) = random.GetTwoRandomNumbers(vector.Length + 1); + (var start, var end) = ComponetsUtils.GetTwoRandomNumbers(vector.Length + 1); var newVector = new T[vector.Length]; for (int i = 0; i < start; i++) diff --git a/GeneticAlgorithm/Exceptions/GeneticAlgorithmArgumentException.cs b/GeneticAlgorithm/Exceptions/GeneticAlgorithmArgumentException.cs new file mode 100644 index 0000000..0f4683f --- /dev/null +++ b/GeneticAlgorithm/Exceptions/GeneticAlgorithmArgumentException.cs @@ -0,0 +1,16 @@ +namespace GeneticAlgorithm.Exceptions +{ + public class GeneticAlgorithmArgumentException : GeneticAlgorithmException + { + public GeneticAlgorithmArgumentException(string message) : + base(message) + { + } + + /// + /// Returns an exception stating that argument name was smaller than zero. + /// + public static GeneticAlgorithmArgumentException SmallerThanZeroException(string argumentName, double value) => + new GeneticAlgorithmArgumentException($"{argumentName} was {value}. {argumentName} must be at least zero!"); + } +} diff --git a/GeneticAlgorithm/GeneticSearchEngineBuilder.cs b/GeneticAlgorithm/GeneticSearchEngineBuilder.cs index f2d8403..39cf404 100644 --- a/GeneticAlgorithm/GeneticSearchEngineBuilder.cs +++ b/GeneticAlgorithm/GeneticSearchEngineBuilder.cs @@ -175,7 +175,7 @@ protected void PreBuildActions() environment = new DefaultEnvironment(); if (mutationManager == null) - mutationManager = new BassicMutationProbabilityManager(mutationProbability); + mutationManager = new BasicMutationProbabilityManager(mutationProbability); } public virtual GeneticSearchEngine Build() diff --git a/GeneticAlgorithm/MutationProbabilityManagers/BassicMutationProbabilityManager.cs b/GeneticAlgorithm/MutationProbabilityManagers/BasicMutationProbabilityManager.cs similarity index 81% rename from GeneticAlgorithm/MutationProbabilityManagers/BassicMutationProbabilityManager.cs rename to GeneticAlgorithm/MutationProbabilityManagers/BasicMutationProbabilityManager.cs index 3adbdd7..a7c2fcc 100644 --- a/GeneticAlgorithm/MutationProbabilityManagers/BassicMutationProbabilityManager.cs +++ b/GeneticAlgorithm/MutationProbabilityManagers/BasicMutationProbabilityManager.cs @@ -3,11 +3,11 @@ namespace GeneticAlgorithm.MutationManagers { - public class BassicMutationProbabilityManager : IMutationProbabilityManager + public class BasicMutationProbabilityManager : IMutationProbabilityManager { private readonly double mutation; - public BassicMutationProbabilityManager(double mutation) + public BasicMutationProbabilityManager(double mutation) { if (mutation > 1 || mutation < 0) throw new GeneticAlgorithmException(nameof(mutation) + " must be between 0.0 to 1.0 (including)"); diff --git a/GeneticAlgorithm/PopulationRenwalManagers/PopulationRenwalUtils.cs b/GeneticAlgorithm/PopulationRenwalManagers/PopulationRenwalUtils.cs new file mode 100644 index 0000000..2d421ea --- /dev/null +++ b/GeneticAlgorithm/PopulationRenwalManagers/PopulationRenwalUtils.cs @@ -0,0 +1,16 @@ +using GeneticAlgorithm.Exceptions; + +namespace GeneticAlgorithm.PopulationRenwalManagers +{ + static class PopulationRenwalUtils + { + public static void VerifyPrecentageToRenew(this double precentageToRenew) + { + if (precentageToRenew <= 0) + throw new GeneticAlgorithmArgumentException($"{nameof(precentageToRenew)} can't be smaller or equale to zero (was {precentageToRenew})"); + if (precentageToRenew > 1) + throw new GeneticAlgorithmArgumentException($"{nameof(precentageToRenew)} can't be greater than one (was {precentageToRenew})"); + + } + } +} diff --git a/GeneticAlgorithm/PopulationRenwalManagers/RenewAtConvergence.cs b/GeneticAlgorithm/PopulationRenwalManagers/RenewAtConvergence.cs index 1b0ed2d..200db00 100644 --- a/GeneticAlgorithm/PopulationRenwalManagers/RenewAtConvergence.cs +++ b/GeneticAlgorithm/PopulationRenwalManagers/RenewAtConvergence.cs @@ -1,4 +1,5 @@ -using GeneticAlgorithm.Interfaces; +using GeneticAlgorithm.Exceptions; +using GeneticAlgorithm.Interfaces; namespace GeneticAlgorithm.PopulationRenwalManagers { @@ -15,6 +16,8 @@ public class RenewAtConvergence : IPopulationRenwalManager /// public RenewAtConvergence(double diff, double precentageToRenew) { + precentageToRenew.VerifyPrecentageToRenew(); + this.precentageToRenew = precentageToRenew; stopManager = new StopManagers.StopAtConvergence(diff); } diff --git a/GeneticAlgorithm/PopulationRenwalManagers/RenewAtDifferenceBetweenAverageAndMaximumFitness.cs b/GeneticAlgorithm/PopulationRenwalManagers/RenewAtDifferenceBetweenAverageAndMaximumFitness.cs new file mode 100644 index 0000000..ce56b00 --- /dev/null +++ b/GeneticAlgorithm/PopulationRenwalManagers/RenewAtDifferenceBetweenAverageAndMaximumFitness.cs @@ -0,0 +1,38 @@ +using GeneticAlgorithm.Exceptions; +using GeneticAlgorithm.Interfaces; +using System.Linq; + +namespace GeneticAlgorithm.PopulationRenwalManagers +{ + /// + /// Will renew "precentageToRenew" of the population when the difference between the average and max evaluation is equal to or less than "diff". + /// + public class RenewAtDifferenceBetweenAverageAndMaximumFitness : IPopulationRenwalManager + { + private readonly double precentageToRenew; + private readonly double diff; + + /// + /// Will renew "precentageToRenew" of the population when the difference between the min evaluation and max evaluation is equal to or less than "diff". + /// + public RenewAtDifferenceBetweenAverageAndMaximumFitness(double diff, double precentageToRenew) + { + precentageToRenew.VerifyPrecentageToRenew(); + this.precentageToRenew = precentageToRenew; + this.diff = diff >= 0 ? diff : throw GeneticAlgorithmArgumentException.SmallerThanZeroException(nameof(diff), diff); ; + } + + public void AddGeneration(Population population) + { + // nothing to do here + } + + public double ShouldRenew(Population population, IEnvironment environment, int generation) + { + var max = population.Select(c => c.Evaluation).Max(); + var average = population.Select(c => c.Evaluation).Average(); + + return max - average < diff ? precentageToRenew : 0; + } + } +} diff --git a/GeneticAlgorithm/PopulationRenwalManagers/RenewIfNoImprovment.cs b/GeneticAlgorithm/PopulationRenwalManagers/RenewIfNoImprovment.cs index 28d05f9..5e3fa88 100644 --- a/GeneticAlgorithm/PopulationRenwalManagers/RenewIfNoImprovment.cs +++ b/GeneticAlgorithm/PopulationRenwalManagers/RenewIfNoImprovment.cs @@ -1,4 +1,5 @@ -using GeneticAlgorithm.Interfaces; +using GeneticAlgorithm.Exceptions; +using GeneticAlgorithm.Interfaces; using GeneticAlgorithm.StopManagers; namespace GeneticAlgorithm.PopulationRenwalManagers @@ -16,6 +17,8 @@ public class RenewIfNoImprovment : IPopulationRenwalManager /// public RenewIfNoImprovment(int generationsToConsider, double minImprvment, double precentageToRenew) { + precentageToRenew.VerifyPrecentageToRenew(); + this.precentageToRenew = precentageToRenew; stopManager = new StopIfNoImprovment(generationsToConsider, minImprvment); } diff --git a/GeneticAlgorithm/ProbabilityUtils.cs b/GeneticAlgorithm/ProbabilityUtils.cs index db34b93..fbca18a 100644 --- a/GeneticAlgorithm/ProbabilityUtils.cs +++ b/GeneticAlgorithm/ProbabilityUtils.cs @@ -6,7 +6,38 @@ namespace GeneticAlgorithm { public static class ProbabilityUtils { - private static readonly Random random = new Random(); + private static Random random = new Random(); + private static object randomLockObject = new object(); + + /// + /// Returens a random double between 0 and 1. + /// + public static double GetRandomDouble() + { + // We need this lock, since random isn't thread-safe + lock (randomLockObject) + return random.NextDouble(); + } + + /// + /// Returens a random double between min (inclusive) and max (exclusive). + /// + public static int GetRandomInt(int min, int max) + { + // We need this lock, since random isn't thread-safe + lock (randomLockObject) + return random.Next(min, max); + } + + /// + /// Returens a random double between 0 (inclusive) and max (exclusive). + /// + public static int GetRandomInt(int max) + { + // We need this lock, since random isn't thread-safe + lock (randomLockObject) + return random.Next(max); + } /// /// Returns true with a probability of probability - where probability is between 0 to 1 (including) @@ -16,7 +47,7 @@ public static bool P(double probability) if (probability > 1 || probability < 0) throw new InternalSearchException($"Code 1008 (probability is {probability})"); - return random.NextDouble() < probability; + return GetRandomDouble() < probability; } /// @@ -68,8 +99,8 @@ public static double GaussianDistribution(double sd, double mean) /// private static double GetStandardDistribution() { - var u1 = 1.0 - random.NextDouble(); - var u2 = 1.0 - random.NextDouble(); + var u1 = 1.0 - GetRandomDouble(); + var u2 = 1.0 - GetRandomDouble(); return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); } } diff --git a/GeneticAlgorithm/SearchUtils.cs b/GeneticAlgorithm/SearchUtils.cs index c02cb59..9f59977 100644 --- a/GeneticAlgorithm/SearchUtils.cs +++ b/GeneticAlgorithm/SearchUtils.cs @@ -56,14 +56,17 @@ public static double Clip(this double value, double min, double max) return value; } - public static T[] Shuffle(this ICollection collection, Random random) + /// + /// Only send the random if the compoment calling shuffle doesn't need to be thread safe. + /// + public static T[] Shuffle(this ICollection collection, Random random = null) { var array = collection.ToArray(); int n = array.Length; while (n > 1) { n--; - int k = random.Next(n + 1); + int k = random != null ? random.Next(n + 1) : ProbabilityUtils.GetRandomInt(n + 1); T value = array[k]; array[k] = array[n]; array[n] = value; diff --git a/GeneticAlgorithm/StopManagers/StopAtConvergence.cs b/GeneticAlgorithm/StopManagers/StopAtConvergence.cs index f44599c..fe2b2f9 100644 --- a/GeneticAlgorithm/StopManagers/StopAtConvergence.cs +++ b/GeneticAlgorithm/StopManagers/StopAtConvergence.cs @@ -1,4 +1,5 @@ -using GeneticAlgorithm.Interfaces; +using GeneticAlgorithm.Exceptions; +using GeneticAlgorithm.Interfaces; namespace GeneticAlgorithm.StopManagers { @@ -11,6 +12,9 @@ public class StopAtConvergence : IStopManager /// public StopAtConvergence(double diff) { + if (diff < 0) + throw GeneticAlgorithmArgumentException.SmallerThanZeroException(nameof(diff), diff); + this.diff = diff; } diff --git a/GeneticAlgorithm/StopManagers/StopAtEvaluation.cs b/GeneticAlgorithm/StopManagers/StopAtEvaluation.cs index 1a0b2e1..30e6dbe 100644 --- a/GeneticAlgorithm/StopManagers/StopAtEvaluation.cs +++ b/GeneticAlgorithm/StopManagers/StopAtEvaluation.cs @@ -1,4 +1,5 @@ using System.Linq; +using GeneticAlgorithm.Exceptions; using GeneticAlgorithm.Interfaces; namespace GeneticAlgorithm.StopManagers @@ -12,6 +13,10 @@ public class StopAtEvaluation : IStopManager /// public StopAtEvaluation(double evaluationToStopAt) { + if (evaluationToStopAt < 0) + throw GeneticAlgorithmArgumentException.SmallerThanZeroException(nameof(evaluationToStopAt), evaluationToStopAt); + + this.evaluationToStopAt = evaluationToStopAt; } diff --git a/GeneticAlgorithm/StopManagers/StopAtGeneration.cs b/GeneticAlgorithm/StopManagers/StopAtGeneration.cs index 19c23da..1838736 100644 --- a/GeneticAlgorithm/StopManagers/StopAtGeneration.cs +++ b/GeneticAlgorithm/StopManagers/StopAtGeneration.cs @@ -3,7 +3,7 @@ namespace GeneticAlgorithm.StopManagers { - class StopAtGeneration : IStopManager + public class StopAtGeneration : IStopManager { private readonly int generationToStopAt; @@ -11,7 +11,7 @@ public StopAtGeneration(int generationToStopAt) { this.generationToStopAt = generationToStopAt > 1 ? generationToStopAt - : throw new GeneticAlgorithmException(nameof(generationToStopAt) + " must be greater then one"); + : throw new GeneticAlgorithmArgumentException(nameof(generationToStopAt) + " must be greater then one"); } public bool ShouldStop(Population population, IEnvironment environment, int generation) => diff --git a/GeneticAlgorithm/StopManagers/StopIfNoImprovment.cs b/GeneticAlgorithm/StopManagers/StopIfNoImprovment.cs index 412fe9a..f258d10 100644 --- a/GeneticAlgorithm/StopManagers/StopIfNoImprovment.cs +++ b/GeneticAlgorithm/StopManagers/StopIfNoImprovment.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using GeneticAlgorithm.Exceptions; using GeneticAlgorithm.Interfaces; namespace GeneticAlgorithm.StopManagers @@ -16,7 +17,9 @@ public class StopIfNoImprovment : IStopManager /// public StopIfNoImprovment(int generationsToConsider, double minImprovment) { - this.generationsToConsider = generationsToConsider; + this.generationsToConsider = generationsToConsider > 0 + ? generationsToConsider + : throw new GeneticAlgorithmArgumentException(nameof(generationsToConsider) + " must be greater then zero"); this.minImprovment = minImprovment; } diff --git a/README.md b/README.md index 22a7dde..ad68b8d 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Your chromosomes will need to implement the [IChromosome](/GeneticAlgorithm/Inte public interface IChromosome { /// - /// Must return a value that is greater then zero + /// Must return a value that is greater then zero. /// double Evaluate(); @@ -60,9 +60,12 @@ Your chromosomes will need to implement the [IChromosome](/GeneticAlgorithm/Inte You can find a sample Chromosome [here](/GeneticAlgorithm/Components/Chromosomes/VectorChromosome.cs). +Please note that the Evaluate and Mutate methods will be called on deferent chromosomes in parallel, so they must be thread safe in that aspect. + ### ICrossoverManager You'll need to implement the [ICrossoverManager](/GeneticAlgorithm/Interfaces/ICrossoverManager.cs) interface. This tells the engine how to perform crossovers for your chromosomes. +Please note that your crossoverManager will be called on deferent chromosomes in parallel, so it must be thread safe in that aspect. You can read more about crossovers [here](https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)). ```CSharp @@ -78,6 +81,7 @@ You can find some sample CrossoverManagers [here](/GeneticAlgorithm/Components/C You'll also need to implement the [IPopulationGenerator](/GeneticAlgorithm/Interfaces/IPopulationGenerator.cs) interface. The engine uses this class to create its initial population. The PopulationGenerator will also renew the population when needed (see [IPopulationRenwalManagers](#ipopulationrenwalmanagers)). +PopulationGenerator doesn't need to be thread safe. ```CSharp public interface IPopulationGenerator @@ -190,6 +194,7 @@ In addition, [here](/GeneticAlgorithm/PopulationRenwalManagers) are some example Existing PopulationRenwalManagers: - [RenewAtConvergence](/GeneticAlgorithm/PopulationRenwalManagers/RenewAtConvergence.cs): The search will renew some of the population if the difference between chromosomes in the population is too small. - [RenewIfNoImprovment](/GeneticAlgorithm/PopulationRenwalManagers/RenewIfNoImprovment.cs): Will renew some of the population if the improvement over 'X' generations isn't good enough. +- [RenewAtDifferenceBetweenAverageAndMaximumFitness](/GeneticAlgorithm/PopulationRenwalManagers/RenewAtDifferenceBetweenAverageAndMaximumFitness.cs): Will renew some of the population when the difference between the average and max evaluation is equal too small (available since 1.3.4). Example: ```CSharp