class TestPlottingFunction(object): def test_population_has_plotting_option(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() try: self.pop.launch(dev='on') except TypeError as e: assert False, "allow for 'on' device to show plots" os.remove("output/vigilance_out.txt") os.remove("output/vigilance_out.gif") os.remove('output/resources_out.txt') os.remove('output/exploration_out.txt') os.remove('output/grid_out.gif') def test_gif_is_created_only_when_dev_on(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() self.pop.launch(dev='on') self.filesListRootOut = os.listdir("./output") assert "vigilance_out.gif" in self.filesListRootOut, "no output gif created" os.remove("output/vigilance_out.gif") self.pop.launch() self.filesListRootOut = os.listdir("./output") assert "vigilance_out.gif" not in self.filesListRootOut, "should not have created output gif!" os.remove("output/vigilance_out.txt") os.remove('output/resources_out.txt') os.remove('output/exploration_out.txt') os.remove('output/grid_out.gif') def test_grid_gif_is_created(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() self.pop.launch(dev='on') self.filesListRootOut = os.listdir("./output") assert "grid_out.gif" in self.filesListRootOut, "no output grid gif created" os.remove("output/vigilance_out.txt") os.remove("output/vigilance_out.gif") os.remove('output/resources_out.txt') os.remove('output/exploration_out.txt') os.remove('output/grid_out.gif')
from model.population import Population as Pop import sys try: visual = sys.argv[1] except IndexError: visual = "off" print("Visualisation turned off. Type 'python run.py on' to run simulation with visualisation") pop = Pop() pop.create() pop.launch(dev=visual) # pop.launch(dev='on')
class TestMutationFunction(object): def test_mutation_function_takes_and_returns_phenotype(self): self.pop = Pop("test/test/parameters.txt") self.pop.create(n=1) self.indiv = self.pop.individuals[0] self.indiv.mutate(mutRate=0.5, mutStep=0.5) x = self.indiv.vigilance assert type( x ) is float, "Phenotypic values ({0}) must be of type float, and not {1}".format( x, type(x)) assert 0 <= x <= 1, "Phenotypic values must be in range [0,1]" gc.collect() def test_mutants_are_defined(self): self.pop = Pop("test/test/parameters.txt") self.pop.create(n=1) self.indiv = self.pop.individuals[0] self.indiv.mutate(mutRate=0.5, mutStep=0.5) assert hasattr( self.indiv, "mutant" ), "We don't know if our individual is a mutant because it doesn't have this attribute" assert type(self.indiv.mutant) is bool gc.collect() def test_mutants_depend_on_seed(self, pseudorandom): self.nIndividuals = 1000 self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=self.nIndividuals) mutants = [] for ind in self.fakepop.individuals: pseudorandom(0) ind.mutate(mutRate=0.3, mutStep=0.1) mutants.append(ind.mutant) assert all(mutants) or not any( mutants), "Mutant does not depend on seed: {0}".format( set(mutants)) def test_mutants_are_drawn_from_binomial(self, pseudorandom): pseudorandom(0) self.nIndividuals = 1000 self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=self.nIndividuals) self.mutationRate = 0.2 self.mutantCount = 0 for ind in self.fakepop.individuals: ind.mutate(mutRate=self.mutationRate, mutStep=0.05) if ind.mutant: self.mutantCount += 1 stat1, pval1 = scistats.ttest_1samp( [1] * self.mutantCount + [0] * (self.nIndividuals - self.mutantCount), self.mutationRate) assert pval1 > 0.05, "T-test mean failed. Observed: {0}, Expected: {1}".format( self.mutantCount / self.nIndividuals, self.mutationRate) self.test = scistats.binom_test(self.mutantCount, self.nIndividuals, self.mutationRate, alternative="two-sided") assert self.test > 0.05, "Success rate = {0} when mutation rate = {1}".format( self.mutantCount / self.nIndividuals, self.mutationRate) gc.collect() def test_deviation_depends_on_seed(self, pseudorandom): self.nIndividuals = 1000 self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=self.nIndividuals) mutationDeviations = [] for ind in self.fakepop.individuals: pseudorandom(0) ind.mutate(mutRate=1, mutStep=0.05) mutationDeviations.append(ind.mutationDeviation) assert all([ x == mutationDeviations[0] for x in mutationDeviations ]), "Mutation deviation does not depend on seed: {0}".format( set(mutationDeviations)) def test_deviation_function_returns_float(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=1) self.indiv = self.fakepop.individuals[0] self.v = self.indiv.vigilance for mutationBool in [True, False]: self.indiv.mutant = mutationBool self.indiv.deviate(mutStep=0.05) assert type(self.indiv.mutationDeviation) is float gc.collect() def test_mutants_get_deviation_from_phenotype(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=1) self.indiv = self.fakepop.individuals[0] self.indiv.mutate(mutRate=1, mutStep=0.05) assert hasattr( self.indiv, "mutationDeviation" ), "Individual is a mutant: it needs to be set a deviation from phenotype" assert -1 < self.indiv.mutationDeviation < 1 gc.collect() def test_only_mutants_change_phenotype(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=2) assert len(self.fakepop.individuals) == 2 self.mutantIndivTrue = self.fakepop.individuals[0] self.mutantIndivFalse = self.fakepop.individuals[1] self.mutantIndivTrue.mutate(mutRate=1, mutStep=0.05) assert bool( self.mutantIndivTrue.mutant ), "Uh-oh, looks like the individual did not mutate when it should have..." assert self.mutantIndivTrue.mutationDeviation != 0, "Your mutant (bool={0}) phenotype does not deviate!".format( self.mutantIndivTrue.mutant) self.mutantIndivFalse.mutate(mutRate=0, mutStep=0.05) assert not bool(self.mutantIndivFalse.mutant) assert self.mutantIndivFalse.mutationDeviation == 0, "Phenotype deviates even though individual not a mutant!" gc.collect() def test_mutation_deviation_follows_normal_distribution( self, pseudorandom): pseudorandom(0) self.nIndividuals = 1000 self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(self.nIndividuals) self.distri = [] for ind in self.fakepop.individuals: ind.mutate(mutRate=1, mutStep=0.05) self.distri.append(ind.mutationDeviation) stat1, pval1 = scistats.ttest_1samp(self.distri, float(0)) assert pval1 > 0.05, "T-test mean failed. Observed: {0}, Expected: {1}".format( mean(self.distri), 0) stat2, pval2 = scistats.kstest(self.distri, 'norm', args=(0, 0.05), N=self.nIndividuals) assert pval2 > 0.05, "Test for goodness of fit failed" stat3, pval3 = scistats.shapiro(self.distri) assert pval3 > 0.05, "Test of normality failed" gc.collect() def test_mutation_adds_deviation_to_phenotype(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(2) # WHEN THERE IS MUTATION self.trueMutant = self.fakepop.individuals[0] self.oldPhenTrueMutant = self.trueMutant.vigilance self.trueMutant.mutant = True self.trueMutant.deviate(mutStep=0.05) assert self.trueMutant.mutationDeviation != 0, "Deviation = {0}".format( self.trueMutant.mutationDeviation) self.trueMutant.applyMutation(self.trueMutant.mutationDeviation, bound=True) assert self.trueMutant.vigilance != self.oldPhenTrueMutant, "New:{0}, Old:{1}".format( self.trueMutant.vigilance, self.oldPhenTrueMutant) # Reset to test the whole thing together: self.trueMutant.vigilance = self.oldPhenTrueMutant self.trueMutant.mutant = None self.trueMutant.mutationDeviation = None self.trueMutant.mutate(mutRate=1, mutStep=0.05) assert type(self.oldPhenTrueMutant) is float and type( self.trueMutant.mutationDeviation ) is float, "Check that both {0} and {1} are float".format( self.oldPhenTrueMutant, self.trueMutant.mutationDeviation) assert min( 1, max(0, self.oldPhenTrueMutant + self.trueMutant.mutationDeviation) ) == pytest.approx( self.trueMutant.vigilance ), "Deviation not added to mutant phenotype: returns {0} instead of {1}!".format( self.trueMutant.vigilance, self.oldPhenTrueMutant + self.trueMutant.mutationDeviation) # WHEN THERE IS NO MUTATION self.falseMutant = self.fakepop.individuals[1] self.oldPhenFalseMutant = self.falseMutant.vigilance self.falseMutant.mutant = False self.falseMutant.deviate(mutStep=0.05) assert self.falseMutant.mutationDeviation == 0, "Deviation = {0}".format( self.falseMutant.mutationDeviation) self.falseMutant.applyMutation(self.falseMutant.mutationDeviation, bound=True) assert self.falseMutant.vigilance == self.oldPhenFalseMutant, "New:{0}, Old:{1}".format( self.falseMutant.vigilance, self.oldPhenFalseMutant) # Reset to test the whole thing together: self.falseMutant.vigilance = self.oldPhenFalseMutant self.falseMutant.mutant = None self.falseMutant.mutationDeviation = None self.falseMutant.mutate(mutRate=0, mutStep=0.05) assert self.oldPhenFalseMutant == self.falseMutant.vigilance, "Your individual shows mutant characteristic = {0}. Yet its phenotype deviates by {1}".format( self.falseMutant.mutant, [ x - y for x, y in zip(self.falseMutant.vigilance, self.oldPhenFalseMutant) ]) assert self.oldPhenFalseMutant == self.falseMutant.vigilance, "Before: {0}, Deviation: {1}, After: {2}".format( self.oldPhenFalseMutant, self.falseMutant.mutationDeviation, self.falseMutant.vigilance) gc.collect() def test_mutation_does_not_affect_phenotype_type(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=1) self.indiv = self.fakepop.individuals[0] self.phen = self.indiv.vigilance self.indiv.mutate(mutRate=1, mutStep=0.05) assert type(self.indiv.vigilance) is float gc.collect() def test_individual_mutation_can_be_unbounded(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.nIndiv = 100 self.fakepop.initialVigilance = 1 self.fakepop.create() collectPhenotypes = [] for ind in self.fakepop.individuals: ind.mutate(1, 0.5, bounded=False) collectPhenotypes.append(ind.vigilance) assert any([i > 1 for i in collectPhenotypes ]), "no phenotype went over 1, even when unbounded."
class TestReproductionFunction(object): def test_individual_can_reproduce(self): assert hasattr(Ind(m=3), "reproduce"), "ind cannot reproduce" def test_fertility_returns_positive_float(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create() for ind in range(len(self.fakepop.individuals)): indiv = self.fakepop.individuals[ind] setattr(indiv, "storage", 1 + 9 * ind) indiv.reproduce(fecundity=2) assert type(indiv.fertility) is float assert indiv.fertility >= 0 def test_fertility_increases_with_storage(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create() fertility = 0 for ind in range(len(self.fakepop.individuals)): indiv = self.fakepop.individuals[ind] setattr(indiv, "storage", 1 + 9 * ind) indiv.reproduce(fecundity=2) assert indiv.fertility > fertility fertility = indiv.fertility def test_reproduction_gives_offspring_number(self): self.indiv = Ind(m=3) self.indiv.reproduce(fecundity=2) assert self.indiv.offspring != None, "No offspring number generated" assert type( self.indiv.offspring ) is int, "Offspring number of wrong format: {0} instead of integer".format( type(self.indiv.offspring)) assert self.indiv.offspring >= 0, "Offspring number cannot be negative" def test_offspring_number_increases_with_fertility(self): self.indLowFertility = Ind(m=3) self.indHighFertility = Ind(m=3) self.indLowFertility.storage = 1 self.indLowFertility.reproduce(fecundity=0.5) self.indHighFertility.storage = 10 self.indHighFertility.reproduce(fecundity=2) assert self.indLowFertility.offspring < self.indHighFertility.offspring, "high fertility should lead to higher offspring number" def test_reproduction_is_seed_dependent(self, pseudorandom): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.nIndiv = 1000 self.fakepop.create() offspring = [] for ind in self.fakepop.individuals: setattr(ind, "storage", 4) pseudorandom(0) ind.reproduce(fecundity=2) offspring.append(ind.offspring) assert all([ x == offspring[0] for x in offspring ]), "number of offspring differs with same seed, {0}".format( set(offspring)) def test_reproduction_follows_a_poisson_distribution(self, pseudorandom): #http://www2.stat-athens.aueb.gr/~exek/papers/Xekalaki-Statistician2000(355-382)ft.pdf pseudorandom(0) self.fakepop = Pop("test/test/parameters.txt") self.fakepop.nIndiv = 1000 self.fakepop.create() self.explambda = 8 offspringPerInd = [] for ind in self.fakepop.individuals: setattr(ind, "storage", 4) ind.reproduce(fecundity=2) offspringPerInd.append(ind.offspring) d = Counter(offspringPerInd) a, b = list(d.keys()), list(d.values()) maxCount = max(a) observedCount = [] expectedCount = [] for k in range(maxCount): if k in a: observedCount.append(d[k]) else: observedCount.append(0) expProbability = m.pow(m.e, (-self.explambda)) * (m.pow( self.explambda, k)) / m.factorial(k) expectedCount.append(self.fakepop.nIndiv * expProbability) chisq, pval = scistats.chisquare(observedCount, expectedCount) assert len(expectedCount) == len( observedCount), "len obs = {0}, len exp = {1}".format( len(observedCount), len(expectedCount)) #assert sum(expectedCount) == sum(observedCount), "n obs = {0}, n exp = {1}".format(sum(observedCount), sum(expectedCount)) assert pval > 0.05, "Test for goodness of fit failed: obs = {0}, exp = {1}".format( observedCount, expectedCount)
class TestPopulationObject(object): def test_population_has_individual_instances(self): self.pop = Pop("test/test/parameters.txt") assert hasattr(self.pop, "create"), "cannot create pop" self.pop.create(2) assert hasattr(self.pop, "individuals"), "no indivs in this pop" def test_population_has_correct_density(self): self.pop = Pop("test/test/parameters.txt") d1 = 2 self.pop.create(d1) assert type(self.pop.individuals) is list assert len(self.pop.individuals) == d1 for i in self.pop.individuals: assert type(i) is Ind d2 = 10 self.pop.create(d2) assert type(self.pop.individuals) is list assert len(self.pop.individuals) == d2 for i in self.pop.individuals: assert type(i) is Ind def test_population_individuals_are_unlinked(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() assert len(set(self.pop.individuals)) == self.pop.nIndiv def test_population_has_life_cycle(self): assert hasattr(Pop(), "lifeCycle") assert callable(getattr(Pop(), "lifeCycle")) # def test_share_calculated_and_assigned_to_grid(self): # assert False, "write this test" # def test_pool_is_wiped_out_at_beginning_of_cycle(self): # assert False, "write this test" def test_population_creates_grid(self): self.pop = Pop("test/test/parameters.txt") self.pop.create(n=20) assert hasattr(self.pop, "grid") assert type(self.pop.grid) is Grid assert self.pop.grid.resources.shape == (self.pop.gridSize, self.pop.gridSize) def test_population_explores_grid(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() assert hasattr(self.pop, "explore") def test_population_exploration_leads_to_change_in_coord(self): self.pop = Pop("test/test/parameters.txt") self.pop.nIndiv = 1000 self.pop.gridSize = 60 self.pop.create() coord = [] for ind in self.pop.individuals: coord.append(ind.coordinates) self.pop.explore() newCoord = [] for ind in self.pop.individuals: newCoord.append(ind.coordinates) assert all([coord[x] == newCoord[x] for x in range(len(coord))]) == False def test_population_exploration_gives_share_info(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() self.pop.explore() m = self.pop.gridSize assert hasattr(self.pop, "ncell") assert type(self.pop.ncell) is np.ndarray assert self.pop.ncell.shape == (m, m) assert hasattr(self.pop, "vcell") assert type(self.pop.vcell) is np.ndarray assert self.pop.vcell.shape == (m, m) def test_only_live_individuals_explore(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() coord = [] for ind in self.pop.individuals: ind.alive = False coord.append(ind.coordinates) self.pop.explore() newCoord = [] for ind in self.pop.individuals: newCoord.append(ind.coordinates) assert all([coord[x] == newCoord[x] for x in range(len(coord))]) def test_population_can_gather_vs_survive(self): assert hasattr(Pop(), "gatherAndSurvive") assert callable(getattr(Pop(), "gatherAndSurvive")) self.pop = Pop("test/test/parameters.txt") self.pop.create() try: self.pop.gatherAndSurvive() except ValueError as e: assert False, "missing info: {0}".format(e) def test_gathering_gives_individuals_storage(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() self.pop.grid.share = np.full([self.pop.gridSize, self.pop.gridSize], 0.8) for i in self.pop.individuals: i.vigilance = 0 self.pop.gatherAndSurvive() for i in self.pop.individuals: assert i.storage > 0 def test_only_live_individuals_gather_and_survive(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() self.pop.explore() for ind in self.pop.individuals: ind.alive = False self.pop.gatherAndSurvive() for ind in self.pop.individuals: assert ind.alive == False assert ind.storage == 0 def test_population_has_a_routine(self): assert hasattr(Pop(), "routine") assert callable(getattr(Pop(), "routine")) def test_population_routine_changes_share_grid(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() shareG = self.pop.grid.share self.pop.routine() compareGrids = self.pop.grid.share == shareG assert compareGrids.all() == False def test_population_routine_changes_resources_grid(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() resG = self.pop.grid.resources self.pop.routine() compareGrids = self.pop.grid.resources != resG assert compareGrids.all() def test_reproduction_at_population_level(self): assert hasattr(Pop(), "reproduce") assert callable(getattr(Pop(), "reproduce")) gc.collect() def test_reproduction_creates_pool(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() self.pop.routine() self.pop.reproduce() assert hasattr(self.pop, "nextGeneration"), "population pool does not exist" assert type(self.pop.nextGeneration) is list assert len(self.pop.nextGeneration) > 0 for elem in self.pop.nextGeneration: assert type(elem) is int assert elem in range(10) gc.collect() def test_pool_is_a_mix_of_individuals(self): self.pop = Pop("test/test/parameters.txt") self.pop.initRes = 10 self.pop.create() self.pop.routine() for ind in self.pop.individuals: ind.alive = True self.pop.reproduce() assert len(set( self.pop.nextGeneration)) > 1, "ids missing in {0}".format( set(self.pop.nextGeneration)) gc.collect() def test_only_live_individuals_reproduce(self): self.pop = Pop("test/test/parameters.txt") self.pop.initRes = 10 self.pop.create() self.pop.routine() for ind in self.pop.individuals: ind.alive = False self.pop.individuals[0].alive = True self.pop.reproduce() #assert len(set(self.pop.nextGeneration)) > 1, "ids missing in {0}".format(set(self.pop.nextGeneration)) assert len(set(self.pop.nextGeneration) ) == 1, "there should be only one id in {0}".format( set(self.pop.nextGeneration)) assert self.pop.nextGeneration.count( 0 ) == self.pop.nIndiv, "only ind #0 should reproduce, not {0}".format( set(self.pop.nextGeneration)) gc.collect() def test_death_counter(self): self.pop = Pop("test/test/parameters.txt") self.pop.predation = 1 self.pop.create() for ind in self.pop.individuals: ind.vigilance = 0 self.pop.routine() for ind in self.pop.individuals: assert ind.alive == False self.pop.reproduce() assert self.pop.deathCount == 10 def test_population_gets_updated_at_new_gen(self): assert hasattr(Pop(), "update") assert callable(getattr(Pop(), "update")) def test_update_replaces_old_gen_with_new_gen(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() self.pop.routine() self.pop.reproduce() oldgen = self.pop.individuals self.pop.update() assert len(self.pop.individuals) == self.pop.nIndiv assert self.pop.individuals != oldgen def test_update_returns_population_vigilance_info(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() self.pop.routine() self.pop.reproduce() self.pop.update() assert hasattr(self.pop, "vigilance") assert self.pop.vigilance is not None assert type(self.pop.vigilance) is float assert 0 <= self.pop.vigilance <= 1 def test_population_vigilance_is_correct(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() self.pop.routine() self.pop.reproduce() self.pop.update() collectVigilances = [] for ind in self.pop.individuals: collectVigilances.append(ind.vigilance) assert self.pop.vigilance == pytest.approx(np.mean(collectVigilances), 0.1) def test_lam_error_when_resources_too_big(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.initRes = 20000000000000000000 self.pop.create() try: self.pop.lifeCycle() except ValueError as e: assert str( e ) == 'lam value too large', "This program should fail at poisson random draw, not '{0}'".format( e) def test_resources_crash_when_too_large(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.initRes = 20 self.pop.growth = 100 self.pop.create() self.pop.lifeCycle() for cell in np.nditer(self.pop.grid.resources): assert cell <= self.pop.initRes, "resources too large, should have crashed" def test_population_keeps_track_of_ecological_time(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() assert hasattr( self.pop, "ecoTime"), "population must keep track of its ecological time!" def test_ecological_time_correctly_assessed(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() self.pop.lifeCycle() ngen = 5 assert self.pop.ecoTime == self.pop.routineSteps, "one life cycle should be {0} units of ecological time, not {1}".format( self.pop.routineSteps, self.pop.ecoTime) self.pop = Pop(par="test/test/parameters.txt") self.pop.create() for i in range(ngen): self.pop.lifeCycle() assert self.pop.ecoTime == self.pop.routineSteps * ngen, "{2} life cycles should be {0} units of ecological time, not {1}".format( self.pop.routineSteps * 10, self.pop.ecoTime, ngen) def test_population_keeps_track_of_resources_over_life_cycle(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() assert hasattr( self.pop, "ecologyShortHistory" ), "population must keep track of its ecological history!" def test_resources_info_augmented_at_each_routine(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() for i in range(self.pop.routineSteps): self.pop.routine() assert self.pop.ecologyShortHistory.shape == ((self.pop.gridSize**2) * self.pop.routineSteps, 4) def test_population_keeps_track_of_exploration_over_life_cycle(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() assert hasattr( self.pop, "explorationShortHistory" ), "population must keep track of its exploration history!" def test_exploration_info_augmented_at_each_routine(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.predation = 0 self.pop.create() for i in range(self.pop.routineSteps): self.pop.routine() assert self.pop.explorationShortHistory.shape == ( self.pop.nIndiv * self.pop.routineSteps, 4)