Skip to content

Commit

Permalink
XOver not working, yeuch!
Browse files Browse the repository at this point in the history
  • Loading branch information
squillero committed Aug 24, 2023
1 parent efdf811 commit 3460bab
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 122 deletions.
25 changes: 23 additions & 2 deletions byron/classes/individual.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def dfs_nodes(self) -> list[int]:
return list(nx.dfs_preorder_nodes(self.structure_tree))

@property
def top_frame(self) -> FrameABC:
def top_frame(self) -> type[FrameABC]:
return self._genome.graph['top_frame']

@property
Expand All @@ -207,7 +207,6 @@ def genome(self, new_genome: nx.classes.MultiDiGraph):
"""Set the new Individual's genome (ie. the underlying NetworkX MultiDiGraph)."""
self._genome = new_genome
self._fitness = None
assert self.run_paranoia_checks()

@property
def lineage(self):
Expand Down Expand Up @@ -381,6 +380,28 @@ def run_paranoia_checks(self) -> bool:
assert isinstance(self._genome.nodes[0]['_selement'], MacroZero), f"{PARANOIA_TYPE_ERROR}: Incorrect NodeZero"

# ==[check structural parameter]=====================================
for node in (n for n, t in self._genome.nodes(data='_type') if t == MACRO_NODE):
for p_name, p_type in (
(p, P)
for p, P in self._genome.nodes[node]['_selement'].parameter_types.items()
if issubclass(P, ParameterStructuralABC)
):
assert (
sum(
1
for u, v, k in self._genome.out_edges(node, keys=True)
if k == self._genome.nodes[node][p_name].key
)
== 1
), f"{PARANOIA_VALUE_ERROR}: Problem with parameter '{p_name}' {p_type}"
keys = [
self._genome.nodes[node][p].key
for p, P in self._genome.nodes[node]['_selement'].parameter_types.items()
if issubclass(P, ParameterStructuralABC)
]
for key in (k for u, v, k, t in self._genome.out_edges(node, data='_type', keys=True) if t == LINK):
assert key in keys, f"{PARANOIA_VALUE_ERROR}: Unknown key {key} in none {node}"

assert all(
p._node_reference == NodeReference(self._genome, n)
for n in self._genome
Expand Down
2 changes: 2 additions & 0 deletions byron/classes/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ def reset_labels(G: nx.Graph) -> None:
"""Set Graph node labels to unique numbers"""
new_labels = {k: Node() for k in G.nodes if k != NODE_ZERO}
nx.relabel_nodes(G, new_labels, copy=False)
for k, v in new_labels.items():
G.nodes[v]['%old_label'] = k
fasten_subtree_parameters(NodeReference(G, NODE_ZERO))
16 changes: 13 additions & 3 deletions byron/classes/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,15 @@ class ParameterABC(SElement, Paranoid):

__slots__ = [] # Preventing the automatic creation of __dict__

COUNTER = 0
__LAST_BYRON_PARAMETER: int = 0

@staticmethod
def generate_new_key():
ParameterABC.__LAST_BYRON_PARAMETER += 1
return ParameterABC.__LAST_BYRON_PARAMETER

def __init__(self):
ParameterStructuralABC.COUNTER += 1
self._key = ParameterStructuralABC.COUNTER
self._key = ParameterABC.generate_new_key()
self._value = None

def __eq__(self, other: 'ParameterABC') -> bool:
Expand Down Expand Up @@ -113,6 +117,12 @@ def fasten(self, node_reference):
assert check_valid_type(node_reference.node, int)
assert node_reference.node in node_reference.graph
self._node_reference = node_reference
old_value = self.value
if old_value:
self.value = None
self._key = ParameterABC.generate_new_key()
if old_value:
self.value = old_value

def unfasten(self):
self._node_reference = None
Expand Down
3 changes: 2 additions & 1 deletion byron/ea/vanilla.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ def vanilla_ea(
op = rrandom.choice(ops)
parents = list()
for _ in range(op.num_parents):
parents.append(tournament_selection(population, 1))
parent = tournament_selection(population, 1)
parents.append(parent)
new_individuals += op(*parents)

if not new_individuals:
Expand Down
16 changes: 4 additions & 12 deletions byron/framework/parameter_structural_global.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,10 @@ def mutate(self, strength: float = 1.0) -> None:
else:
target = rrandom.choice(potential_targets, self.value, sigma=strength)
if target is None:
new_node_reference = unroll_selement(self._target_frame, self._node_reference.graph)

parent = NODE_ZERO

self._node_reference.graph.add_edge(parent, new_node_reference.node, _type=FRAMEWORK)
initialize_subtree(new_node_reference)

# second and last try
if strength == 1.0:
target = rrandom.choice(self.get_potential_targets(add_none=False))
else:
target = rrandom.choice(self.get_potential_targets(add_none=False), self.value, sigma=strength)
new_node = unroll_selement(self._target_frame, self._node_reference.graph)
self._node_reference.graph.add_edge(NODE_ZERO, new_node.node, _type=FRAMEWORK)
initialize_subtree(new_node)
target = new_node.node

if not target:
raise ByronOperatorFailure
Expand Down
41 changes: 17 additions & 24 deletions byron/operators/ea_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
__all__ = [
'merge_individuals',
'group_selements',
'group_selements_on_classpath',
'group_parameters_on_macro',
'group_parameters_on_classpath',
]
Expand Down Expand Up @@ -65,40 +64,34 @@ def _is_target(G: nx.MultiDiGraph, n: int) -> bool:


def group_selements(
individuals: Sequence[Individual], only_targets: bool = False, only_heads: bool = False
individuals: Sequence[Individual], choosy: bool = False, only_targets: bool = False, only_heads: bool = False
) -> dict[tuple[SElement], dict[Individual, list[int]]]:
"""Group macros"""

se_filters = list()
partial_filters = list()
if only_targets:
se_filters.append(lambda G, n: _is_target(G, n))
partial_filters.append(lambda G, n: _is_target(G, n))
if only_heads:
se_filters.append(lambda G, n: _is_head(G, n))
filter_ = lambda G, n: all(f(G, n) for f in se_filters)
partial_filters.append(lambda G, n: _is_head(G, n))
node_filter = lambda G, n: all(f(G, n) for f in partial_filters)

if choosy:
make_index = lambda G, path: tuple(G.nodes[v]['_selement'].__class__ for v in path)
else:
make_index = lambda G, path: ind.genome.nodes[path[-1]]['_selement'].__class__

groups = defaultdict(lambda: defaultdict(list))
for ind in individuals:
for node in filter(lambda n: filter_(ind.genome, n), ind.genome.nodes):
groups[ind.genome.nodes[node]['_selement'].__class__][ind].append(node)
if MacroZero in groups:
del groups[MacroZero]
for node, path in nx.single_source_dijkstra_path(ind.genome, 0).items():
groups[make_index(ind.genome, path)][ind].append(node)

# remove NodeZero
for cleanup in {MacroZero, (MacroZero,)}:
if cleanup in groups:
del groups[cleanup]
# remove entries with empty values
groups = {macro: {p: v for p, v in values.items() if v} for macro, values in groups.items()}
return {k: v for k, v in groups.items() if v}


def group_selements_on_classpath(
individuals: Sequence[Individual],
) -> dict[tuple[SElement], dict[Individual, list[int]]]:
"""Group macros with same classpath"""

groups = defaultdict(lambda: defaultdict(list))
for ind in individuals:
for node, path in nx.single_source_dijkstra_path(ind.structure_tree, 0).items():
groups[tuple(ind.genome.nodes[v]['_selement'].__class__ for v in path)][ind].append(node)
del groups[(MacroZero,)]
# remove entries with empty values
groups = {path: {p: v for p, v in values.items() if v} for path, values in groups.items()}
return {k: v for k, v in groups.items() if v}


Expand Down
37 changes: 24 additions & 13 deletions byron/operators/single_node_crossover.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,37 @@


@genetic_operator(num_parents=2)
def single_node_crossover(parent1: Individual, parent2: Individual):
def single_target_node_crossover(parent1: Individual, parent2: Individual):
assert parent1.run_paranoia_checks()
assert parent2.run_paranoia_checks()
common_selements = {k: v for k, v in group_selements([parent1, parent2], only_targets=True).items() if len(v) == 2}
if not common_selements:
raise ByronOperatorFailure
target = rrandom.choice(list(common_selements.keys()))
node1 = rrandom.choice(common_selements[target][parent1])
node2 = rrandom.choice(common_selements[target][parent2])
offspring = nx.compose(parent1.genome, parent2.genome)
node1_fanin = offspring.in_edges(node1, data='_type', keys=True)
node2_fanin = offspring.in_edges(node2, data='_type', keys=True)
node1_parent_link = rrandom.choice([(u, v, k, t) for u, v, k, t in node1_fanin if t != FRAMEWORK])
node2_parent_link = rrandom.choice([(u, v, k, t) for u, v, k, t in node2_fanin if t == node1_parent_link[3]])
new_genome = nx.compose(deepcopy(parent1.genome), deepcopy(parent2.genome))
node1_fanin = new_genome.in_edges(node1, data='_type', keys=True)
node2_fanin = new_genome.in_edges(node2, data='_type', keys=True)
try:
node1_parent_link = rrandom.choice([(u, v, k, t) for u, v, k, t in node1_fanin if t != FRAMEWORK])
node2_parent_link = rrandom.choice([(u, v, k, t) for u, v, k, t in node2_fanin if t == node1_parent_link[3]])
except:
raise ByronOperatorFailure

offspring.remove_edge(node1_parent_link[0], node1_parent_link[1], node1_parent_link[2])
offspring.remove_edge(node2_parent_link[0], node2_parent_link[1], node2_parent_link[2])
offspring.add_edge(node1_parent_link[0], node2_parent_link[1], node1_parent_link[2], _type=node1_parent_link[3])
discard_useless_components(offspring)
new_genome.remove_edge(node1_parent_link[0], node1_parent_link[1], node1_parent_link[2])
new_genome.remove_edge(node2_parent_link[0], node2_parent_link[1], node2_parent_link[2])
new_genome.add_edge(node1_parent_link[0], node2_parent_link[1], node1_parent_link[2], _type=node1_parent_link[3])
new_genome.add_edge(node2_parent_link[0], node1_parent_link[1], node2_parent_link[2], _type=node1_parent_link[3])
discard_useless_components(new_genome)

if not get_structure_tree(offspring):
if not get_structure_tree(new_genome):
raise ByronOperatorFailure

Node.reset_labels(offspring)
return [Individual(parent1.top_frame, offspring)]
Node.reset_labels(new_genome)
new_individual = Individual(parent1.top_frame, new_genome)
assert new_individual.run_paranoia_checks()

assert parent1.run_paranoia_checks()
assert parent2.run_paranoia_checks()
return [new_individual]
61 changes: 61 additions & 0 deletions examples/onemax/onemax-go/golang.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#################################|###|#####################################
# __ | | #
# | |--.--.--.----.-----.-----. |===| This file is part of Byron v0.8 #
# | _ | | | _| _ | | |___| An evolutionary optimizer & fuzzer #
# |_____|___ |__| |_____|__|__| ).( https://pypi.org/project/byron/ #
# |_____| \|/ #
################################## ' ######################################
# Copyright 2023 Giovanni Squillero and Alberto Tonda
# SPDX-License-Identifier: Apache-2.0

import byron


def framework():
byron.f.set_parameter('_comment', '//')
byron.f.set_option('$dump_node_info', False)

# The parameter '{_byron}' can be used to get information on the current system. Available fields are:
# 'version', 'system', 'machine', 'python', and 'networkx'
# To get all information use the macro 'byron.f.Info'

int64 = byron.f.integer_parameter(0, 2**64)
math_op = byron.f.choice_parameter(['+', '-', '*', '/', '&', '^', '|'])
variable = byron.f.macro('var {_node} uint64', _label='')
variable.force_parent('prologue')

# standard
main_prologue = byron.f.macro('package main\nfunc evolved_function() uint64 {{')
main_prologue.baptize('prologue')
imath = byron.f.macro(
'{var} {op}= {num}',
var=byron.f.global_reference(variable, creative_zeal=1),
op=math_op,
num=int64,
)
vmath = byron.f.macro(
'{var1} = {var2} {op} {var3}',
var1=(byron.f.global_reference(variable, creative_zeal=1, first_macro=True)),
op=math_op,
var2=(byron.f.global_reference(variable)),
var3=(byron.f.global_reference(variable)),
)

# proc
subs_prologue = byron.f.macro('func {_node}(foo uint64) uint64 {{', _label='')
subs_math = byron.f.macro('foo {op}= {num}', op=math_op, num=int64)
subs_epilogue = byron.f.macro('return foo\n}}')
subs = byron.f.sequence([subs_prologue, byron.f.bunch([subs_math], size=2), subs_epilogue])

call = byron.f.macro(
'{var1} = {sub}({var2})',
var1=byron.f.global_reference(variable),
sub=byron.f.global_reference(subs, creative_zeal=1, first_macro=True),
var2=byron.f.global_reference(variable),
)

code = byron.f.bunch([imath, vmath, call], size=5)
main_epilogue = byron.f.macro('return {var}\n}}', var=byron.f.global_reference(variable))
return byron.f.sequence((main_prologue, code, main_epilogue), max_instances=1)
6 changes: 3 additions & 3 deletions examples/onemax/onemax-go/onemax-go.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ def main():
top_frame = define_library()

evaluator = byron.evaluator.ScriptEvaluator('./evaluate-all.sh', filename_format="individual{i:06}.go")
evaluator = byron.evaluator.ParallelScriptEvaluator(
'go', 'onemax.go', other_required_files=('main.go',), flags=('run',), timeout=10, default_result='0'
)
# evaluator = byron.evaluator.ParallelScriptEvaluator(
# 'go', 'onemax.go', other_required_files=('main.go',), flags=('run',), timeout=10, default_result='0'
# )

byron.f.set_option('$dump_node_info', True)
final_population = byron.ea.vanilla_ea(
Expand Down
Loading

0 comments on commit 3460bab

Please sign in to comment.