Skip to content

Commit

Permalink
some docstrings refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
PasaOpasen committed Apr 13, 2024
1 parent e86e28a commit c4a9bb3
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 74 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ https://pasaopasen.github.io/geneticalgorithm2/
- [Updates information](#updates-information)
- [**Future**](#future)
- [**TODO firstly**](#todo-firstly)
- [6.8.8 reborn](#688-reborn)
- [6.9.0 reborn](#690-reborn)
- [6.8.7 minor update](#687-minor-update)
- [6.8.6 minor update](#686-minor-update)
- [6.8.5 minor update](#685-minor-update)
Expand Down Expand Up @@ -157,7 +157,7 @@ pip install geneticalgorithm2[full]
- Remove old style mensions from README


## 6.8.8 reborn
## 6.9.0 reborn

- recreate the repository without excess heavy files materials
- host the [code documentation](https://pasaopasen.github.io/geneticalgorithm2/)
Expand Down
11 changes: 10 additions & 1 deletion geneticalgorithm2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@
from typing_extensions import TypeAlias

from .classes import Generation, AlgorithmParams
"""
Genetic Algorithm (Elitist version) for Python3.8+
An implementation of elitist genetic algorithm for solving problems with
continuous, integers, or mixed variables.
repo path: https://github.com/PasaOpasen/geneticalgorithm2
code docs path: https://pasaopasen.github.io/geneticalgorithm2/
"""

from .geneticalgorithm2 import GeneticAlgorithm2

# to keep backward compatibility
# to keep backward compatibility like it was since geneticalgorithm package
geneticalgorithm2: TypeAlias = GeneticAlgorithm2

from .mutations import Mutations
Expand Down
19 changes: 13 additions & 6 deletions geneticalgorithm2/crossovers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,29 @@
from .utils.aliases import TypeAlias, array1D

CrossoverFunc: TypeAlias = Callable[[array1D, array1D], Tuple[array1D, array1D]]
"""
Function (parent1, parent2) -> (child1, child2)
"""


def get_copies(x: array1D, y: array1D) -> Tuple[array1D, array1D]:
return x.copy(), y.copy()


class Crossover:
"""Crossover functions static class"""

@staticmethod
def crossovers_dict() -> Dict[str, CrossoverFunc]:
return {
'one_point': Crossover.one_point(),
'two_point': Crossover.two_point(),
'uniform': Crossover.uniform(),
'segment': Crossover.segment(),
'shuffle': Crossover.shuffle(),
n: getattr(Crossover, n)()
for n in (
'one_point',
'two_point',
'uniform',
'segment',
'shuffle',
)
}

@staticmethod
Expand Down Expand Up @@ -92,7 +99,7 @@ def func(x: array1D, y: array1D):

ofs1, ofs2 = get_copies(x, y)

index = np.random.choice(np.arange(0, x.size), x.size, replace = False)
index = np.random.choice(np.arange(0, x.size), x.size, replace=False)

ran = np.random.randint(0, x.size)

Expand Down
10 changes: 2 additions & 8 deletions geneticalgorithm2/geneticalgorithm2.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,13 @@


class GeneticAlgorithm2:

"""
Genetic Algorithm (Elitist version) for Python3.8+
An implementation of elitist genetic algorithm for solving problems with
continuous, integers, or mixed variables.
repo path: https://github.com/PasaOpasen/geneticalgorithm2
code docs path: https://pasaopasen.github.io/geneticalgorithm2/
Genetic algorithm optimization process
"""

default_params = AlgorithmParams()
PROGRESS_BAR_LEN = 20
"""max count of symbols in the progress bar"""

@property
def output_dict(self):
Expand Down
18 changes: 13 additions & 5 deletions geneticalgorithm2/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,26 @@
MutationFloatFunc: TypeAlias = Callable[[float, float, float], float]
MutationIntFunc: TypeAlias = Callable[[int, int, int], int]
MutationFunc: TypeAlias = Union[MutationIntFunc, MutationFloatFunc]
"""
Function (x, left, right) -> value
Which mutates x to value according to bounds (left, right)
"""


class Mutations:
"""Mutations functions static class"""

@staticmethod
def mutations_dict() -> Dict[str, MutationFloatFunc]:
return {
'uniform_by_x': Mutations.uniform_by_x(),
'uniform_by_center': Mutations.uniform_by_center(),
'gauss_by_center': Mutations.gauss_by_center(),
'gauss_by_x': Mutations.gauss_by_x(),
n: getattr(Mutations, n)()
for n in (
'uniform_by_x',
'uniform_by_center',
'gauss_by_center',
'gauss_by_x',
)
}

@staticmethod
Expand All @@ -31,7 +40,6 @@ def mutations_discrete_dict() -> Dict[str, MutationIntFunc]:
'uniform_discrete': Mutations.uniform_discrete()
}


@staticmethod
def uniform_by_x() -> MutationFloatFunc:

Expand Down
118 changes: 66 additions & 52 deletions geneticalgorithm2/selections.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,76 +8,88 @@
from .utils.aliases import array1D, TypeAlias

SelectionFunc: TypeAlias = Callable[[array1D, int], array1D]
"""
Function (scores, count to select) -> indexes of selected
"""


#region UTILS

def inverse_scores(scores: array1D) -> array1D:
"""
inverses scores (min val goes to max)
"""
minobj = scores[0]
normobj = scores - minobj if minobj < 0 else scores

return (np.amax(normobj) + 1) - normobj


def roulette(scores: array1D, parents_count: int) -> array1D:
"""simplest roulette selector for which the highest score means more preferred"""

sum_normobj = np.sum(scores)
prob = scores / sum_normobj
cumprob = np.cumsum(prob)

parents_indexes = np.array(
[
index if index < cumprob.size else np.random.randint(0, index - 1)
for index in (
np.searchsorted(cumprob, np.random.random())
for _ in range(parents_count)
)
]
)

return parents_indexes


#endregion

class Selection:
"""
Selections functions static class
"""

@staticmethod
def selections_dict() -> Dict[str, SelectionFunc]:
return {
'fully_random': Selection.fully_random(),
'roulette': Selection.roulette(),
'stochastic': Selection.stochastic(),
'sigma_scaling': Selection.sigma_scaling(),
'ranking': Selection.ranking(),
'linear_ranking': Selection.linear_ranking(),
'tournament': Selection.tournament(),
n: getattr(Selection, n)()
for n in (
'fully_random',
'roulette',
'stochastic',
'sigma_scaling',
'ranking',
'linear_ranking',
'tournament',
)
}

@staticmethod
def __inverse_scores(scores: array1D) -> array1D:
"""
inverse scores (min val goes to max)
"""
minobj = scores[0]
normobj = scores - minobj if minobj < 0 else scores

return (np.amax(normobj) + 1) - normobj

@staticmethod
def fully_random() -> SelectionFunc:
"""returns the selector of fully random parents (for tests purposes)"""

def func(scores: array1D, parents_count: int):
indexes = np.arange(parents_count)
return np.random.choice(indexes, parents_count, replace = False)
return np.random.choice(indexes, parents_count, replace=False)

return func

@staticmethod
def __roulette(scores: array1D, parents_count: int) -> array1D:

sum_normobj = np.sum(scores)
prob = scores/sum_normobj
cumprob = np.cumsum(prob)

parents_indexes = np.empty(parents_count)

# it can be vectorized
for k in range(parents_count):
index = np.searchsorted(cumprob, np.random.random())
if index < cumprob.size:
parents_indexes[k] = index
else:
parents_indexes[k] = np.random.randint(0, index - 1)

return parents_indexes

@staticmethod
def roulette() -> SelectionFunc:

def func(scores: array1D, parents_count: int):

normobj = Selection.__inverse_scores(scores)

return Selection.__roulette(normobj, parents_count)
return roulette(inverse_scores(scores), parents_count)

return func

@staticmethod
def stochastic() -> SelectionFunc:

def func(scores: np.ndarray, parents_count: int):
f = Selection.__inverse_scores(scores)
f = inverse_scores(scores)

fN: float = 1.0 / parents_count
k: int = 0
Expand Down Expand Up @@ -105,7 +117,7 @@ def func(scores: np.ndarray, parents_count: int):
def sigma_scaling(epsilon: float = 0.01, is_noisy: bool = False) -> SelectionFunc:

def func(scores: array1D, parents_count):
f = Selection.__inverse_scores(scores)
f = inverse_scores(scores)

sigma = np.std(f, ddof = 1) if is_noisy else np.std(f)
average = np.mean(f)
Expand All @@ -123,31 +135,34 @@ def func(scores: array1D, parents_count):
def ranking() -> SelectionFunc:

def func(scores: array1D, parents_count: int):
return Selection.__roulette(1 + np.arange(parents_count)[::-1], parents_count)
return roulette(1 + np.arange(parents_count)[::-1], parents_count)

return func

@staticmethod
def linear_ranking(selection_pressure: float = 1.5) -> SelectionFunc:

assert (selection_pressure > 1 and selection_pressure < 2), f"selection_pressure should be in (1, 2), but got {selection_pressure}"
assert 1 <= selection_pressure <= 2, f"selection_pressure should be in (1, 2), but got {selection_pressure}"

def func(scores: array1D, parents_count: int):
tmp = parents_count * (parents_count-1)
alpha = (2 * parents_count - selection_pressure * (parents_count + 1)) / tmp
beta = 2 * (selection_pressure - 1) / tmp



a = -2 * alpha - beta
b = (2 * alpha + beta) ** 2
c = 8 * beta
d = 2 * beta

indexes = np.arange(parents_count)

return np.array([indexes[-round((a + math.sqrt(b + c*random.random()))/d)] for _ in range(parents_count)])


return np.array(
[
indexes[-round((a + math.sqrt(b + c * random.random())) / d)]
for _ in range(parents_count)
]
)

return func

@staticmethod
Expand All @@ -163,11 +178,10 @@ def func(scores: array1D, parents_count: int):

return np.array(
[
np.min(np.random.choice(indexes, tau, replace = False))
np.min(np.random.choice(indexes, tau, replace=False))
for _ in range(parents_count)
]
)


return func

Expand Down

0 comments on commit c4a9bb3

Please sign in to comment.