Package: pyrimidine
pyrimidinepyrimidineis an extensible framework of genetic/evolutionary algorithm by Python. Seepyrimidine's documentfor more details.WhyWhy is the package named as “pyrimidine”? Because it begins with “py”.— Are you kiding?— No, I am serious.DownloadIt has been uploaded topypi, so download it withpip install pyrimidine, and also could download it from github.IdeaWe regard the population as a container of individuals, an individual as a container of chromosomes
and a chromosome as a container(array) of genes.The container could be a list or an array.
The container class has an attributeelement_class, telling itself the type of the elements in it.Following is the part of the source code ofBaseIndividualandBasePopulation.classBaseIndividual(FitnessModel,metaclass=MetaContainer):element_class=BaseChromosomedefault_size=1classBasePopulation(PopulationModel,metaclass=MetaContainer):element_class=BaseIndividualdefault_size=20There is mainly tow kinds of containers: list and tuple as in programming languageHaskell. See following examples.# individual with chromosomes of type _Chromosome_Individual1=BaseIndividual[_Choromosome]# individual with 2 chromosomes of type _Chromosome1 and _Chromosome2 respectively_Individual2=MixedIndividual[_Chromosome1,_Chromosome2]Math expression$s$ of type $S$ is a container of $a:A$, represented as follows:s={a:A}:SWe define a population as a container of individuals or chromosomes, and an individual is a container of chromosomes.Algebraically, an indivdiual has only one chromosome is equivalent to a chromosome mathematically. A population could also be a container of chromosomes. If the individual has only one chromosome, then just build the population based on chromosomes directly.The methods are the functions or operators defined on $s$.UseMain classesBaseGene: the gene of chromosomeBaseChromosome: sequence of genes, represents part of a solutionBaseIndividual: sequence of chromosomes, represents a solution of a problemBasePopulation: a container of individuals, represents a container of a problem
also the state of a stachostic processBaseMultipopulation: a container of population for more complicated optimalizationimportJust use the commandfrom pyrimidine import *import all of the algorithms.subclassChromosomeGenerally, it is an array of genes.As an array of 0-1s,BinaryChromosomeis used most frequently.Individualjust subclassMonoIndividualin most cases.classMyIndividual(MonoIndividual):"""individual with only one chromosomewe set the gene is 0 or 1 in the chromosome"""element_class=BinaryChromosomedef_fitness(self):...Since the helpermakeIndividual(n_chromosomes=1, size=8)could create such individual, it is equivalent toclassMyIndividual(binaryIndividual()):# only need define the fitnessdef_fitness(self):...If an individual contains several chromosomes, then subclassMultiIndividualorPolyIndividual. It could be applied in multi-real-variable optimization problems where each variable has a separative binary encoding.In most cases, we have to decode chromosomes to real numbers.class_Chromosome(BinaryChromosome):defdecode(self):"""Decode a binary chromosomeif the sequence of 0-1 represents a real number, then overide the methodto transform it to a nubmer"""classExampleIndividual(BaseIndividual):element_class=_Chromosomedef_fitness(self):# define the method to calculate the fitnessx=self.decode()# will call decode method of _Chromosomereturnevaluate(x)If the chromosomes in an individual are different with each other, then subclassMixedIndividual, meanwhile, the propertyelement_classshould be assigned with a tuple of classes for each chromosome.classMyIndividual(MixedIndividual):"""Inherit the fitness from ExampleIndividual directly.It has 6 chromosomes, 5 are instances of _Chromosome, 1 is instance of FloatChromosome"""element_class=(_Chromosome,)*5+(FloatChromosome,)It equivalent toMyIndividual=MixedIndividual[(_Chromosome,)*5 + (FloatChromosome,)]PopulationclassMyPopulation(StandardPopulation):element_class=MyIndividualIt is equivalent toMyPopulation = StandardPopulation[MyIndividual].Initialize randomlyrandomis a factory method!Generate a population, with 50 individuals and each individual has 100 genes:pop = MyPopulation.random(n_individuals=50, size=100)When each individual contains 5 chromosomes, usepop = MyPopulation.random(n_individuals=10, n_chromosomes=5, size=10)However, we recommand to setdefault_sizein the classes, then runMyPopulation.random()classMyPopulation(StandardPopulation):element_class=MyIndividual//5default_size=10# equiv. toMyPopulation=StandardPopulation[MyIndividual//5]//10In fact,randommethod ofBasePopulationwill call random method ofBaseIndividual. If you want to generate an individual, then just executeMyIndividual.random(n_chromosomes=5, size=10), or setdefault_size, then executeMyIndividual.random().EvolutionevolvemethodInitialize a population withrandommethod, then callevolvemethod.pop=MyPopulation.random(n_individuals=50,size=100)pop.evolve()print(pop.solution)setverbose=Trueto display the data for each generation.evolvemethod mainly excute two methods:init: initial configuration of the algo.transition: do each step of the iteration.HistoryGet the history of the evolution.stat={'Mean Fitness':'mean_fitness','Best Fitness':lambdapop:pop.best_individual.fitness}data=pop.history(stat=stat)# use history instead of evolvestatis a dict mapping keys to function, where string 'mean_fitness' means functionlambda pop:pop.mean_fitnesswhich gets the mean fitness of the individuals inpop. Since we have defined pop.best_individual.fitness as a property,statcould be redefined as{'Fitness': 'fitness', 'Best Fitness': 'max_fitness'}.It requiresezstat, a easy statistical tool devoloped by the author.performanceUsepop.perf()to check the performance, which callsevolveseveral times.ExampleExample 1Descriptionselect some of ti, ni, i=1,...,L, ti in {1,2,...,T}, ni in {1,2,...,N}
the sum of ni approx. 10, while ti dose not repeatThe opt. problem ismin abs(sum_i{ni}-10) + maximum of frequences in {ti}
where i is selected.$$
\min_I |\sum_{i\in I} n_i -10| + t_m
\
I\subset{1,\cdots,L}
$$
where $t_m$ is the mode of ${t_i, i\in I}$t=np.random.randint(1,5,100)n=np.random.randint(1,4,100)importcollectionsdefmax_repeat(x):# Maximum repetitionc=collections.Counter(x)returnnp.max([bfora,binc.items()])classMyIndividual(makeBinaryIndividual()):def_fitness(self):x=abs(np.dot(n,self.chromosome)-10)y=max_repeat(tiforti,cinzip(t,self)ifc==1)return-x-yclassMyPopulation(StandardPopulation):element_class=MyIndividualpop=MyPopulation.random(n_individuals=50,size=100)pop.evolve()print(pop.solution)# or pop.best_individual.decode()Note that there is only one chromosome inMonoIndividual, which could be got byself.chromosome.In fact, the population could be the container of chromosomes. Therefore, we can rewrite the classes as follows in a more natural way.classMyChromosome(BinaryChromosome):def_fitness(self):x=abs(np.dot(n,self)-10)y=max_repeat(tiforti,cinzip(t,self)ifc==1)return-x-yclassMyPopulation(StandardPopulation):element_class=MyChromosomeIt is equiv. todef_fitness(obj):x=abs(np.dot(n,obj)-10)y=max_repeat(tiforti,cinzip(t,obj)ifc==1)return-x-yMyPopulation=StandardPopulation[BinaryChromosome].set_fitness(_fitness)Example2: Knapsack ProblemOne of the famous problem is the knapsack problem. It is a good example for GA.We sethistory=Trueinevolvemethod for the example, that will record the main data of the whole evolution. It will return an object ofpandas.DataFrame. The argumentstatis a dict from a key to function/str(corresponding to a method) representing a mapping from a population to a number. these numbers of one generation will be stored in a row of the dataframe.see# examples/example0#!/usr/bin/env python3frompyrimidineimportbinaryIndividual,StandardPopulationfrompyrimidine.benchmarks.optimizationimport*# generate a knapsack problem randomlyevaluate=Knapsack.random(n=20)classMyIndividual(binaryIndividual(size=20)):def_fitness(self):returnevaluate(self)classMyPopulation(StandardPopulation):element_class=MyIndividualdefault_size=10pop=MyPopulation.random()stat={'Mean Fitness':'mean_fitness','Best Fitness':'max_fitness'}data=pop.evolve(stat=stat,history=True)# an instance of `pandas.DataFrame`# Visualizationimportmatplotlib.pyplotaspltfig=plt.figure()ax=fig.add_subplot(111)data[['Mean Fitness','Best Fitness']].plot(ax=ax)ax.set_xlabel('Generations')ax.set_ylabel('Fitness')plt.show()Extensionpyrimidineis extremely extendable. It is easy to implement other iterative models or algorithms, such as simulation annealing(SA) and particle swarm optimization(PSO).Currently, it is recommended to define subclasses based onIterativeModelas a mixin. (not mandatory)In PSO, we regard a particle as an individual, andParticleSwarmas a population. But in the following, we subclass it fromIterativeModel# pso.py@basic_memoryclassParticle(BaseIndividual):"""A particle in PSOExtends BaseIndividualVariables:default_size {number} -- one individual represented by 2 chromosomes: position and velocityphantom {Particle} -- the current state of the particle moving in the solution space."""element_class=FloatChromosomedefault_size=2# other methodsclassParticleSwarm(PopulationMixin):"""Standard PSOExtends:PopulationMixin"""element_class=Particledefault_size=20params={'learning_factor':2,'acceleration_coefficient':3,'inertia':0.75,'n_best_particles':0.2,'max_velocity':None}definit(self):forparticleinself:particle.init()self.hall_of_fame=self.get_best_individuals(self.n_best_particles,copy=True)defupdate_hall_of_fame(self):hof_size=len(self.hall_of_fame)forindinself:forkinrange(hof_size):ifself.hall_of_fame[-k-1].fitness<ind.fitness:self.hall_of_fame.insert(hof_size-k,ind.copy())self.hall_of_fame.pop(0)break@propertydefbest_fitness(self):ifself.hall_of_fame:returnmax(map(attrgetter('fitness'),self.hall_of_fame))else:returnsuper().best_fitnessdeftransition(self,*args,**kwargs):"""Transitation of the states of particles"""self.move()self.backup()self.update_hall_of_fame()defbackup(self):# overwrite the memory of the particle if its current state is better its memoryforparticleinself:particle.backup(check=True)defmove(self):"""Move the particlesDefine the moving rule of particles, according to the hall of fame and the best record"""scale=random()eta=random()scale_fame=random()forparticleinself:forfameinself.hall_of_fame:ifparticle.fitness<fame.fitness:particle.update_vilocity_by_fame(fame,scale,scale_fame,self.inertia,self.learning_factor,self.acceleration_coefficient)particle.position=particle.position+particle.velocitybreakforparticleinself.hall_of_fame:particle.update_vilocity(scale,self.inertia,self.learning_factor)particle.position=particle.position+particle.velocityIf you want to apply PSO, then you can defineclassMyParticleSwarm(ParticleSwarm,BasePopulation):element_class=_Particledefault_size=20pop=MyParticleSwarm.random()Of course, it is not mandatory. It is allowed to inheritParticleSwarmfrom for exampleHOFPopulationdirectly.