def test_reset_parameters_upon_creation_of_node(rng): class CustomParameter(cgp.Parameter): _initial_values = {"<p>": lambda: np.pi} genome_params = { "n_inputs": 1, "n_outputs": 1, "n_columns": 1, "n_rows": 1, } primitives = (CustomParameter, CustomParameter) genome = cgp.Genome(**genome_params, primitives=primitives) # f(x) = p genome.dna = [ID_INPUT_NODE, ID_NON_CODING_GENE, 0, 0, ID_OUTPUT_NODE, 1] genome._parameter_names_to_values["<p1>"] = 1.0 f = cgp.CartesianGraph(genome).to_func() y = f(0.0) assert y == pytest.approx(1.0) # now mutate the genome, since there is only one other option for # the hidden node, a new CustomParameter node will be created; # creating this new node should reset the parameter to its initial # value; after mutation we recreate the correct graph connectivity # manually genome.mutate(1.0, rng) # f(x) = p genome.dna = [ID_INPUT_NODE, ID_NON_CODING_GENE, 0, 0, ID_OUTPUT_NODE, 1] f = cgp.CartesianGraph(genome).to_func() y = f(0.0) assert y == pytest.approx(np.pi)
def _objective(individual): if individual.fitness is not None: return individual def f0(x): return x[0] * (x[0] + x[0]) def f1(x): return (x[0] * x[1]) - x[1] y0 = cgp.CartesianGraph(individual.genome[0]).to_func() y1 = cgp.CartesianGraph(individual.genome[1]).to_func() loss = 0 for _ in range(100): x0 = np.random.uniform(size=1) x1 = np.random.uniform(size=2) loss += float((f0(x0) - y0(x0))**2) loss += float((f1(x1) - y1(x1))**2) individual.fitness = -loss return individual
def _objective(individual): if not individual.fitness_is_None(): return individual rng = np.random.RandomState(rng_seed) def f0(x): return x[0] * (x[0] + x[0]) def f1(x): return (x[0] * x[1]) - x[1] y0 = cgp.CartesianGraph(individual.genome[0]).to_func() y1 = cgp.CartesianGraph(individual.genome[1]).to_func() loss = 0 for _ in range(100): x0 = rng.uniform(size=1) x1 = rng.uniform(size=2) loss += float((f0(x0) - y0(x0))**2) loss += float((f1(x1) - y1(x1[0], x1[1]))**2) individual.fitness = -loss return individual
def test_to_func_simple(): primitives = (cgp.Add, ) genome = cgp.Genome(2, 1, 1, 1, primitives) genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 1, ID_OUTPUT_NODE, 2, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) f = graph.to_func() x = [5.0, 2.0] y = f(*x) assert x[0] + x[1] == pytest.approx(y) primitives = (cgp.Sub, ) genome = cgp.Genome(2, 1, 1, 1, primitives) genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 1, ID_OUTPUT_NODE, 2, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) f = graph.to_func() x = [5.0, 2.0] y = f(*x) assert x[0] - x[1] == pytest.approx(y)
def test_compile_torch_output_shape(genome, batch_size): torch = pytest.importorskip("torch") c = cgp.CartesianGraph(genome).to_torch() x = torch.Tensor(batch_size, 1).normal_() y = c(x) assert y.shape == (batch_size, genome._n_outputs)
def test_multiple_parameters_per_node(): p = 3.1415 q = 2.7128 class DoubleParameter(cgp.OperatorNode): _arity = 0 _initial_values = {"<p>": lambda: p, "<q>": lambda: q} _def_output = "<p> + <q>" _def_numpy_output = "np.ones(len(x[0])) * (<p> + <q>)" _def_torch_output = "torch.ones(1).expand(x.shape[0]) * (<p> + <q>)" genome_params = { "n_inputs": 1, "n_outputs": 1, "n_columns": 1, "n_rows": 1, } primitives = (DoubleParameter, ) genome = cgp.Genome(**genome_params, primitives=primitives) # f(x) = p + q genome.dna = [ID_INPUT_NODE, ID_NON_CODING_GENE, 0, 0, ID_OUTPUT_NODE, 1] f = cgp.CartesianGraph(genome).to_func() y = f(0.0) assert y == pytest.approx(p + q)
def test_allow_sympy_expr_with_infinities(): pytest.importorskip("sympy") primitives = (cgp.Sub, cgp.Div) genome = cgp.Genome(1, 1, 2, 1, primitives, 1) # x[0] / (x[0] - x[0]) genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 0, 1, 0, 1, ID_OUTPUT_NODE, 2, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) expr = graph.to_sympy(simplify=True) # complex infinity should appear in expression assert "zoo" in str(expr)
def test_pretty_str(): primitives = (cgp.Sub, cgp.Mul) genome = cgp.Genome(1, 1, 2, 1, primitives, 1) # x[0] ** 2 genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 0, 1, 0, 0, ID_OUTPUT_NODE, 2, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) pretty_str = graph.pretty_str() for node in graph.input_nodes: assert node.__class__.__name__ in pretty_str for node in graph.output_nodes: assert node.__class__.__name__ in pretty_str for node in graph.hidden_nodes: assert node.__class__.__name__ in pretty_str
def test_compile_two_columns(): primitives = (cgp.Add, cgp.Sub) genome = cgp.Genome(2, 1, 2, 1, primitives, 1) genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 1, 1, 0, 2, ID_OUTPUT_NODE, 3, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) f = graph.to_func() x = [5.0, 2.0] y = f(*x) assert x[0] - (x[0] + x[1]) == pytest.approx(y)
def test_parameter_w_random_initial_value(rng_seed): np.random.seed(rng_seed) min_val = 0.5 max_val = 1.5 class CustomParameter(cgp.Parameter): _initial_values = {"<p>": lambda: np.random.uniform(min_val, max_val)} genome_params = { "n_inputs": 1, "n_outputs": 1, "n_columns": 1, "n_rows": 1, } primitives = (CustomParameter, ) genome = cgp.Genome(**genome_params, primitives=primitives) # f(x) = c genome.dna = [ID_INPUT_NODE, ID_NON_CODING_GENE, 0, 0, ID_OUTPUT_NODE, 1] f = cgp.CartesianGraph(genome).to_func() y = f(0.0) assert min_val <= y assert y <= max_val assert y != pytest.approx(1.0)
def test_mul(): params = {"n_inputs": 2, "n_outputs": 1, "n_columns": 1, "n_rows": 1, "levels_back": 1} primitives = (cgp.Mul,) genome = cgp.Genome( params["n_inputs"], params["n_outputs"], params["n_columns"], params["n_rows"], params["levels_back"], primitives, ) genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 1, ID_OUTPUT_NODE, 2, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) x = [5.0, 1.5] y = graph(x) assert x[0] * x[1] == pytest.approx(y[0])
def test_constant_float(): params = {"n_inputs": 2, "n_outputs": 1, "n_columns": 1, "n_rows": 1, "levels_back": 1} primitives = (cgp.ConstantFloat,) genome = cgp.Genome( params["n_inputs"], params["n_outputs"], params["n_columns"], params["n_rows"], params["levels_back"], primitives, ) genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_INPUT_NODE, ID_NON_CODING_GENE, 0, 0, ID_OUTPUT_NODE, 2, ] graph = cgp.CartesianGraph(genome) x = [None, None] y = graph(x) # by default the output value of the ConstantFloat node is 1.0 assert 1.0 == pytest.approx(y[0])
def test_to_numpy(): primitives = (cgp.Add, cgp.Mul, cgp.ConstantFloat) genome = cgp.Genome(1, 1, 2, 2, primitives, 1) # f(x) = x ** 2 + 1. genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 2, 0, 0, 1, 0, 0, 0, 1, 2, 0, 0, 1, ID_OUTPUT_NODE, 3, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) f = graph.to_numpy() x = np.random.normal(size=100) y = f(x) y_target = x**2 + 1.0 assert y == pytest.approx(y_target)
def objective(individual, n_trials, simulate_func, exp_params, input_spikes, synapse_params): """Objective function of the evolution. Evaluates the fitness of a given individual in a number of trials and sets the fitness of the individual. Parameters ---------- individual : cgp.IndividualMultiGenome Individual to be evaluated. n_trials : int Number of trials that the invidiual is evaluated on. simulate_func : Callable Function executing the NEST simulation. exp_params : dict Dictionary holding the experiment parameters. input_spikes : list List of input spikes for each trial. Each item is a list of numpy arrays defining the spikes of each input neurons. synapse_params : dict Dictionary holding the synapse parameters. Returns ------- cgp.IndividualMultiGenome Individual with updated fitness. """ if individual.fitness is not None: return individual graph = [cgp.CartesianGraph(genome) for genome in individual.genome] try: sympy_expr = [str(g.to_sympy(simplify=True)[0]) for g in graph] except cgp.InvalidSympyExpression: individual.fitness = -np.inf return individual @cgp.utils.disk_cache("cache.pkl") def inner_objective(exp_params, synapse_params, input_spikes, sympy_expr): SNR = [] for i in range(n_trials): SNR.append( simulate_func( sympy_expr, exp_params=exp_params, input_spikes=input_spikes[i], synapse_params=synapse_params[i], )) fitness = np.min(SNR) return fitness # return the updated individual individual.fitness = inner_objective(exp_params, synapse_params, input_spikes, sympy_expr) return individual
def test_compile_numpy_output_shape(genome, batch_size): c = cgp.CartesianGraph(genome).to_numpy() x = np.random.normal(size=batch_size) y = c(x) if genome._n_outputs == 1: assert y.shape == (batch_size, ) else: assert len(y) == genome._n_outputs assert y[0].shape == (batch_size, )
def test_constant_float(): params = { "n_inputs": 2, "n_outputs": 1, "n_columns": 1, "n_rows": 1, "levels_back": 1 } val = 1.678 primitives = (cgp.node_factories.ConstantFloatFactory(val), ) genome = cgp.Genome( params["n_inputs"], params["n_outputs"], params["n_columns"], params["n_rows"], params["levels_back"], primitives, ) genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_INPUT_NODE, ID_NON_CODING_GENE, 0, 0, ID_OUTPUT_NODE, 2, ] graph = cgp.CartesianGraph(genome) x = [None, None] y = graph(x) assert val == pytest.approx(y[0]) # make sure different classes are created for multiple calls to the class # factory prim_0 = cgp.node_factories.ConstantFloatFactory(val)(0, [None]) prim_1 = cgp.node_factories.ConstantFloatFactory(val)(0, [None]) assert prim_0 is not prim_1 prim_1._output = 2 * val assert val == pytest.approx(prim_0._output) assert 2 * val == pytest.approx(prim_1._output)
def test_compile_addsubmul(): params = { "n_inputs": 2, "n_outputs": 1, "n_columns": 2, "n_rows": 2, "levels_back": 1 } primitives = (cgp.Add, cgp.Sub, cgp.Mul) genome = cgp.Genome( params["n_inputs"], params["n_outputs"], params["n_columns"], params["n_rows"], primitives, params["levels_back"], ) genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 2, 0, 1, 1, 0, 1, 1, 2, 3, 0, 0, 0, ID_OUTPUT_NODE, 4, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) f = graph.to_func() x = [5.0, 2.0] y = f(*x) assert (x[0] * x[1]) - (x[0] - x[1]) == pytest.approx(y)
def test_input_dim_python(rng): genome = cgp.Genome(2, 1, 1, 1, (cgp.ConstantFloat, )) genome.randomize(rng) f = cgp.CartesianGraph(genome).to_func() # fail for too short input with pytest.raises(ValueError): f(None) # fail for too long input with pytest.raises(ValueError): f(None, None, None) # do not fail for input with correct length f(None, None)
def test_mutate_hidden_region(rng_seed): rng = np.random.RandomState(rng_seed) genome = cgp.Genome(1, 1, 3, 1, None, (cgp.Add, cgp.ConstantFloat)) dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 1, 0, 0, 1, 0, 0, 0, 0, 2, ID_OUTPUT_NODE, 3, ID_NON_CODING_GENE, ] genome.dna = list(dna) active_regions = cgp.CartesianGraph(genome).determine_active_regions() # mutating any gene in inactive region returns True genome.dna = list(dna) assert genome._mutate_hidden_region(3, active_regions, rng) is True genome.dna = list(dna) assert genome._mutate_hidden_region(4, active_regions, rng) is True genome.dna = list(dna) assert genome._mutate_hidden_region(5, active_regions, rng) is True # mutating function gene in active region returns False genome.dna = list(dna) assert genome._mutate_hidden_region(6, active_regions, rng) is False # mutating inactive genes in active region returns True genome.dna = list(dna) assert genome._mutate_hidden_region(7, active_regions, rng) is True genome.dna = list(dna) assert genome._mutate_hidden_region(8, active_regions, rng) is True # mutating any gene in active region without silent genes returns False genome.dna = list(dna) assert genome._mutate_hidden_region(9, active_regions, rng) is False genome.dna = list(dna) assert genome._mutate_hidden_region(10, active_regions, rng) is False genome.dna = list(dna) assert genome._mutate_hidden_region(11, active_regions, rng) is False
def test_direct_input_output(): params = {"n_inputs": 1, "n_outputs": 1, "n_columns": 3, "n_rows": 3, "levels_back": 2} primitives = (cgp.Add, cgp.Sub) genome = cgp.Genome( params["n_inputs"], params["n_outputs"], params["n_columns"], params["n_rows"], params["levels_back"], primitives, ) genome.randomize(np.random) genome[-2:] = [0, ID_NON_CODING_GENE] # set inputs for output node to input node graph = cgp.CartesianGraph(genome) x = [2.14159] y = graph(x) assert x[0] == pytest.approx(y[0])
def test_input_dim_numpy(rng): genome = cgp.Genome(2, 1, 1, 1, (cgp.ConstantFloat, )) genome.randomize(rng) f = cgp.CartesianGraph(genome).to_numpy() # # fail for missing batch dimension # with pytest.raises(ValueError): # f(np.array([1.0])) # fail for too short input with pytest.raises(ValueError): f(np.array([1.0])) # fail for too long input with pytest.raises(ValueError): f(np.array([1.0]), np.array([1.0]), np.array([1.0])) # do not fail for input with correct shape f(np.array([1.0]), np.array([1.0]))
def test_input_dim_torch(rng): torch = pytest.importorskip("torch") genome = cgp.Genome(2, 1, 1, 1, (cgp.ConstantFloat, )) genome.randomize(rng) f = cgp.CartesianGraph(genome).to_torch() # fail for missing batch dimension with pytest.raises(ValueError): f(torch.Tensor([1.0])) # fail for too short input with pytest.raises(ValueError): f(torch.Tensor([1.0]).reshape(-1, 1)) # fail for too long input with pytest.raises(ValueError): f(torch.Tensor([1.0, 1.0, 1.0]).reshape(-1, 3)) # do not fail for input with correct shape f(torch.Tensor([1.0, 1.0]).reshape(-1, 2))
def test_input_dim_numpy(rng_seed): rng = np.random.RandomState(rng_seed) genome = cgp.Genome(2, 1, 1, 1, 1, (cgp.ConstantFloat,)) genome.randomize(rng) f = cgp.CartesianGraph(genome).to_numpy() # fail for missing batch dimension with pytest.raises(ValueError): f(np.array([1.0])) # fail for too short input with pytest.raises(ValueError): f(np.array([1.0]).reshape(-1, 1)) # fail for too long input with pytest.raises(ValueError): f(np.array([1.0, 1.0, 1.0]).reshape(-1, 3)) # do not fail for input with correct shape f(np.array([1.0, 1.0]).reshape(-1, 2))
def test_to_sympy(): sympy = pytest.importorskip("sympy") primitives = (cgp.Add, cgp.ConstantFloat) genome = cgp.Genome(1, 1, 2, 2, primitives, 1) # f = x_0 + x_0 + 1.0 genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 0, 1, ID_OUTPUT_NODE, 3, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) y_0_target = sympy.sympify("x_0 + x_0 + 1.0", evaluate=False) y_0 = graph.to_sympy(simplify=False) assert y_0_target == y_0 y_0_target = sympy.sympify("2 * x_0 + 1.0", evaluate=True) y_0 = graph.to_sympy() assert y_0_target == y_0 for x in np.random.normal(size=100): assert y_0_target.subs("x_0", x).evalf() == pytest.approx( y_0.subs("x_0", x).evalf())
def test_allow_powers_of_x_0(): pytest.importorskip("sympy") primitives = (cgp.Sub, cgp.Mul) genome = cgp.Genome(1, 1, 2, 1, primitives, 1) # x[0] ** 2 genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 0, 1, 0, 0, ID_OUTPUT_NODE, 2, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) graph.to_sympy(simplify=True)
def test_to_sympy(): sympy = pytest.importorskip("sympy") primitives = (cgp.Add, cgp.ConstantFloat) genome = cgp.Genome(1, 1, 2, 2, 1, primitives) genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, ID_OUTPUT_NODE, 3, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) x_0_target, y_0_target = sympy.symbols("x_0_target y_0_target") y_0_target = x_0_target + 1.0 y_0 = graph.to_sympy()[0] for x in np.random.normal(size=100): assert pytest.approx( y_0_target.subs("x_0_target", x).evalf() == y_0.subs("x_0", x).evalf() )
"pc0": pc0, #"pc0_empirical": pc0_empirical }) [history, champion] = evolution(data=data, population_params=params['population_params'], genome_params=params['genome_params'], ea_params=params['ea_params'], evolve_params=params['evolve_params'], learning_rate=learning_rate, alpha=alpha, fitness_mode=fitness_mode) # evaluate weights of champion (not passed down for non-re-evaluated champion) champion_learning_rule = cgp.CartesianGraph(champion.genome).to_numpy() champion_fitness, champion_weights_per_dataset = calculate_fitness( individual=champion, data=data, learning_rate=learning_rate, alpha=alpha, mode=fitness_mode, ) # evaluate hypothetical fitness of oja rule oja_fitness, oja_weights_per_dataset = calculate_fitness( individual=ind_oja_min, data=data, learning_rate=learning_rate, alpha=alpha, mode=fitness_mode)
def test_genome_reordering_empirically(rng): # empirically test that reordering does not change the output function of a genome pytest.importorskip("sympy") genome_params = { "n_inputs": 2, "n_outputs": 1, "n_columns": 14, "n_rows": 1, "primitives": (cgp.Mul, cgp.Sub, cgp.Add, cgp.ConstantFloat, cgp.Parameter), } genome = cgp.Genome(**genome_params) # f(x_0, x_1) = x_0 ** 2 - x_1 + 1 + 0.5 dna_fixed = [ ID_INPUT_NODE, # x_0 (address 0) ID_NON_CODING_GENE, ID_NON_CODING_GENE, ID_INPUT_NODE, # x_1 (address 1) ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, # Mul -> x_0^2 (address 2) 0, # x 0, # x 1, # Sub -> x_0^2 - x_1 (address 3) 2, # x^2 1, # y 1, # Sub -> 0 (address 4) 0, # x 0, # x 3, # const -> 1 (address 5) 2, 3, 3, # const -> 1 (address 6) 0, 0, 4, # param -> 0.9 (address 7) 4, 3, 4, # param -> 0.4 (address 8) 7, 2, 2, # Add -> x_0^2 - x_1 + 1 (address 9) 3, # x_0^2 - x_1 5, # 1 1, # Sub -> x_0^2 - x_1 + 1 - 0.9 (address 10) 9, # x_0^2 - x_1 + 1 7, # 0.9 2, # Add -> x_0^2 - x_1 + 1 - 0.9 + 0.4 (address 11) 10, # x_0^2 - x_1 + 1 - 0.9 8, # 0.4 3, # const (address 12) 0, 1, 3, # const (address 13) 0, 1, 3, # const (address 14) 0, 1, 3, # const (address 15) 0, 1, ID_OUTPUT_NODE, 11, ID_NON_CODING_GENE, ] genome.dna = dna_fixed genome._parameter_names_to_values["<p7>"] = 0.9 genome._parameter_names_to_values["<p8>"] = 0.4 sympy_expression = cgp.CartesianGraph(genome).to_sympy() n_reorderings = 100 for _ in range(n_reorderings): genome.reorder(rng) new_graph = cgp.CartesianGraph(genome) sympy_expression_after_reorder = new_graph.to_sympy() assert sympy_expression_after_reorder == sympy_expression
def test_repr(rng, genome_params): genome = cgp.Genome(**genome_params) genome.randomize(rng) # Assert that the CartesianGraph.__repr__ doesn't raise an error str(cgp.CartesianGraph(genome))
def test_pretty_str_with_unequal_inputs_rows_outputs(): primitives = (cgp.Add, ) # less rows than inputs/outputs genome = cgp.Genome(1, 1, 1, 2, primitives) # f(x) = x[0] + x[0] genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 0, 0, 0, 0, ID_OUTPUT_NODE, 1, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) expected_pretty_str = """ 00 * InputNode \t01 * Add (00,00) \t03 * OutputNode (01) \t \t02 Add \t \t """ assert graph.pretty_str() == expected_pretty_str # more rows than inputs/outputs genome = cgp.Genome(3, 3, 1, 2, primitives) # f(x) = [x[0] + x[1], x[0] + x[1], x[1] + x[2]] genome.dna = [ ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, ID_INPUT_NODE, ID_NON_CODING_GENE, ID_NON_CODING_GENE, 0, 0, 1, 0, 1, 2, ID_OUTPUT_NODE, 3, ID_NON_CODING_GENE, ID_OUTPUT_NODE, 3, ID_NON_CODING_GENE, ID_OUTPUT_NODE, 4, ID_NON_CODING_GENE, ] graph = cgp.CartesianGraph(genome) expected_pretty_str = """ 00 * InputNode \t03 * Add (00,01) \t05 * OutputNode (03) \t 01 * InputNode \t04 * Add (01,02) \t06 * OutputNode (03) \t 02 * InputNode \t \t07 * OutputNode (04) \t """ assert graph.pretty_str() == expected_pretty_str