Skip to content

Commit

Permalink
finalizing unit tests for utils
Browse files Browse the repository at this point in the history
  • Loading branch information
williampiat3 committed Jan 20, 2020
1 parent 61e9b81 commit 14d30cc
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 5 deletions.
2 changes: 2 additions & 0 deletions Algorithms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ For coding the genetical algorithm I needed some classes for making the interact

For evaluating the models I needed to code specific Fitness objects that are available in the custom_fitnesses.py file

These three files have unit tests coded in the folder Utils/tests to make sure that they have the correct behavior: of course is can giv you some insights and some example of the inner working of the algorithms (but all the code is here so why having just insights when you can have the whole code!)

## Algorithms

### Simple Discrete GA
Expand Down
101 changes: 99 additions & 2 deletions Algorithms/Utils/tests/tests_dna.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,113 @@ def test_discrete(self):
"b":[-1,-3,-5]
}
translator1 = DNA_creator(params,gray_code=False)
#testing if generator initialize viable individuals
#testing if generator initializes viable individuals
self.assertTrue(all([translator1.is_dna_viable(translator1.generate_random()) for _ in range(1000)]))
#testing the blind spot in the binary coding
self.assertFalse(translator1.is_dna_viable([1,0,1,1]))
#testing translation capacity
try:
translator1.dna_to_phen([1,0,1,0])
except Exception:
self.fail("translation failed on discrete dna, binary encoding")


translator2 = DNA_creator(params,gray_code=True)
#testing if generator initialize viable individuals
#testing if generator initializes viable individuals
self.assertTrue(all([translator2.is_dna_viable(translator2.generate_random()) for _ in range(1000)]))
#testing blind spot in gray encoding
self.assertFalse(translator2.is_dna_viable([1,0,1,0]))
#testing translation capacity
try:
translator2.dna_to_phen([1,0,1,1])
except Exception:
self.fail("translation failed on discrete dna, gray code encoding")



def test_continuous(self):
"""
Function to test the translation operated by the continuous dna
the format of the parameters has to be precise as this is the only
format decoded by the continuous DNA
"""
params = {
"a":{"range":[2,8],"scale":"linear"},
"b":{"range":[0.1,10],"scale":"log"}
}

translator1 = Continuous_DNA(params,stric_interval=False)
#testing if generator initializes viable individuals
self.assertTrue(all([translator1.is_dna_viable(translator1.generate_random()) for _ in range(1000)]))
#testing if the translator accepts genes beyond initial scope
self.assertTrue(translator1.is_dna_viable([1,1.1]))
#testing translation capacity
try:
translator1.dna_to_phen([1.,1.9])
except Exception:
self.fail("translation failed on continuous dna, not strict interval")



#changing the value of the strictness of the interval
translator2 = Continuous_DNA(params,stric_interval=True)
#testing if generator initializes viable individuals
self.assertTrue(all([translator2.is_dna_viable(translator2.generate_random()) for _ in range(1000)]))
#testing if translator rejects the individual once interval are strict
self.assertFalse(translator2.is_dna_viable([1.,1.1]))
#testing translation capacity
try:
translator2.dna_to_phen([1.,0.9])
except Exception:
self.fail("translation failed on continuous dna, strict interval")


def test_hybrid(self):
"""
Function to test the HybrisDNA class, it mixes the two other approaches
"""
params={}
params["discrete"] = {
"a":[1,2,3,4],
"b":[-1,-3,-5]
}
params["continuous"] = {
"c":{"range":[2,8],"scale":"linear"},
"d":{"range":[0.1,10],"scale":"log"}
}
translator1 = HybridDNA(params,gray_code=False,stric_interval=False)
#testing if generator initialize viable individuals
self.assertTrue(all([translator1.is_dna_viable(translator1.generate_random()) for _ in range(1000)]))

#the genotype here is [discrete_gene,continuous_gen]
#testing if the translator reject faulty discrete dna
self.assertFalse(translator1.is_dna_viable([[1,0,1,1],[1,1.1]]))
#testing if the translator accepts genes beyond initial scope
self.assertTrue(translator1.is_dna_viable([[1,0,1,0],[1,1.1]]))
#testing translation capacity
try:
translator1.dna_to_phen([[1,0,1,0],[1,1.1]])
except Exception:
self.fail("translation failed on hybrid dna, not strict interval, binary encoding")


translator2 = HybridDNA(params,gray_code=True,stric_interval=True)
#testing if generator initialize viable individuals
self.assertTrue(all([translator2.is_dna_viable(translator2.generate_random()) for _ in range(1000)]))

#the genotype here is [continuous_gene,discrete_gen]
#testing if the translator rejects continuous part
self.assertFalse(translator2.is_dna_viable([[1,0,1,1],[1,1.1]]))
#testing if the translator rejects genes discrete part
self.assertFalse(translator2.is_dna_viable([[1,0,1,0],[1.,1.]]))

#testing translation capacity
try:
translator2.dna_to_phen([[1,0,1,1],[1.,1.]])
except Exception:
self.fail("translation failed on hybrid dna, strict interval, gray encoding")





Expand Down
2 changes: 1 addition & 1 deletion Algorithms/Utils/tests/tests_fitnesses.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TestFitnesses(unittest.TestCase):
"""

def __init_(self):
super(TestUtils,self).__init__()
super(TestFitnesses,self).__init__()

def test_fitnesses(self):
"""
Expand Down
96 changes: 96 additions & 0 deletions Algorithms/Utils/tests/tests_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import unittest
from utils import generate,evalOneMax,decorator_cross,decorator_mut,decorator_selection,recover_last_gen,evaluate
from dna_translator import DNA_creator
from deap import creator, tools, base

@decorator_cross
def dummy_cross(indiv1,indiv2):
return indiv1,indiv2

@decorator_mut
def dummy_mut(indiv):
return indiv,

@decorator_selection
def dummy_selection(indivs):
return indivs


class TestUtils(unittest.TestCase):


def __init__(self,*args,**kwargs):
"""
Constructor that creates the basic classes on which we will tes the operations
"""
super(TestUtils,self).__init__(*args,**kwargs)
creator.create("FitnessMax", base.Fitness, weights=(1.,))
#creating the individual with extra arguments (parents, mutated, id and age for logs)
#these parameters are changed by the decorators of the operators to trace which operator was applied to whom
creator.create("Individual", list, fitness=creator.FitnessMax,parents=None,mutated=None,id=None,age=0)
hparams = {"a":[1,0,8,9],"b":[2,5]}
translator=DNA_creator(hparams,gray_code=False)

self.toolbox = base.Toolbox()
# generation operation: relies on the function generate_random of the translator
self.toolbox.register("individual", generate,translator=translator,creator=creator)
# a population is a repetition of individuals
self.toolbox.register("population", tools.initRepeat, list, self.toolbox.individual)
#function that returns the fitness from an individual
self.toolbox.register("evaluate_one", evalOneMax,model=lambda x: 1,translator=translator)
#function 2 for evaluation
self.toolbox.register("evaluate_multi", evaluate,model=lambda x: (1,),weights=(1.,),translator=translator)
#mate function decorated for logs of parents
self.toolbox.register("mate", dummy_cross)
#mutation function decorated for logging on which individual the mutation was done
self.toolbox.register("mutate", dummy_mut)
#selection operator decorated for changing logs
self.toolbox.register("select", dummy_selection)

def test_decorated_ops(self):
#intializing pop
population = self.toolbox.population(n=10)
gen = 0
#giving ids to the individuals
for l,ind in enumerate(population):
ind.age+=1
if ind.mutated!=None or ind.parents!=None or gen ==0:
ind.id=str(gen+1)+"."+str(l)
#testing first attributes
for ind in population:
self.assertTrue(ind.parents is None)
self.assertTrue(ind.mutated is None)
self.assertTrue(ind.id is not None)
self.assertTrue(ind.age == 1)
#testing cross over
population[0],population[1] = self.toolbox.mate(population[0],population[1])
for ind in [population[0],population[1]]:
#attribute parents has been changed by the decorator
self.assertTrue(ind.parents is not None)
#testing mutation
population[3], = self.toolbox.mutate(population[3])
#attribute mutated has been changed by the decorator
self.assertTrue(population[3].mutated is not None)
#testing evaluation operators
fits_one = self.toolbox.map(self.toolbox.evaluate_one,population)
fits_multi = self.toolbox.map(self.toolbox.evaluate_multi,population)
#simple check to see if we have similar objects
self.assertTrue(all([fit1==fit2 for fit1,fit2 in zip(fits_one,fits_multi)]))

#selction operator
population = self.toolbox.select(population)
for ind in population:
#checking if attributes were reset
self.assertTrue(ind.parents is None)
self.assertTrue(ind.mutated is None)
self.assertTrue(ind.id is not None)
#some new individuals were created so their age is equal to 0
self.assertTrue(not all([(ind.age == 1) for ind in population]))


if __name__ == "__main__":
unittest.main()




3 changes: 3 additions & 0 deletions Algorithms/Utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@


def get_all_combinations(feed_dict):
"""
Depreciated util function for discrete parameter space
"""
output=[]
keys=feed_dict.keys()
iterables=list(map(lambda x: feed_dict[x],keys))
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</p>


In ```unit_test.py``` you can find unit tests on the three main approaches the discrete appoach, the continuous approach and the hybrid approach, they are tested against the the [Griewank function](https://deap.readthedocs.io/en/master/api/benchmarks.html#deap.benchmarks.griewank) you can change the test function but remember that the algorithm are meant for maximization.
In ```unit_test.py``` you can find unit tests on the three main approaches the discrete approach, the continuous approach and the hybrid approach, they are tested against the the [Griewank function](https://deap.readthedocs.io/en/master/api/benchmarks.html#deap.benchmarks.griewank) you can change the test function but remember that the algorithm are meant for maximization. The unit test also test the genetic selection on a toy example.


The goal of this folder is to present different genetic algorithms that I develloped in order to tackle optimization problems in my different jobs. It includes the evolutionnary algorithms that I created but also the different visualizations that I coded/adapted in order to give some insight on how the algorithms operates.
Expand Down
15 changes: 14 additions & 1 deletion unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from Algorithms import run_evolution
from Algorithms import continuous_ga
from Algorithms import run_hybrid_ga
from Algorithms import run_genetic_selection

"""
This file aims at building unit tests for the algorithms develloped in the Algorithm folder with benchmark function
Expand All @@ -25,7 +26,7 @@ def __init__(self,*args,**kwargs):
#minima for comparing the distance to the minima
self.minima = {"x0":0,"x1":0,"x2":0}
#distance to consider we have found the objective
self.epsilon = 1
self.epsilon = 0.3

def is_a_success(self,indiv):
"""
Expand Down Expand Up @@ -97,6 +98,18 @@ def test_hybrid(self):
#asserting if minima found
self.assertTrue(self.is_a_success(best))

def test_feature_selection(self):
"""
This test is not really a feature selection but the test function evolves with the indexes given by the ga
so it will naturally converge toward the maximum of the model function which is having all maximum indexes
"""
# test function for assesing the ga
# if the Ga works it should select only the highest indexes
def model(indiv):
return sum(indiv)
#we limit the length so that only the 6 best are to be selected
run_genetic_selection(model,(1,),nb_indiv=100,nb_gen=40,min_index=0,max_index=12,rm_mutpb=0.3,add_mutpb=0.3,length_max=6,tourn_size=3,cxpb=0.5,mutpb=0.5,log_file=None)



if __name__ == "__main__":
Expand Down

0 comments on commit 14d30cc

Please sign in to comment.