Skip to content

Remove n_rows and levels_back from genome #362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
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