Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.pyc
5 changes: 4 additions & 1 deletion alogos/_grammar/data_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class Grammar:
"production_rules",
"start_symbol",
"_cache",
"rng",
)

# Initialization and reset
Expand Down Expand Up @@ -246,6 +247,8 @@ def __init__(
):
_warnings._warn_multiple_grammar_specs()

self.rng = kwargs.pop("rng", _random.Random())

# Transformation
if bnf_text is not None:
self.from_bnf_text(bnf_text, **kwargs)
Expand Down Expand Up @@ -2560,7 +2563,7 @@ def node_seq_repr(node_seq):
derivation = [[self.root_node]]
stack = [self.root_node]
while stack:
idx = _random.randint(0, len(stack) - 1)
idx = rng.randint(0, len(stack) - 1)
nt_node = stack.pop(idx)
derivation = next_derivation_step(derivation, nt_node, nt_node.children)
new_nt_nodes = [node for node in nt_node.children if node.children]
Expand Down
3 changes: 1 addition & 2 deletions alogos/_optimization/ea/algorithm.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Data structure for a evolutionary algorithm that uses G3P systems."""

import random as _random
from numbers import Number as _Number

import pylru as _pylru
Expand Down Expand Up @@ -970,7 +969,7 @@ def _cross_over(self, parent_population):
n = len(parent_population)
while len(crossed_over_individuals) < n:
if n > 2:
parent0, parent1 = _random.sample(parent_population.individuals, 2)
parent0, parent1 = pr["rng"].sample(parent_population.individuals, 2)
elif n == 2:
parent0, parent1 = parent_population.individuals
else:
Expand Down
19 changes: 9 additions & 10 deletions alogos/_optimization/ea/operators/selection.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import random as _random
from functools import lru_cache as _lru_cache
from math import isfinite as _isfinite
from math import isnan as _isnan
Expand All @@ -17,7 +16,7 @@ def uniform(individuals, sample_size, objective, parameters, state):
- Eiben, Introduction to Evolutionary Computing (2e 2015): p. 86

"""
sel_inds = _uniform_sampling_with_replacement(individuals, sample_size)
sel_inds = _uniform_sampling_with_replacement(individuals, sample_size, parameters)
return sel_inds


Expand Down Expand Up @@ -60,15 +59,15 @@ def tournament(individuals, sample_size, objective, parameters, state):
sel_inds = []
if objective == "min":
for _ in range(sample_size):
competitors = _uniform_sampling_with_replacement(individuals, ts)
competitors = _uniform_sampling_with_replacement(individuals, ts, parameters)
winner = competitors[0]
for competitor in competitors[1:]:
if competitor.less_than(winner, objective):
winner = competitor
sel_inds.append(winner)
else:
for _ in range(sample_size):
competitors = _uniform_sampling_with_replacement(individuals, ts)
competitors = _uniform_sampling_with_replacement(individuals, ts, parameters)
winner = competitors[0]
for competitor in competitors[1:]:
if competitor.greater_than(winner, objective):
Expand All @@ -89,7 +88,7 @@ def rank_proportional(individuals, sample_size, objective, parameters, state):
probabilities = _calculate_rank_probabilities(num_inds, mu, eta_plus)

# Sampling
sel_inds = _stochastic_universal_sampling(srt_inds, probabilities, sample_size)
sel_inds = _stochastic_universal_sampling(srt_inds, probabilities, sample_size, parameters)
return sel_inds


Expand All @@ -115,7 +114,7 @@ def fitness_proportional(individuals, sample_size, objective, parameters, state)
probabilities = _calculate_fitness_probabilities(scaled_fitnesses, usage_tracking)

# Sampling
sel_inds = _stochastic_universal_sampling(individuals, probabilities, sample_size)
sel_inds = _stochastic_universal_sampling(individuals, probabilities, sample_size, parameters)
return sel_inds


Expand Down Expand Up @@ -231,13 +230,13 @@ def _sort_individuals(individuals, reverse=False):
# Sampling algorithms: draw individuals from a population according to a probability distribution


def _uniform_sampling_with_replacement(population, sample_size):
def _uniform_sampling_with_replacement(population, sample_size, parameters):
"""Uniform sampling with replacement."""
selected_individuals = [_random.choice(population) for _ in range(sample_size)]
selected_individuals = [parameters["rng"].choice(population) for _ in range(sample_size)]
return selected_individuals


def _stochastic_universal_sampling(population, probabilities, sample_size):
def _stochastic_universal_sampling(population, probabilities, sample_size, parameters):
"""Stochastic universal sampling (SUS) for drawing individuals from a probability distribution.

A sampling algorithm by James Baker, designed as improvement to roulette wheel sampling.
Expand All @@ -251,7 +250,7 @@ def _stochastic_universal_sampling(population, probabilities, sample_size):
lambda_inv = 1.0 / sample_size

# Draw from uniform distribution
rand_uniform = _random.uniform(0.0, lambda_inv)
rand_uniform = parameters["rng"].uniform(0.0, lambda_inv)

# Decide which individual gets how many children (=copies of itself)
num_children = []
Expand Down
2 changes: 2 additions & 0 deletions alogos/_optimization/ea/parameters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Default parameters for an evolutionary algorithm."""

import random as _random
from ..._utilities.parametrization import ParameterCollection as _ParameterCollection


Expand All @@ -9,6 +10,7 @@
population_size=100,
offspring_size=100,
verbose=False,
rng=_random.Random(),
# Storage
database_on=False,
database_location=":memory:",
Expand Down
12 changes: 5 additions & 7 deletions alogos/systems/_shared/crossover.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Shared crossover functions for several systems."""

from random import randint as _ri

from ... import exceptions as _exceptions


Expand Down Expand Up @@ -51,11 +49,11 @@ def two_point_length_preserving(grammar, gt1, gt2, parameters, representation):
_exceptions.raise_crossover_lp_error2()

# Get a random segment in genotype 1: choose two valid random points
s1, e1 = _get_two_different_points(l1)
s1, e1 = _get_two_different_points(l1, parameters)

# Get a random segment in genotype 2: choose a valid start position for a same-sized segment
lseg = e1 - s1
s2 = _ri(0, l2 - lseg)
s2 = grammar.rng.randint(0, l2 - lseg)
e2 = s2 + lseg

# Crossover: Swap two randomly positioned, but equally long segments
Expand All @@ -64,11 +62,11 @@ def two_point_length_preserving(grammar, gt1, gt2, parameters, representation):
return _GT(n1), _GT(n2)


def _get_two_different_points(n):
def _get_two_different_points(n, parameters):
"""Get two different numbers between 0 and n-1 to use as indices."""
while True:
p1 = _ri(0, n)
p2 = _ri(0, n)
p1 = grammar.rng.randint(0, n)
p2 = grammar.rng.randint(0, n)
if p1 == p2:
continue
if (p1 == 0 and p2 == n) or (p1 == n and p2 == 0):
Expand Down
6 changes: 2 additions & 4 deletions alogos/systems/_shared/init_individual.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Shared individual initialization functions of different systems."""

import random as _random

from ... import exceptions as _exceptions
from ..._utilities.parametrization import get_given_or_default as _get_given_or_default
from . import init_tree as _init_tree
Expand Down Expand Up @@ -193,12 +191,12 @@ def random_genotype(grammar, parameters, default_parameters, representation, map
# Parameter extraction
gl = _get_given_or_default("genotype_length", parameters, default_parameters)
cs = _get_given_or_default("codon_size", parameters, default_parameters)

# Transformation
try:
assert cs > 0
max_int = 2**cs - 1
random_genotype = [_random.randint(0, max_int) for _ in range(gl)]
random_genotype = [grammar.rng.randint(0, max_int) for _ in range(gl)]
except Exception:
_exceptions.raise_init_ind_rand_gt_error()

Expand Down
17 changes: 8 additions & 9 deletions alogos/systems/_shared/init_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import copy as _copy
import itertools as _itertools
import random as _random

from ... import exceptions as _exceptions
from ..._grammar import data_structures as _data_structures
Expand Down Expand Up @@ -59,7 +58,7 @@ def uniform(grammar, max_expansions=10_000):
# 2) Choose rule: randomly
rules = grammar.production_rules[chosen_nt_node.symbol]
if len(rules) > 1:
chosen_rule_idx = _random.randint(0, len(rules) - 1)
chosen_rule_idx = grammar.rng.randint(0, len(rules) - 1)
else:
chosen_rule_idx = 0
chosen_rule = rules[chosen_rule_idx]
Expand Down Expand Up @@ -123,7 +122,7 @@ def weighted(grammar, max_expansions=10_000, reduction_factor=0.96):
def weighted_choice(lhs, weights):
rhs_list = grammar.production_rules[lhs]
weight_list = weights[lhs]
chosen_rule_cumulative_weight = sum(weight_list) * _random.random()
chosen_rule_cumulative_weight = sum(weight_list) * grammar.rng.random()
chosen_rule_idx = 0
for cumulative_weight in _itertools.accumulate(weight_list):
if cumulative_weight >= chosen_rule_cumulative_weight:
Expand Down Expand Up @@ -207,7 +206,7 @@ def ptc2(grammar, max_expansions=100):
expansions = 0
while stack:
# 1) Choose nonterminal: random
chosen_nt_idx = _random.choice(range(len(stack)))
chosen_nt_idx = grammar.rng.choice(range(len(stack)))
chosen_nt_node = stack.pop(chosen_nt_idx)
# 2) Choose rule: randomly from those that do not lead over the wanted expansions
rules = grammar.production_rules[chosen_nt_node.symbol]
Expand All @@ -222,7 +221,7 @@ def ptc2(grammar, max_expansions=100):
min_expansions_per_symbol,
stack,
)
chosen_rule_idx = _random.randint(0, len(rules) - 1)
chosen_rule_idx = grammar.rng.randint(0, len(rules) - 1)
chosen_rule = rules[chosen_rule_idx]
# 3) Expand the chosen nonterminal with the rhs of the chosen rule
new_nodes = dt._expand(chosen_nt_node, chosen_rule)
Expand Down Expand Up @@ -267,7 +266,7 @@ def grow_one_branch_to_max_depth(grammar, max_depth=20):
stack = [(dt.root_node, 0)]
while stack:
# 1) Choose nonterminal: random
chosen_nt_idx = _random.choice(range(len(stack)))
chosen_nt_idx = grammar.rng.choice(range(len(stack)))
chosen_nt_node, depth = stack.pop(chosen_nt_idx)
# 2) Choose rule: randomly from those that do not lead over the wanted depth
rules = grammar.production_rules[chosen_nt_node.symbol]
Expand All @@ -288,7 +287,7 @@ def grow_one_branch_to_max_depth(grammar, max_depth=20):
rules = _filter_rules_for_grow(
chosen_nt_node.symbol, rules, depth, max_depth, min_depths
)
chosen_rule_idx = _random.randint(0, len(rules) - 1)
chosen_rule_idx = grammar.rng.randint(0, len(rules) - 1)
chosen_rule = rules[chosen_rule_idx]
# 3) Expand the chosen nonterminal with the rhs of the chosen rule
new_nodes = dt._expand(chosen_nt_node, chosen_rule)
Expand Down Expand Up @@ -333,7 +332,7 @@ def grow_all_branches_within_max_depth(grammar, max_depth=20):
rules = _filter_rules_for_grow(
chosen_nt_node.symbol, rules, depth, max_depth, min_depths
)
chosen_rule_idx = _random.randint(0, len(rules) - 1)
chosen_rule_idx = grammar.rng.randint(0, len(rules) - 1)
chosen_rule = rules[chosen_rule_idx]
# 3) Expand the chosen nonterminal with the rhs of the chosen rule
new_nodes = dt._expand(chosen_nt_node, chosen_rule)
Expand Down Expand Up @@ -381,7 +380,7 @@ def grow_all_branches_to_max_depth(grammar, max_depth=20):
rules = _filter_rules_for_full(
chosen_nt_node.symbol, rules, depth, max_depth, min_depths, is_recursive
)
chosen_rule_idx = _random.randint(0, len(rules) - 1)
chosen_rule_idx = grammar.rng.randint(0, len(rules) - 1)
chosen_rule = rules[chosen_rule_idx]
# 3) Expand the chosen nonterminal with the rhs of the chosen rule
new_nodes = dt._expand(chosen_nt_node, chosen_rule)
Expand Down
8 changes: 4 additions & 4 deletions alogos/systems/_shared/neighborhood.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
import functools as _functools
import itertools as _itertools
import operator as _operator
import random as _random


def generate_combinations(num_choices_per_pos, distance, max_size=None):
def generate_combinations(num_choices_per_pos, distance, parameters, max_size=None):
"""Generate all combinations of choices available at each position.

In each returned combination, the value 0 means that the original choice
Expand Down Expand Up @@ -59,6 +58,7 @@ def generate_combinations(num_choices_per_pos, distance, max_size=None):
max_size,
count,
count_per_pos_comb,
parameters
)
else:
# If no, generate the entire set
Expand All @@ -75,11 +75,11 @@ def _product(iterable):


def _generate_some_combinations(
choices_per_pos, pos_combinations, num_pos, max_size, count, count_per_pos_comb
choices_per_pos, pos_combinations, num_pos, max_size, count, count_per_pos_comb, parameters
):
"""Generate a random subset of all possible combinations."""
# Choose random neighbors by selecting indices from the enumerated possibilities
chosen = sorted(_random.sample(range(count), max_size))
chosen = sorted(parameters["rng"].sample(range(count), max_size))

# Construct the chosen neighbors
if chosen:
Expand Down
8 changes: 3 additions & 5 deletions alogos/systems/cfggp/crossover.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
"""Crossover functions for CFG-GP."""

import random as _random

from ..._utilities.parametrization import get_given_or_default as _get_given_or_default
from . import default_parameters as _default_parameters
from . import representation as _representation


def subtree_exchange(grammar, genotype1, genotype2, parameters=None):
def subtree_exchange(grammar, genotype1, genotype2, parameters):
"""Generate new CFG-GP genotypes by exchanging suitable subtrees.

Randomly select nodes containing the same nonterminal in two trees
Expand Down Expand Up @@ -89,10 +87,10 @@ def subtree_exchange(grammar, genotype1, genotype2, parameters=None):
si = s1.intersection(s2)
if si:
# Randomly select a non-terminal in the first tree, which is also part of second tree
n1 = _random.choice([n for n in ns1 if n.symbol.text in si])
n1 = grammar.rng.choice([n for n in ns1 if n.symbol.text in si])
t1 = n1.symbol.text
# Randomly select the same non-terminal in the second genotype
n2 = _random.choice([n for n in ns2 if n.symbol.text == t1])
n2 = grammar.rng.choice([n for n in ns2 if n.symbol.text == t1])
# Swap subtrees by exchanging the list of child nodes
n1.children, n2.children = n2.children, n1.children
# Ensure max_depth constraint is not violated
Expand Down
6 changes: 2 additions & 4 deletions alogos/systems/cfggp/mutation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Mutation functions for CFG-GP."""

import random as _random

from ..._grammar import data_structures as _data_structures
from ..._utilities.parametrization import get_given_or_default as _get_given_or_default
from .._shared import _cached_calculations
Expand Down Expand Up @@ -59,7 +57,7 @@ def subtree_replacement(grammar, genotype, parameters=None):
nodes_and_depths.append((node, depth))
stack = stack + [(node, depth + 1) for node in node.children]
# - Randomly select a node for mutation
node, depth = _random.choice(nodes_and_depths)
node, depth = grammar.rng.choice(nodes_and_depths)
# - Replace the node's subtree with a randomly generated new one
node.children = []
_grow_random_subtree(grammar, max_depth, start_depth=depth, root_node=node)
Expand All @@ -85,7 +83,7 @@ def _grow_random_subtree(grammar, max_depth, start_depth, root_node):
rules = _filter_rules_for_grow(
chosen_nt_node.symbol, rules, depth, max_depth, min_depths
)
chosen_rule_idx = _random.randint(0, len(rules) - 1)
chosen_rule_idx = grammar.rng.randint(0, len(rules) - 1)
chosen_rule = rules[chosen_rule_idx]
# 3) Expand the chosen nonterminal with the rhs of the chosen rule
new_nodes = dt._expand(chosen_nt_node, chosen_rule)
Expand Down
4 changes: 2 additions & 2 deletions alogos/systems/cfggp/neighborhood.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
_T = _grammar.data_structures.TerminalSymbol


def subtree_replacement(grammar, genotype, parameters=None):
def subtree_replacement(grammar, genotype, parameters):
"""Systematically change a chosen number of nodes.

Parameters
Expand Down Expand Up @@ -58,7 +58,7 @@ def subtree_replacement(grammar, genotype, parameters=None):

# Generate combinations of choices
combinations = _shared.neighborhood.generate_combinations(
num_choices_per_pos, distance, max_size
num_choices_per_pos, distance, max_size, parameters
)

# Neighborhood construction
Expand Down
Loading