Skip to content

Commit

Permalink
Add Individual base class.
Browse files Browse the repository at this point in the history
  • Loading branch information
PytLab committed Dec 12, 2017
1 parent 77770d7 commit c41112f
Show file tree
Hide file tree
Showing 17 changed files with 148 additions and 152 deletions.
2 changes: 1 addition & 1 deletion gaft/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .individual import GAIndividual
from .individual import *
from .population import GAPopulation

163 changes: 85 additions & 78 deletions gaft/components/individual.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,85 @@
from ..mpiutil import mpi


class GAIndividual(object):
def __init__(self, ranges, encoding='binary', eps=0.001, verbosity=1):
class IndividualBase(object):
''' Base class for individuals.
'''
def __init__(self, ranges, eps=0.001):
self.ranges = ranges
self.eps = eps
self.precisions = eps

self._check_parameters()

self.variants, self.chromsome = [], []

def _check_parameters(self):
'''
Private helper function to check individual parameters.
'''
# Check decrete precision.
if type(self.eps) is float:
self.eps = [self.eps]*len(self.ranges)
self.precisions = self.eps
else:
# Check eps length.
if len(self.eps) != len(self.ranges):
raise ValueError('Lengths of eps and ranges should be the same')
for eps, (a, b) in zip(self.eps, self.ranges):
if eps > (b - a):
msg = 'Invalid eps {} in range ({}, {})'.format(eps, a, b)
raise ValueError(msg)

def _rand_variants(self):
''' Initialize individual variants randomly.
'''
variants = []
for eps, (a, b) in zip(self.precisions, self.ranges):
n_intervals = (b - a)//eps
n = int(uniform(0, n_intervals + 1))
variants.append(a + n*eps)
return variants

def init(self, chromsome=None, variants=None):
'''
Initialize the individual by providing chromsome or variants.
If both chromsome and variants are provided, only the chromsome would
be used. If neither is provided, individual would be initialized randomly.
:param chromsome: chromesome sequence for the individual
:type chromsome: list of float/int.
:param variants: the variable vector of the target function.
:type variants: list of float.
'''
if not any([chromsome, variants]):
self.variants = self._rand_variants()
self.chromsome = self.encode()
elif chromsome:
self.chromsome = chromsome
self.variants = self.decode()
else:
self.variants = variants
self.chromsome = self.encode()

return self

def encode(self):
''' *NEED IMPLIMENTATION*
Convert variants to chromsome sequence.
'''
raise NotImplementedError

def decode(self):
''' *NEED IMPLIMENTATION*
Convert chromsome sequence to variants.
'''
raise NotImplementedError


class BinaryIndividual(IndividualBase):
def __init__(self, ranges, eps=0.001, verbosity=1):
'''
Class for individual in population. Random variants will be initialized
by default.
Expand All @@ -25,116 +102,49 @@ def __init__(self, ranges, encoding='binary', eps=0.001, verbosity=1):
:param ranges: value ranges for all entries in variants.
:type ranges: list of range tuples. e.g. [(0, 1), (-1, 1)]
:param encoding: gene encoding, 'decimal' or 'binary', default is binary.
:type encoding: str
:param eps: decrete precisions for binary encoding, default is 0.001.
:type eps: float or float list with the same length with ranges.
:param verbosity: The verbosity level of info output.
:param verbosity: int, 0 or 1(default)
'''
super(self.__class__, self).__init__(ranges, eps)

self.ranges = ranges
self.eps = eps
self.encoding = encoding
self.verbosity = verbosity

# Check parameters.
self._check_parameters()

# Lengths for all binary sequence in chromsome and adjusted decrete precisions.
self.lengths, self.precisions = [], []
self.lengths = []

for (a, b), eps in zip(self.ranges, self.eps):
for i, ((a, b), eps) in enumerate(zip(self.ranges, self.eps)):
length = int(log2((b - a)/eps))
precision = (b - a)/(2**length)

if precision != eps and mpi.is_master and self.verbosity:
print('Precision loss {} -> {}'.format(eps, precision))

self.lengths.append(length)
self.precisions.append(precision)
self.precisions[i] = precision

# The start and end indices for each gene segment for entries in variants.
self.gene_indices = self._get_gene_indices()

# Generate randomly.
self.variants = self._init_variants()

# Gene encoding.
self.chromsome = self.encode()

def init(self, chromsome=None, variants=None):
'''
Initialize the individual by providing chromsome or variants.
If both chromsome and variants are provided, only the chromsome would
be used.
:param chromsome: chromesome sequence for the individual
:type chromsome: list of float/int.
:param variants: the variable vector of the target function.
:type variants: list of float.
'''
if not any([chromsome, variants]):
msg = 'Chromsome or variants must be supplied for individual initialization'
raise ValueError(msg)

if chromsome:
self.chromsome = chromsome
self.variants = self.decode()
else:
self.variants = variants
self.chromsome = self.encode()

return self
# Initialize individual randomly.
self.init()

def clone(self):
'''
Clone a new individual from current one.
'''
indv = self.__class__(self.ranges,
encoding=self.encoding,
eps=self.eps,
verbosity=self.verbosity)
indv.init(chromsome=self.chromsome)
return indv

def _check_parameters(self):
'''
Private helper function to check individual parameters.
'''
# Check decrete precision.
if type(self.eps) is float:
self.eps = [self.eps]*len(self.ranges)
else:
# Check eps length.
if len(self.eps) != len(self.ranges):
raise ValueError('Lengths of eps and ranges should be the same')
for eps, (a, b) in zip(self.eps, self.ranges):
if eps > (b - a):
msg = 'Invalid eps {} in range ({}, {})'.format(eps, a, b)
raise ValueError(msg)

def _init_variants(self):
'''
Initialize individual variants randomly.
'''
variants = []
for eps, (a, b) in zip(self.precisions, self.ranges):
n_intervals = (b - a)//eps
n = int(uniform(0, n_intervals + 1))
variants.append(a + n*eps)
return variants

def encode(self):
'''
Encode variant to gene sequence in individual using different encoding.
'''
if self.encoding == 'decimal':
return self.variants

chromsome = []
for var, (a, _), length, eps in zip(self.variants, self.ranges,
self.lengths, self.precisions):
Expand All @@ -146,9 +156,6 @@ def decode(self):
'''
Decode gene sequence to variants of target function.
'''
if self.encoding == 'decimal':
return self.variants

variants = [self.decimalize(self.chromsome[start: end], eps, lower_bound)
for (start, end), (lower_bound, _), eps in
zip(self.gene_indices, self.ranges, self.precisions)]
Expand Down
17 changes: 9 additions & 8 deletions gaft/components/population.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from .individual import GAIndividual
from .individual import IndividualBase, BinaryIndividual


class Memoized(object):
Expand Down Expand Up @@ -123,22 +123,23 @@ def init(self, indvs=None):
:param indvs: Initial individuals in population, randomly initialized
individuals are created if not provided.
:type indvs: list of GAIndividual
:type indvs: list of Individual object
'''
IndvType = self.indv_template.__class__

if indvs is None:
for _ in range(self.size):
indv = GAIndividual(ranges=self.indv_template.ranges,
encoding=self.indv_template.encoding,
eps=self.indv_template.eps,
verbosity=self.indv_template.verbosity)
indv = IndvType(ranges=self.indv_template.ranges,
eps=self.indv_template.eps,
verbosity=self.indv_template.verbosity)
self.individuals.append(indv)
else:
# Check individuals.
if len(indvs) != self.size:
raise ValueError('Invalid individuals number')
for indv in indvs:
if not isinstance(indv, GAIndividual):
raise ValueError('individual must be GAIndividual object')
if not isinstance(indv, IndividualBase):
raise ValueError('individual class must be subclass of IndividualBase')
self.individuals = indvs

self._updated = True
Expand Down
6 changes: 3 additions & 3 deletions gaft/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import pstats
import os

from .components import GAIndividual, GAPopulation
from .components import IndividualBase, GAPopulation
from .plugin_interfaces.operators import GASelection, GACrossover, GAMutation
from .plugin_interfaces.analysis import OnTheFlyAnalysis
from .mpiutil import mpi
Expand Down Expand Up @@ -257,8 +257,8 @@ def _fn_with_fitness_check(indv):
A wrapper function for fitness function with fitness value check.
'''
# Check indv type.
if not isinstance(indv, GAIndividual):
raise TypeError('indv must be a GAIndividual object')
if not isinstance(indv, IndividualBase):
raise TypeError('indv\'s class must be subclass of IndividualBase')

# Check fitness.
fitness = fn(indv)
Expand Down
14 changes: 7 additions & 7 deletions gaft/plugin_interfaces/metaclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import inspect
from functools import wraps

from ..components.individual import GAIndividual
from ..components.individual import IndividualBase
from ..components.population import GAPopulation
from ..mpiutil import master_only

Expand Down Expand Up @@ -76,9 +76,9 @@ def _wrapped_cross(self, father, mother):
''' Wrapper to add parameters type checking.
'''
# Check parameter types.
if not (isinstance(father, GAIndividual) and
isinstance(mother, GAIndividual)):
raise TypeError('father and mother must be GAIndividual object')
if not (isinstance(father, IndividualBase) and
isinstance(mother, IndividualBase)):
raise TypeError('father and mother\'s type must be subclass of IndividualBase')

return cross(self, father, mother)

Expand Down Expand Up @@ -115,8 +115,8 @@ def _wrapped_mutate(self, individual, engine):
''' Wrapper to add parameters type checking.
'''
# Check parameter types.
if not isinstance(individual, GAIndividual):
raise TypeError('individual must be a GAIndividual object')
if not isinstance(individual, IndividualBase):
raise TypeError('individual\' type must be subclass of IndividualBase')

return mutate(self, individual, engine)

Expand Down Expand Up @@ -154,7 +154,7 @@ def _wrapped_select(self, population, fitness):
'''
# Check parameter types.
if not isinstance(population, GAPopulation):
raise TypeError('father and mother must be GAIndividual object')
raise TypeError('population must be GAPopulation object')
if not callable(fitness):
raise TypeError('fitness must be a callable object')

Expand Down
4 changes: 2 additions & 2 deletions tests/dynamic_linear_scaling_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from math import sin, cos

from gaft import GAEngine
from gaft.components import GAIndividual
from gaft.components import BinaryIndividual
from gaft.components import GAPopulation
from gaft.operators import RouletteWheelSelection
from gaft.operators import UniformCrossover
Expand All @@ -24,7 +24,7 @@ def test_run(self):
'''
Make sure GA engine can run correctly.
'''
indv_template = GAIndividual(ranges=[(0, 10)], encoding='binary', eps=0.001, verbosity=0)
indv_template = BinaryIndividual(ranges=[(0, 10)], eps=0.001, verbosity=0)
population = GAPopulation(indv_template=indv_template, size=50).init()

# Create genetic operators.
Expand Down
4 changes: 2 additions & 2 deletions tests/engine_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from math import sin, cos

from gaft import GAEngine
from gaft.components import GAIndividual
from gaft.components import BinaryIndividual
from gaft.components import GAPopulation
from gaft.operators import RouletteWheelSelection
from gaft.operators import UniformCrossover
Expand All @@ -24,7 +24,7 @@ def test_run(self):
'''
Make sure GA engine can run correctly.
'''
indv_template = GAIndividual(ranges=[(0, 10)], encoding='binary', eps=0.001, verbosity=0)
indv_template = BinaryIndividual(ranges=[(0, 10)], eps=0.001, verbosity=0)
population = GAPopulation(indv_template=indv_template, size=50).init()

# Create genetic operators.
Expand Down
8 changes: 4 additions & 4 deletions tests/exponential_ranking_selection_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import unittest

from gaft.components.population import GAPopulation
from gaft.components.individual import GAIndividual
from gaft.components.individual import BinaryIndividual
from gaft.operators import ExponentialRankingSelection

class ExponentialRankingSelectionTest(unittest.TestCase):
Expand All @@ -20,15 +20,15 @@ def fitness(indv):
self.fitness = fitness

def test_selection(self):
indv = GAIndividual(ranges=[(0, 30)], verbosity=0)
indv = BinaryIndividual(ranges=[(0, 30)], verbosity=0)
p = GAPopulation(indv)
p.init()

selection = ExponentialRankingSelection()
father, mother = selection.select(p, fitness=self.fitness)

self.assertTrue(isinstance(father, GAIndividual))
self.assertTrue(isinstance(mother, GAIndividual))
self.assertTrue(isinstance(father, BinaryIndividual))
self.assertTrue(isinstance(mother, BinaryIndividual))
self.assertNotEqual(father.chromsome, mother.chromsome)

if '__main__' == __name__:
Expand Down
Loading

0 comments on commit c41112f

Please sign in to comment.