import random
import numpy as np
import warnings
from dragon.search_operators.addons import VarNeighborhood
from dragon.search_space.base_variables import CatVar, Constant
from dragon.search_space.dag_encoding import Node
from dragon.utils.tools import logger
from dragon.search_space.dag_variables import EvoDagVariable, HpVar, NodeVariable
warnings.filterwarnings("ignore")
def int_neighborhood(b_min, b_max, scale=4):
return int(np.ceil(max(int((b_max - b_min) / scale), 2)))
[docs]
class HpInterval(VarNeighborhood):
"""HpInterval
`Addon`, used to determine the neighbor of an HpVar.
Mutate the operation if it is not a constant and the hyperparameters.
Parameters
----------
variable : HpVar, default=None
Targeted `Variable`
Examples
--------
>>> from dragon.search_space.bricks import MLP
>>> from dragon.search_space.base_variables import Constant, IntVar
>>> from dragon.search_space.dag_variables import HpVar
>>> from dragon.search_operators.base_neighborhoods import ConstantInterval, IntInterval
>>> from dragon.search_algorithmdag_neighborhoods import HpInterval
>>> mlp = Constant("MLP operation", MLP, neighbor=ConstantInterval())
>>> hp = {"out_channels": IntVar("out_channels", 1, 10, neighbor=IntInterval(2))}
>>> mlp_var = HpVar("MLP var", mlp, hyperparameters=hp, neighbor=HpInterval())
>>> print(mlp_var)
HpVar(MLP var,
>>> test_mlp = mlp_var.random()
>>> print(test_mlp)
[<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 2}]
>>> mlp_var.neighbor(test_mlp[0], test_mlp[1])
[<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 1}]
"""
def __init__(self, neighborhood=None, variable=None):
super(HpInterval, self).__init__(variable)
self._neighborhood = neighborhood
def __call__(self, operation, hp, size=1, *kwargs):
if size > 1:
res = []
for _ in range(size):
new_hp = hp.copy()
new_operation = operation
hp_index = list(set(np.random.choice(range(len(hp.keys())+1), size=len(hp.keys())+1)))
for idx in hp_index:
if idx >= len(hp.keys()):
if hasattr(self._target.operation, "neighbor"):
new_operation = self._target.operation.neighbor(operation)
else:
h = list(hp.keys())[idx]
new_hp[h] = self._target.hyperparameters[h].neighbor(hp[h])
res.append([new_operation, new_hp])
return res
else:
new_hp = hp.copy()
new_operation = operation
hp_index = list(set(np.random.choice(range(len(hp.keys())+1), size=len(hp.keys())+1)))
for idx in hp_index:
if idx >= len(hp.keys()):
if hasattr(self._target.operation, "neighbor"):
new_operation = self._target.operation.neighbor(operation)
else:
h = list(hp.keys())[idx]
new_hp[h] = self._target.hyperparameters[h].neighbor(hp[h])
return [new_operation, new_hp]
@VarNeighborhood.neighborhood.setter
def neighborhood(self, neighborhood=None):
self._neighborhood = neighborhood
@VarNeighborhood.target.setter
def target(self, variable):
assert isinstance(variable, HpVar) or variable is None, logger.error(
f"Target object must be a `HpVar` for {self.__class__.__operation__},\
got {variable}"
)
self._target = variable
[docs]
class CatHpInterval(VarNeighborhood):
"""CatHpInterval
`Addon`, used to determine the neighbor of a CatVar of candidates operations.
Given a probability `neighborhood`, draw a neighbor of the current operation, or draw a complete new operation
Parameters
----------
variable : CatVar, default=None
Targeted `Variable`
neighborhood: float < 1, default=0.9
Probability of drawing a neighbor instead of changing the whole operation.
Examples
--------
>>> from dragon.search_space.bricks import MLP, LayerNorm1d, BatchNorm1d
>>> from dragon.search_space.base_variables import Constant, IntVar, CatVar
>>> from dragon.search_space.dag_variables import HpVar
>>> from dragon.search_operators.base_neighborhoods import ConstantInterval, IntInterval, CatInterval
>>> from dragon.search_algorithmdag_neighborhoods import HpInterval, CatHpInterval
>>> mlp = Constant("MLP operation", MLP, neighbor=ConstantInterval())
>>> hp = {"out_channels": IntVar("out_channels", 1, 10, neighbor=IntInterval(2))}
>>> mlp_var = HpVar("MLP var", mlp, hyperparameters=hp, neighbor=HpInterval())
>>> norm = CatVar("1d norm layers", features=[LayerNorm1d, BatchNorm1d], neighbor=CatInterval())
>>> norm_var = HpVar("Norm var", norm, hyperparameters={}, neighbor=HpInterval())
>>> candidates=CatVar("Candidates", features=[mlp_var, norm_var],neighbor=CatHpInterval(neighborhood=0.4))
>>> print(candidates)
CatVar(Candidates, [HpVar(MLP var, , HpVar(Norm var, ])
>>> test_candidates = candidates.random()
>>> print(test_candidates)
[<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 2}]
>>> candidates.neighbor(test_candidates[0], test_candidates[1], size=10)
[[<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 2}], [<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 1}], [<class 'dragon.search_space.bricks.normalization.BatchNorm1d'>, {}], [<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 1}], [<class 'dragon.search_space.bricks.normalization.LayerNorm1d'>, {}], [<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 3}], [<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 1}], [<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 2}], [<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 4}], [<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 3}]]
"""
def __init__(self, neighborhood=None, variable=None):
super(CatHpInterval, self).__init__(variable)
if neighborhood is None:
neighborhood = 0.9
self._neighborhood = neighborhood
def __call__(self, operation, hp, size=1, *kwargs):
if size > 1:
res = []
for _ in range(size):
p = np.random.uniform()
if p>self._neighborhood:
# Draw completely new layer with a probability of 1-p
new_layer = self._target.random()
else:
# Draw a neighbor of the layer
for f in self._target.features:
assert isinstance(f, HpVar), f"Target features should be of type HpVar but got {f} instead."
assert isinstance(f.operation, CatVar) or isinstance(f.operation, Constant), f"Target features should have operation argument of isntance Constant or CatVar but got {f.operation} instead."
if isinstance(f.operation, CatVar):
bool = operation in f.operation.features
elif isinstance(f.operation, Constant):
bool = operation == f.operation.value
if bool:
new_layer = f.neighbor(operation, hp)
break
res.append(new_layer)
return res
else:
p = np.random.uniform()
if p>self._neighborhood:
# Draw completely new layer with a probability of 1-p
new_layer = self._target.neighbor(operation, hp)
else:
# Neighbor of layer
for f in self._target.features:
assert isinstance(f, HpVar), f"Target features should be of type HpVar but got {f} instead."
assert isinstance(f.operation, Constant) or isinstance(f.operation, CatVar), f"Target features should have operation argument of isntance Constant or CatVar but got {f.operation} instead."
if isinstance(f.operation, CatVar):
bool = operation in f.operation.features
elif isinstance(f.operation, Constant):
bool = operation == f.operation.value
if bool:
new_layer = f.neighbor(operation, hp)
break
return new_layer
@VarNeighborhood.neighborhood.setter
def neighborhood(self, neighborhood=None):
assert isinstance(neighborhood, list) or neighborhood is None, logger.error(
f"Layers neighborhood must be a list of weights, got {neighborhood}"
)
self._neighborhood = neighborhood
@VarNeighborhood.target.setter
def target(self, variable):
assert isinstance(variable, CatVar) or variable is None, logger.error(
f"Target object must be a `CatInterval` for {self.__class__.__operation__},\
got {variable}"
)
self._target = variable
[docs]
class NodeInterval(VarNeighborhood):
"""NodeInterval
`Addon`, used to determine the neighbor of a Node.
Change the combiner and/or the operation and/or the hyperparameters and/or the activation function.
Parameters
----------
variable : CatVar, default=None
Targeted `Variable`
Examples
--------
>>> from dragon.search_space.dag_variables import NodeVariable, HpVar
>>> from dragon.search_space.bricks import MLP
>>> from dragon.search_space.base_variables import Constant, IntVar, CatVar
>>> from dragon.search_space.bricks_variables import activation_var
>>> from dragon.search_operators.base_neighborhoods import ConstantInterval, IntInterval, CatInterval
>>> from dragon.search_algorithmdag_neighborhoods import NodeInterval, HpInterval
>>> combiner = CatVar("Combiner", features = ['add', 'mul'], neighbor=CatInterval())
>>> operation = HpVar("Operation", Constant("MLP operation", MLP, neighbor=ConstantInterval()),
... hyperparameters={"out_channels": IntVar("out_channels", 1, 10, neighbor=IntInterval(1))}, neighbor=HpInterval())
>>> node = NodeVariable(label="Node variable",
... combiner=combiner,
... operation=operation,
... activation_function=activation_var("Activation"), neighbor=NodeInterval())
>>> print(node)
Combiner: CatVar(Combiner, ['add', 'mul']) - Operation: HpVar(Operation, - Act. Function: CatVar(Activation, [ReLU(), LeakyReLU(negative_slope=0.01), Identity(), Sigmoid(), Tanh(), ELU(alpha=1.0), GELU(approximate='none'), SiLU()])
>>> test_node = node.random()
>>> print(test_node)
(combiner) mul -- (name) <class 'dragon.search_space.bricks.basics.MLP'> -- (hp) {'out_channels': 6} -- (activation) LeakyReLU(negative_slope=0.01) --
>>> neighbor = node.neighbor(test_node)
>>> print('Neighbor: ', neighbor)
Neighbor:
(combiner) add -- (name) <class 'dragon.search_space.bricks.basics.MLP'> -- (hp) {'out_channels': 6} -- (activation) LeakyReLU(negative_slope=0.01) --
>>> neighbor.set((3,))
>>> print('Neighbor after setting: ', neighbor)
Neighbor after setting:
(input shape) (3,) -- (combiner) add -- (op) MLP(
(linear): Linear(in_features=3, out_features=6, bias=True)
) -- (activation) LeakyReLU(negative_slope=0.01) -- (output shape) (6,)
>>> node.neighbor(neighbor)
(input shape) (3,) -- (combiner) mul -- (op) MLP(
(linear): Linear(in_features=3, out_features=5, bias=True)
) -- (activation) LeakyReLU(negative_slope=0.01) -- (output shape) (5,)
"""
def __init__(self, neighborhood=None, variable=None):
super(NodeInterval, self).__init__(variable)
self._neighborhood = neighborhood
def __call__(self, node, size=1, *kwargs):
assert isinstance(node, Node), f"node should be of type Node but got {node} instead."
if size > 1:
res = []
for _ in range(size):
new_node = node.copy()
changed = {}
idx_list = list(set(np.random.choice(range(3), size=3)))
if 0 in idx_list:
changed["combiner"] = self._target.combiner.neighbor(node.combiner)
if 1 in idx_list:
op = self._target.operation.neighbor(node.name, node.hp, node.operation)
changed["operation"], changed["hp"] = op[0], op[1]
new_node.modification(**changed)
if 2 in idx_list:
new_node.activation = self._target.activation_function.neighbor(node.activation)
res.append(new_node)
return res
else:
node.copy()
changed = {}
idx_list = list(set(np.random.choice(range(3), size=3)))
if 0 in idx_list:
changed["combiner"] = self._target.combiner.neighbor(node.combiner)
if 1 in idx_list:
op = self._target.operation.neighbor(node.name, node.hp)
changed["operation"], changed["hp"] = op[0], op[1]
if hasattr(node, "input_shapes"):
node.modification(**changed)
else:
if "combiner" in changed:
node.combiner = changed['combiner']
if "operation" in changed:
node.name = changed["operation"]
if "hp" in changed:
node.hp = changed['hp']
if 2 in idx_list:
node.activation = self._target.activation_function.neighbor(node.activation)
return node
@VarNeighborhood.neighborhood.setter
def neighborhood(self, neighborhood=None):
assert isinstance(neighborhood, list) or neighborhood is None, logger.error(
f"Nodes neighborhood must be a list of weights, got {neighborhood}"
)
self._neighborhood = neighborhood
@VarNeighborhood.target.setter
def target(self, variable):
assert isinstance(variable, NodeVariable) or variable is None, logger.error(
f"Target object must be a `NodeVariable` for {self.__class__.__operation__},\
got {variable}"
)
self._target = variable
[docs]
class EvoDagInterval(VarNeighborhood):
"""NodeInterval
`Addon`, used to determine the neighbor of an EvoDagVariable.
May perform several modifications such as adding / deleting nodes, changing the nodes content, adding/removing connections.
Parameters
----------
variable : EvoDagVariable, default=None
Targeted `Variable`.
Examples
--------
>>> from dragon.search_space.dag_variables import HpVar, NodeVariable, EvoDagVariable
>>> from dragon.search_space.bricks import MLP, MaxPooling1D, AVGPooling1D
>>> from dragon.search_space.base_variables import Constant, IntVar, CatVar, DynamicBlock
>>> from dragon.search_space.bricks_variables import activation_var
>>> from dragon.search_algorithmdag_neighborhoods import CatHpInterval, EvoDagInterval, NodeInterval, HpInterval
>>> from dragon.search_operators.base_neighborhoods import ConstantInterval, IntInterval, CatInterval, DynamicBlockInterval
>>> mlp = HpVar("Operation", Constant("MLP operation", MLP, neighbor=ConstantInterval()), hyperparameters={"out_channels": IntVar("out_channels", 1, 10, neighbor=IntInterval(5))}, neighbor=HpInterval())
>>> pooling = HpVar("Operation", CatVar("Pooling operation", [MaxPooling1D, AVGPooling1D], neighbor=CatInterval()), hyperparameters={"pool_size": IntVar("pool_size", 1, 5, neighbor=IntInterval(2))}, neighbor=HpInterval())
>>> candidates = NodeVariable(label = "Candidates",
... combiner=CatVar("Combiner", features=['add', 'concat'], neighbor=CatInterval()),
... operation=CatVar("Candidates", [mlp, pooling], neighbor=CatHpInterval(0.4)),
... activation_function=activation_var("Activation"), neighbor=NodeInterval())
>>> operations = DynamicBlock("Operations", candidates, repeat=5, neighbor=DynamicBlockInterval(2))
>>> dag = EvoDagVariable(label="DAG", operations=operations, neighbor=EvoDagInterval())
>>> print(dag)
EvoDagVariable(DAG, - Operations:
DynamicBlock(Operations, Combiner: CatVar(Combiner, ['add', 'concat']) - Operation: CatVar(Candidates, [HpVar(Operation, , HpVar(Operation, ]) - Act. Function: CatVar(Activation, [ReLU(), LeakyReLU(negative_slope=0.01), Identity(), Sigmoid(), Tanh(), ELU(alpha=1.0), GELU(approximate='none'), SiLU()])
>>> test_dag = dag.random()
>>> print(test_dag)
NODES: [
(combiner) add -- (name) <class 'dragon.search_space.bricks.basics.Identity'> -- (hp) {} -- (activation) Identity() -- ,
(combiner) add -- (name) <class 'dragon.search_space.bricks.pooling.MaxPooling1D'> -- (hp) {'pool_size': 3} -- (activation) Sigmoid() -- ,
(combiner) concat -- (name) <class 'dragon.search_space.bricks.pooling.MaxPooling1D'> -- (hp) {'pool_size': 4} -- (activation) Identity() -- ] | MATRIX:[[0, 1, 1], [0, 0, 1], [0, 0, 0]]
>>> neighbor = dag.neighbor(test_dag, 3)
>>> print(neighbor)
[NODES: [
(combiner) add -- (name) <class 'dragon.search_space.bricks.basics.Identity'> -- (hp) {} -- (activation) Identity() -- ,
(combiner) add -- (name) <class 'dragon.search_space.bricks.basics.MLP'> -- (hp) {'out_channels': 6} -- (activation) ReLU() -- ,
(combiner) concat -- (name) <class 'dragon.search_space.bricks.pooling.AVGPooling1D'> -- (hp) {'pool_size': 3} -- (activation) Sigmoid() -- ] | MATRIX:[[0, 1, 1], [0, 0, 1], [0, 0, 0]], NODES: [
(combiner) add -- (name) <class 'dragon.search_space.bricks.basics.Identity'> -- (hp) {} -- (activation) Identity() -- ,
(combiner) concat -- (name) <class 'dragon.search_space.bricks.pooling.AVGPooling1D'> -- (hp) {'pool_size': 3} -- (activation) Sigmoid() -- ,
(combiner) concat -- (name) <class 'dragon.search_space.bricks.pooling.MaxPooling1D'> -- (hp) {'pool_size': 4} -- (activation) Identity() -- ] | MATRIX:[[0, 1, 1], [0, 0, 1], [0, 0, 0]], NODES: [
(combiner) add -- (name) <class 'dragon.search_space.bricks.basics.Identity'> -- (hp) {} -- (activation) Identity() -- ,
(combiner) concat -- (name) <class 'dragon.search_space.bricks.pooling.AVGPooling1D'> -- (hp) {'pool_size': 3} -- (activation) Sigmoid() -- ,
(combiner) concat -- (name) <class 'dragon.search_space.bricks.pooling.MaxPooling1D'> -- (hp) {'pool_size': 4} -- (activation) Identity() -- ] | MATRIX:[[0, 1, 0], [0, 0, 1], [0, 0, 0]]]
>>> neighbor[0].set((3,))
>>> print('First neighbor after setting: ', neighbor)
First neighbor after setting: [ModuleList(
(0):
(input shape) (3,) -- (combiner) add -- (op) Identity() -- (activation) Identity() -- (output shape) (3,)
(1):
(input shape) (3,) -- (combiner) add -- (op) MLP(
(linear): Linear(in_features=3, out_features=6, bias=True)
) -- (activation) ReLU() -- (output shape) (6,)
(2):
(input shape) (9,) -- (combiner) concat -- (op) AVGPooling1D(
(pooling): AvgPool1d(kernel_size=(3,), stride=(3,), padding=(0,))
) -- (activation) Sigmoid() -- (output shape) (3,)
), NODES: [
(input shape) (3,) -- (combiner) add -- (op) Identity() -- (activation) Identity() -- (output shape) (3,),
(input shape) (9,) -- (combiner) concat -- (op) AVGPooling1D(
(pooling): AvgPool1d(kernel_size=(3,), stride=(3,), padding=(0,))
) -- (activation) Sigmoid() -- (output shape) (3,),
(combiner) concat -- (name) <class 'dragon.search_space.bricks.pooling.MaxPooling1D'> -- (hp) {'pool_size': 4} -- (activation) Identity() -- ] | MATRIX:[[0, 1, 1], [0, 0, 1], [0, 0, 0]], NODES: [
(input shape) (3,) -- (combiner) add -- (op) Identity() -- (activation) Identity() -- (output shape) (3,),
(input shape) (9,) -- (combiner) concat -- (op) AVGPooling1D(
(pooling): AvgPool1d(kernel_size=(3,), stride=(3,), padding=(0,))
) -- (activation) Sigmoid() -- (output shape) (3,),
(combiner) concat -- (name) <class 'dragon.search_space.bricks.pooling.MaxPooling1D'> -- (hp) {'pool_size': 4} -- (activation) Identity() -- ] | MATRIX:[[0, 1, 0], [0, 0, 1], [0, 0, 0]]]
>>> dag.neighbor(neighbor[0])
NODES: [
(input shape) (3,) -- (combiner) add -- (op) Identity() -- (activation) Identity() -- (output shape) (3,),
(input shape) (3,) -- (combiner) concat -- (op) MLP(
(linear): Linear(in_features=3, out_features=9, bias=True)
) -- (activation) ReLU() -- (output shape) (9,),
(input shape) (12,) -- (combiner) concat -- (op) AVGPooling1D(
(pooling): AvgPool1d(kernel_size=(3,), stride=(3,), padding=(0,))
) -- (activation) Sigmoid() -- (output shape) (4,)] | MATRIX:[[0, 1, 1], [0, 0, 1], [0, 0, 0]]
"""
def __init__(self, neighborhood=None, variable=None, nb_mutations=None):
super(EvoDagInterval, self).__init__(variable)
self._neighborhood = neighborhood
self.nb_mutations = nb_mutations
def __call__(self, value, size=1):
if size == 1:
valid = False
while not valid:
inter = value.copy()
# choose the nodes that will be modified
if self.nb_mutations is None:
nb_mutations = size=len(inter.operations)
else:
nb_mutations = self.nb_mutations
variable_idx = list(set(np.random.choice(range(len(inter.operations)), nb_mutations)))
modifications = []
for i in range(len(variable_idx)):
idx = variable_idx[i]
if idx == 0:
choices = ['add', 'children']
if inter.matrix.shape[0] == self.target.max_size:
choices = ["children"]
elif idx == len(inter.operations) - 1:
if inter.matrix.shape[0] == self.target.max_size:
choices = ['delete', 'modify', 'parents']
elif inter.matrix.shape[0] == 2:
choices = ['add', 'modify', 'parents']
else:
choices = ['add', 'delete', 'modify', 'parents']
else:
if inter.matrix.shape[0] == self.target.max_size:
choices = ['delete', 'modify', 'children', 'parents']
else:
choices = ['add', 'delete', 'modify', 'children', 'parents']
# choose the modification we are going to perform
modification = random.choice(choices)
if idx >= 0:
inter = self.modification(modification, idx, inter)
modifications.append(modification)
if modification == "add":
variable_idx[i+1:] = [j + 1 for j in variable_idx[i+1:]]
elif modification == "delete":
variable_idx[i+1:] = [j - 1 for j in variable_idx[i+1:]]
else:
logger.error(f'Idx: {idx}, modification: {modification}, modifications: {modifications}, variable idx: {variable_idx}')
pass
try:
inter.assert_adj_matrix()
valid = True
except AssertionError as e:
logger.error(f"Modifications = {modification}, idx={idx}, value=\n{value}\n{e}", exc_info=True)
return inter
else:
res = []
for _ in range(size):
inter = value.copy()
variable_idx = list(set(np.random.choice(range(len(inter.operations)), size=len(inter.operations))))
for i in range(len(variable_idx)):
idx = variable_idx[i]
if idx == 0:
modification = random.choice(['add', 'children'])
elif idx == len(inter.operations) - 1:
modification = random.choice(['add', 'delete', 'modify', 'parents'])
else:
modification = random.choice(['add', 'delete', 'modify', 'children', 'parents'])
inter = self.modification(modification, idx, inter)
if modification == "add":
variable_idx[i + 1:] = [j + 1 for j in variable_idx[i + 1:]]
elif modification == "delete":
variable_idx[i + 1:] = [j - 1 for j in variable_idx[i + 1:]]
inter.assert_adj_matrix()
res.append(inter)
return res
@VarNeighborhood.neighborhood.setter
def neighborhood(self, neighborhood=None):
if isinstance(neighborhood, list):
self._neighborhood = neighborhood[0]
self.target.operations.value.neighborhood = neighborhood
else:
self._neighborhood = neighborhood
@VarNeighborhood.target.setter
def target(self, variable):
assert isinstance(variable, EvoDagVariable) or variable is None, logger.error(
f"Target object must be a `EvoDagVariable` for {self.__class__.__operation__},\
got {variable}"
)
self._target = variable
if variable is not None:
assert hasattr(self.target.operations.value, "neighbor"), logger.error(
f"To use `EvoDagVariable`, value for operations for `EvoDagVariable` must have a `neighbor` method. "
f"Use `neighbor` kwarg when defining a variable "
)
[docs]
def modification(self, modif, idx, inter):
assert modif in ['add', 'delete', 'modify', 'children', 'parents'], f"""Modification should be in ['add',
'delete', 'modify', 'children', 'parent'], got{modif} instead"""
if modif == "add": # Add new node after the one selected
idxx = idx + 1
new_node = self.target.operations.value.random(1)
inter.operations.insert(idxx, new_node)
N = len(inter.operations)
parents = np.random.choice(2, idxx)
while sum(parents) == 0:
parents = np.random.choice(2, idxx)
children = np.random.choice(2, N - idxx - 1)
if N - idxx - 1 > 0:
while sum(children) == 0:
children = np.random.choice(2, N - idxx - 1)
inter.matrix = np.insert(inter.matrix, idxx, 0, axis=0)
inter.matrix = np.insert(inter.matrix, idxx, 0, axis=1)
inter.matrix[idxx, idxx+1:] = children
inter.matrix[:idxx, idxx] = parents
inter.matrix[-2, -1] = 1 # In case we add a node at the end
if hasattr(inter.operations[idxx], "set_operation"):
if hasattr(inter.operations[idx], "input_shapes"):
input_shapes = [inter.operations[i].output_shape for i in range(idxx) if parents[i] == 1]
inter.operations[idxx].set_operation(input_shapes)
elif modif == "delete": # Delete selected node
inter.matrix = np.delete(inter.matrix, idx, axis=0)
inter.matrix = np.delete(inter.matrix, idx, axis=1)
for i in range(inter.matrix.shape[0] - 1):
new_row = inter.matrix[i, i + 1:]
while sum(new_row) == 0:
new_row = np.random.choice(2, inter.matrix.shape[0] - i - 1)
inter.matrix[i, i + 1:] = new_row
for j in range(1, inter.matrix.shape[1]):
new_col = inter.matrix[:j, j]
while sum(new_col) == 0:
new_col = np.random.choice(2, j)
inter.matrix[:j, j] = new_col
inter.operations.pop(idx)
elif modif == "modify": # Modify node operation
inter.operations[idx] = self.target.operations.value.neighbor(inter.operations[idx])
elif modif == "children": # Modify node children
new_row = np.zeros(inter.matrix.shape[0] - idx - 1)
while sum(new_row) == 0:
new_row = np.random.choice(2, new_row.shape[0])
inter.matrix[idx, idx + 1:] = new_row
for j in range(1, inter.matrix.shape[1]):
new_col = inter.matrix[:j, j]
while sum(new_col) == 0:
new_col = np.random.choice(2, j)
inter.matrix[:j, j] = new_col
elif modif == "parents": # Modify node parents
new_col = np.zeros(idx)
while sum(new_col) == 0:
new_col = np.random.choice(2, new_col.shape[0])
inter.matrix[:idx, idx] = new_col
for i in range(inter.matrix.shape[0] - 1):
new_row = inter.matrix[i, i + 1:]
while sum(new_row) == 0:
new_row = np.random.choice(2, inter.matrix.shape[0] - i - 1)
inter.matrix[i, i + 1:] = new_row
inter.matrix[-2, -1] = 1
# Reconstruct nodes:
for j in range(1, len(inter.operations)):
if hasattr(inter.operations[j], "modification"):
if hasattr(inter.operations[j], "input_shapes"):
input_shapes = [inter.operations[i].output_shape for i in range(j) if inter.matrix[i, j] == 1]
inter.operations[j].modification(input_shapes=input_shapes)
return inter