diff --git a/README.rst b/README.rst index 6193f18..0eab496 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ A **G**\ enetic **A**\ lgorithm **F**\ ramework in py\ **T**\ hon :target: https://www.python.org/downloads/release/python-351/ :alt: platform -.. image:: https://img.shields.io/badge/pypi-v0.5.4-blue.svg +.. image:: https://img.shields.io/badge/pypi-v0.6.0-blue.svg :target: https://pypi.python.org/pypi/gaft/ :alt: versions diff --git a/gaft/__init__.py b/gaft/__init__.py index 8c74bfc..78aabb6 100644 --- a/gaft/__init__.py +++ b/gaft/__init__.py @@ -3,7 +3,7 @@ from .engine import GAEngine -__version__ = '0.5.4' +__version__ = '0.6.0' __author__ = 'ShaoZhengjiang ' # Set root logger. diff --git a/gaft/components/binary_individual.py b/gaft/components/binary_individual.py index e9f1853..c9a68a1 100644 --- a/gaft/components/binary_individual.py +++ b/gaft/components/binary_individual.py @@ -8,7 +8,9 @@ import logging from .individual import IndividualBase -from ..mpiutil import mpi +from ..mpiutil import MPIUtil + +mpi = MPIUtil() class BinaryIndividual(IndividualBase): diff --git a/gaft/engine.py b/gaft/engine.py index 6b1f443..f7f8278 100644 --- a/gaft/engine.py +++ b/gaft/engine.py @@ -1,7 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -''' Genetic Algorithm engine definition ''' +''' Genetic Algorithm engine definition +''' import logging import math @@ -15,8 +16,9 @@ from .components import IndividualBase, Population from .plugin_interfaces.operators import Selection, Crossover, Mutation from .plugin_interfaces.analysis import OnTheFlyAnalysis -from .mpiutil import mpi +from .mpiutil import MPIUtil +mpi = MPIUtil() def do_profile(filename, sortby='tottime'): ''' Constructor for function profiling decorator. @@ -84,7 +86,29 @@ def __set__(self, engine, value): class GAEngine(object): - ''' Class for representing a Genetic Algorithm engine. + ''' Class for representing a Genetic Algorithm engine. The class is the + central object in GAFT framework for running a genetic algorithm optimization. + Once the population with individuals, a set of genetic operators and fitness + function are setup, the engine object unites these informations and provide + means for running a genetic algorthm optimization. + + :param population: The Population to be reproduced in evolution iteration. + :type population: :obj:`gaft.components.Population` + + :param selection: The Selection to be used for individual seleciton. + :type selection: :obj:`gaft.plugin_interfaces.operators.Selection` + + :param crossover: The Crossover to be used for individual crossover. + :type crossover: :obj:`gaft.plugin_interfaces.operators.Crossover` + + :param mutation: The Mutation to be used for individual mutation. + :type mutation: :obj:`gaft.plugin_interfaces.operators.Mutation` + + :param fitness: The fitness calculation function for an individual in population. + :type fitness: function + + :param analysis: All analysis class for on-the-fly analysis. + :type analysis: :obj:`OnTheFlyAnalysis` list ''' # Statistical attributes for population. fmax, fmin, fmean = StatVar('fmax'), StatVar('fmin'), StatVar('fmean') @@ -94,30 +118,6 @@ class GAEngine(object): def __init__(self, population, selection, crossover, mutation, fitness=None, analysis=None): - ''' Genetic algorithm engine. The class is the central object in GAFT framework - for running a genetic algorithm optimization. Once the population with - individuals, a set of genetic operators and fitness function are setup, - the engine object unites these informations and provide means for running - a genetic algorthm optimization. - - :param population: The Population to be reproduced in evolution iteration. - :type population: :obj:`gaft.components.Population` - - :param selection: The Selection to be used for individual seleciton. - :type selection: :obj:`gaft.plugin_interfaces.operators.Selection` - - :param crossover: The Crossover to be used for individual crossover. - :type crossover: :obj:`gaft.plugin_interfaces.operators.Crossover` - - :param mutation: The Mutation to be used for individual mutation. - :type mutation: :obj:`gaft.plugin_interfaces.operators.Mutation` - - :param fitness: The fitness calculation function for an individual in population. - :type fitness: function - - :param analysis: All analysis class for on-the-fly analysis. - :type analysis: :obj:`OnTheFlyAnalysis` list - ''' # Set logger. logger_name = 'gaft.{}'.format(self.__class__.__name__) self.logger = logging.getLogger(logger_name) @@ -303,10 +303,12 @@ def linear_scaling(self, target='max', ksi=0.5): :param ksi: Selective pressure adjustment value. :type ksi: float - Note:: + .. Note:: + Linear Scaling: - 1. arg max f(x), then f' = f - min{f(x)} + ksi; - 2. arg min f(x), then f' = max{f(x)} - f(x) + ksi; + 1. :math:`arg \max f(x)`, then the scaled fitness would be :math:`f - \min f(x) + {\\xi}` + 2. :math:`arg \min f(x)`, then the scaled fitness would be :math:`\max f(x) - f(x) + {\\xi}` + ''' def _linear_scaling(fn): # For original fitness calculation. @@ -346,9 +348,10 @@ def dynamic_linear_scaling(self, target='max', ksi0=2, r=0.9): value is 0.9 :type r: float in range [0.9, 0.999] - Note:: + .. Note:: Dynamic Linear Scaling: - For maximizaiton, f' = f(x) - min{f(x)} + ksi^k, k is generation number. + + For maximizaiton, :math:`f' = f(x) - \min f(x) + {\\xi}^{k}`, :math:`k` is generation number. ''' def _dynamic_linear_scaling(fn): # For original fitness calculation. diff --git a/gaft/mpiutil.py b/gaft/mpiutil.py index 7837ceb..f7a221d 100644 --- a/gaft/mpiutil.py +++ b/gaft/mpiutil.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ''' -Utitlities for parallelizing Genetic Algorithm by using MPI interfaces +A high-level utility class for parallelizing Genetic Algorithm by using MPI interfaces in distributed MPI environment. ''' @@ -16,12 +16,30 @@ MPI_INSTALLED = False +class Singleton(type): + def __call__(cls, *args, **kwargs): + if not hasattr(cls, '_instance'): + cls._instance = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instance + + class MPIUtil(object): + + __metaclass__ = Singleton + def __init__(self): + ''' Wrapper class for higher level of MPI interfaces that will create a + singleton for parallelization. + ''' logger_name = 'gaft.{}'.format(self.__class__.__name__) self._logger = logging.getLogger(logger_name) def bcast(self, data): + ''' Broadcast data to MPI processes + + :param data: Data to be broadcasted + :type data: any Python object + ''' if MPI_INSTALLED: mpi_comm = MPI.COMM_WORLD bdata = mpi_comm.bcast(data, root=0) @@ -31,12 +49,16 @@ def bcast(self, data): # Wrapper for common MPI interfaces. def barrier(self): + ''' Block until all processes in the communicator have reached this routine + ''' if MPI_INSTALLED: mpi_comm = MPI.COMM_WORLD mpi_comm.barrier() @property def rank(self): + ''' Get the rank of the calling process in the communicator + ''' if MPI_INSTALLED: mpi_comm = MPI.COMM_WORLD return mpi_comm.Get_rank() @@ -45,6 +67,8 @@ def rank(self): @property def size(self): + ''' Get the size of the group associated with a communicator + ''' if MPI_INSTALLED: mpi_comm = MPI.COMM_WORLD return mpi_comm.Get_size() @@ -53,12 +77,19 @@ def size(self): @property def is_master(self): + ''' If current process is the master process + ''' return self.rank == 0 # Utility methods. def split_seq(self, sequence): - ''' - Split the sequence according to rank and processor number. + ''' Split the sequence according to rank and processor number. + + :param sequence: Data sequence to be splitted + :type sequence: any Python object list + + :return: Sub data sequence for current process + :rtype: any Python object list ''' starts = [i for i in range(0, len(sequence), len(sequence)//self.size)] ends = starts[1: ] + [len(sequence)] @@ -67,8 +98,13 @@ def split_seq(self, sequence): return sequence[start: end] def split_size(self, size): - ''' - Split a size number(int) to sub-size number. + ''' Split a size number(int) to sub-size number. + + :param size: The size number to be splitted. + :type size: int + + :return: Sub-size for current process + :rtype: int ''' if size < self.size: warn_msg = ('Splitting size({}) is smaller than process ' + @@ -87,8 +123,13 @@ def split_size(self, size): return splited_sizes[self.rank] def merge_seq(self, seq): - ''' - Gather data in sub-process to root process. + ''' Gather data in sub-process to root process. + + :param seq: Sub data sequence for current process + :type seq: any Python object list + + :return: Merged data sequence from all processes in a communicator + :rtype: any Python object list ''' if self.size == 1: return seq @@ -98,13 +139,8 @@ def merge_seq(self, seq): return list(chain(*merged_seq)) -mpi = MPIUtil() - - def master_only(func): - ''' - Decorator to limit a function to be called - only in master process in MPI env. + ''' Decorator to limit a function to be called only in master process in MPI env. ''' @wraps(func) def _call_in_master_proc(*args, **kwargs): diff --git a/gaft/operators/mutation/flip_bit_mutation.py b/gaft/operators/mutation/flip_bit_mutation.py index 8d7195b..c29468f 100644 --- a/gaft/operators/mutation/flip_bit_mutation.py +++ b/gaft/operators/mutation/flip_bit_mutation.py @@ -5,11 +5,13 @@ from random import random, uniform -from ...mpiutil import mpi +from ...mpiutil import MPIUtil from ...plugin_interfaces.operators.mutation import Mutation from ...components.binary_individual import BinaryIndividual from ...components.decimal_individual import DecimalIndividual +mpi = MPIUtil() + class FlipBitMutation(Mutation): def __init__(self, pm): diff --git a/setup.py b/setup.py index 49de297..963e534 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ :target: https://www.python.org/downloads/release/python-351/ :alt: platform -.. image:: https://img.shields.io/badge/pypi-v0.5.4-blue.svg +.. image:: https://img.shields.io/badge/pypi-v0.6.0-blue.svg :target: https://pypi.python.org/pypi/gaft/ :alt: versions diff --git a/tests/mpiutil_test.py b/tests/mpiutil_test.py index b421a7c..8c6a401 100644 --- a/tests/mpiutil_test.py +++ b/tests/mpiutil_test.py @@ -6,7 +6,9 @@ import unittest -from gaft.mpiutil import mpi +from gaft.mpiutil import MPIUtil + +mpi = MPIUtil() class MPIUtilTest(unittest.TestCase):