Skip to content
41 changes: 14 additions & 27 deletions cgp/cartesian_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ def __init__(self, genome: "Genome") -> None:
"""
self._n_inputs: int
self._n_outputs: int
self._n_columns: int
self._n_rows: int
self._n_hidden_units: int
self._nodes: List
self._parameter_names_to_values: Dict[str, float]

Expand Down Expand Up @@ -82,29 +81,18 @@ def empty_node_str() -> str:

s = "\n"

for row in range(max(self._n_inputs, self._n_rows)):
for column in range(-1, self._n_columns + 1):

if column == -1:
if row < self._n_inputs:
s += pretty_node_str(self.input_nodes[row])
else:
s += empty_node_str()
s += "\t"

elif column < self._n_columns:
if row < self._n_rows:
s += pretty_node_str(self.hidden_nodes[row + column * self._n_rows])
else:
s += empty_node_str()
s += "\t"
else:
if row < self._n_outputs:
s += pretty_node_str(self.output_nodes[row])
else:
s += empty_node_str()
s += "\t"
for idx in range(self._n_inputs + self._n_hidden_units + self._n_outputs):

if idx < self._n_inputs:
s += pretty_node_str(self.input_nodes[idx])

elif idx < self._n_inputs + self._n_hidden_units:
s += pretty_node_str(self.hidden_nodes[idx - self._n_inputs])
else:
s += pretty_node_str(
self.output_nodes[idx - self._n_inputs - self._n_hidden_units]
)
s += "\t"
s += "\n"

return s
Expand All @@ -115,8 +103,7 @@ def parse_genome(self, genome: "Genome") -> None:

self._n_inputs = genome._n_inputs
self._n_outputs = genome._n_outputs
self._n_columns = genome._n_columns
self._n_rows = genome._n_rows
self._n_hidden_units = genome._n_hidden_units
self._parameter_names_to_values = copy.deepcopy(genome._parameter_names_to_values)

self._nodes = []
Expand All @@ -137,7 +124,7 @@ def parse_genome(self, genome: "Genome") -> None:
self._determine_active_nodes()

def _hidden_column_idx(self, idx: int) -> int:
return (idx - self._n_inputs) // self._n_rows
return idx - self._n_inputs

@property
def input_nodes(self) -> List[Node]:
Expand Down
74 changes: 21 additions & 53 deletions cgp/genome.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ def __init__(
self,
n_inputs: int = 1,
n_outputs: int = 1,
n_columns: int = 128,
n_rows: int = 1,
n_hidden_units: int = 128,
primitives: Optional[Tuple[Type[Node], ...]] = None,
levels_back: Optional[int] = None,
) -> None:
"""Init function.

Expand All @@ -41,41 +39,24 @@ def __init__(
Number of inputs of the function represented by the genome. Defaults to 1.
n_outputs : int, optional
Number of outputs of the function represented by the genome. Defaults to 1.
n_columns : int, optional
Number of columns in the representation of the genome. Defaults to 12.
n_rows : int, optional
Number of rows in the representation of the genome. Defaults to 1.
n_hidden_units : int, optional
Number of hidden units in the representation of the genome. Defaults to 128.
primitives : Tuple[Type[Node], ...], optional
Tuple of primitives that the genome can refer to. Defaults to (+, -, *, 1.0).
levels_back : Optional[int], optional
Maximal column distance of inputs to an internal node. If
set to `None`, no restrictions are used. Defaults to None.

"""
if n_inputs <= 0:
raise ValueError("n_inputs must be strictly positive")
self._n_inputs = n_inputs

if n_columns < 0:
if n_hidden_units < 0:
raise ValueError("n_columns must be non-negative")
self._n_columns = n_columns

if n_rows < 0:
raise ValueError("n_rows must be non-negative")
self._n_rows = n_rows
self._n_hidden_units = n_hidden_units

if n_outputs <= 0:
raise ValueError("n_outputs must be strictly positive")
self._n_outputs = n_outputs

if levels_back is None:
levels_back = n_columns
if levels_back == 0 and n_columns != 0:
raise ValueError("levels_back must be strictly positive")
if levels_back > n_columns:
raise ValueError("levels_back can not be larger than n_columns")
self._levels_back = levels_back

if primitives is None:
# we need to delay this import to avoid circular imports: node_impl
# -> node -> node_validation -> genome
Expand Down Expand Up @@ -128,7 +109,7 @@ def dna(self, value: List[int]) -> None:

@property
def _n_hidden(self) -> int:
return self._n_columns * self._n_rows
return self._n_hidden_units

@property
def _n_regions(self) -> int:
Expand Down Expand Up @@ -267,9 +248,7 @@ def randomize(self, rng: np.random.RandomState) -> None:
# add hidden nodes
for i in range(self._n_hidden):

if i % self._n_rows == 0: # only compute permissible addresses once per column
permissible_addresses = self._permissible_addresses(i + self._n_inputs)

permissible_addresses = self._permissible_addresses(i + self._n_inputs)
dna += self._create_random_hidden_region(rng, permissible_addresses)

# add output nodes
Expand Down Expand Up @@ -312,7 +291,7 @@ def splice_dna(self, new_dna: List[int], hidden_start_node: int = 0) -> int:
if hidden_start_node < 0 or hidden_start_node > self._n_hidden:
raise ValueError("hidden_start_node must be non-negative and smaller than n_hidden")

if hidden_start_node + n_inserted_nodes >= self._n_hidden:
if hidden_start_node + n_inserted_nodes > self._n_hidden:
raise ValueError("New dna too long")

dna = self.dna
Expand Down Expand Up @@ -409,10 +388,6 @@ def reorder(self, rng: np.random.RandomState) -> None:
----------
None
"""
if (self._n_rows != 1) or (self._levels_back != self._n_columns):
raise ValueError(
"Genome reordering is only implemented for n_rows=1" " and levels_back=n_columns"
)

dna = self._dna.copy()

Expand Down Expand Up @@ -501,9 +476,7 @@ def _update_address_genes(self, dna: List[int], used_node_indices: List[int]) ->
def _replace_invalid_address_alleles(self, dna: List[int], rng: np.random.RandomState) -> None:
"""Replace invalid alleles for unused address genes of all nodes
by random permissible values.
WARNING: Works only if self.n_rows==1.
"""
assert self._n_rows == 1

for gene_idx, gene_value in enumerate(dna):
region_idx = self._get_region_idx(gene_idx)
Expand Down Expand Up @@ -578,22 +551,22 @@ def _permissible_addresses(self, region_idx: int) -> List[int]:
# all nodes can be connected to input
permissible_addresses += [j for j in range(0, self._n_inputs)]

# add all nodes reachable according to levels back
# add all nodes reachable
if self._is_hidden_region(region_idx):
hidden_column_idx = self._hidden_column_idx(region_idx)
lower = self._n_inputs + self._n_rows * max(0, (hidden_column_idx - self._levels_back))
upper = self._n_inputs + self._n_rows * hidden_column_idx
hidden_idx = self._hidden_idx(region_idx)
lower = self._n_inputs
upper = self._n_inputs + hidden_idx
else:
assert self._is_output_region(region_idx)
lower = self._n_inputs
upper = self._n_inputs + self._n_rows * self._n_columns
upper = self._n_inputs + self._n_hidden_units

permissible_addresses += [j for j in range(lower, upper)]

return permissible_addresses

def _permissible_addresses_for_output_region(self) -> List[int]:
return self._permissible_addresses(self._n_inputs + self._n_rows * self._n_columns)
return self._permissible_addresses(self._n_inputs + self._n_hidden_units)

def _validate_dna(self, dna: List[int]) -> None:

Expand Down Expand Up @@ -635,13 +608,13 @@ def _validate_dna(self, dna: List[int]) -> None:
if output_region[2:] != [self._id_unused_gene] * (self._primitives.max_arity - 1):
raise ValueError("inactive address genes for output nodes need to be empty")

def _hidden_column_idx(self, region_idx: int) -> int:
def _hidden_idx(self, region_idx: int) -> int:
assert self._n_inputs <= region_idx
assert region_idx < self._n_inputs + self._n_rows * self._n_columns
hidden_column_idx = (region_idx - self._n_inputs) // self._n_rows
assert 0 <= hidden_column_idx
assert hidden_column_idx < self._n_columns
return hidden_column_idx
assert region_idx < self._n_inputs + self._n_hidden_units
hidden_idx = region_idx - self._n_inputs
assert 0 <= hidden_idx
assert hidden_idx < self._n_hidden_units
return hidden_idx

def iter_input_regions(
self, dna: Optional[List[int]] = None
Expand Down Expand Up @@ -788,12 +761,7 @@ def clone(self) -> "Genome":
"""

new = Genome(
self._n_inputs,
self._n_outputs,
self._n_columns,
self._n_rows,
tuple(self._primitives),
self._levels_back,
self._n_inputs, self._n_outputs, self._n_hidden_units, tuple(self._primitives)
)
new.dna = self.dna.copy()

Expand Down
2 changes: 1 addition & 1 deletion cgp/node_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def _create_genome(cls: Type["OperatorNode"]) -> "Genome":
from .genome import ID_INPUT_NODE, ID_NON_CODING_GENE, ID_OUTPUT_NODE, Genome

primitives = (cls,)
genome = Genome(1, 1, 1, 1, primitives)
genome = Genome(1, 1, 1, primitives)
dna = [ID_INPUT_NODE]
arity = max(cls._arity, 1)
for _ in range(arity):
Expand Down
1 change: 0 additions & 1 deletion examples/example_caching.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ def objective(individual):
return individual


# %%
# Finally, we call the `evolve` method to perform the evolutionary search.


Expand Down
2 changes: 0 additions & 2 deletions examples/example_differential_evo_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@ def objective(individual, seed):
seed = 1234
genome_params = {"primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.Parameter)}

# apply local search only to the top two individuals
ea_params = {"k_local_search": 2}

evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": -1e-8}

Expand Down
4 changes: 1 addition & 3 deletions examples/example_evo_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,7 @@ def evolution(f_target):
genome_params = {
"n_inputs": 2,
"n_outputs": 1,
"n_columns": 12,
"n_rows": 2,
"levels_back": 5,
"n_hidden_units": 12,
"primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.Div, cgp.ConstantFloat),
}

Expand Down
4 changes: 1 addition & 3 deletions examples/example_evo_regression_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,7 @@ def evolution(f_target):
genome_params = {
"n_inputs": 2,
"n_outputs": 1,
"n_columns": 12,
"n_rows": 2,
"levels_back": 5,
"n_hidden_units": 12,
"primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.Div, cgp.ConstantFloat),
}

Expand Down
1 change: 0 additions & 1 deletion examples/example_fec_caching.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ def objective(individual):
return individual


# %%
# Finally, we call the `evolve` method to perform the evolutionary search.


Expand Down
2 changes: 1 addition & 1 deletion examples/example_local_search_evolution_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def objective(individual, seed):
seed = 1234

genome_params = {
"n_columns": 36,
"n_hidden_units": 36,
"primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.Parameter),
}

Expand Down
2 changes: 1 addition & 1 deletion examples/example_parametrized_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def objective(individual, seed):

genome_params = {
"n_inputs": 2,
"n_columns": 5,
"n_hidden_units": 5,
"primitives": (ParametrizedAdd, cgp.Add, cgp.Sub, cgp.Mul),
}

Expand Down
4 changes: 1 addition & 3 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ def genome_params():
return {
"n_inputs": 2,
"n_outputs": 1,
"n_columns": 3,
"n_rows": 3,
"levels_back": 2,
"n_hidden_units": 3,
"primitives": (cgp.Add, cgp.Sub, cgp.ConstantFloat),
}

Expand Down
Loading