class Blueprint(object):

    def __init__(self, layout, training, score=None):
        self.layout = layout
        self.training = training
        self.score = score

    def scale_layers_size(self, scale):
        for layer in self.layout.get_layers():
            output_dim = layer.parameters.get('output_dim', None)
            if not output_dim:
                continue
            new_size = output_dim * scale
            logging.debug(
                'Scaling output dim: %d=>%d',
                output_dim,
                new_size)
            layer.parameters['output_dim'] = new_size

    def todict(self):
        return {
            'layout': self.layout.todict(),
            'training': self.training.todict(),
            'score': self.score} class Training(object):

    def __init__(self, objective, optimizer, metric,
                 stopping, batch_size, class_weight=None):
        self.objective = objective
        self.optimizer = optimizer
        self.metric = metric
        self.stopping = stopping
        self.batch_size = batch_size
        self.class_weight = class_weight

    def todict(self):
        return {
            'objective': self.objective.todict(),
            'optimizer': self.optimizer.todict(),
            'metric': self.metric.todict(),
            'stopping': self.stopping.todict(),
            'batch_size': self.batch_size,
            'class_weight': self.class_weight} class Layout(object):

    def __init__(self, input_size, output_size,
                 output_activation, block=None, block_input=None, rows=None):
        self.input_size = int(input_size)
        self.output_size = int(output_size)
        self.output_activation = output_activation
        self.block = block
        self.block_input = block_input
        self.rows = rows or list()

    def get_rows(self):
        return self.rows

    def get_blocks(self):
        return [
            block
            for row in self.rows
            for block in row.get_blocks()]

    def get_layers(self):
        return [
            layer
            for row in self.rows
            for layer in row.get_layers()]

    def todict(self):
        dict_repr = dict(vars(self))
        del dict_repr['block']
        dict_repr['rows'] = [row.todict() for row in self.rows]
        return dict_repr class Row(object):

    def __init__(self, blocks=None):
        self.blocks = blocks or list()

    def get_blocks(self):
        return self.blocks

    def get_layers(self):
        return [
            layer
            for block in self.blocks
            for layer in block.get_layers()]

    def todict(self):
        return {
            'blocks': [
                block.todict()
                for block in self.blocks]} class Layer(object):

    def __init__(self, layer_type,
                 parameters=None, parameter_constraints=None):
        self.layer_type = layer_type
        self.parameters = parameters or dict()
        self.parameter_constraints = parameter_constraints or dict()

    def apply_constraints(self):
        self.parameters.update(self.parameter_constraints)

    def todict(self):
        return dict(vars(self)) class Block(object):

    def __init__(self, layers=None, input_layers=None):
        self.layers = layers or list()
        self.input_layers = input_layers or list()

    def get_layers(self):
        return self.layers

    def todict(self):
        return {
            'input_layers': [
                layer.todict()
                for layer in self.input_layers],
            'layers': [
                layer.todict()
                for layer in self.layers]} class Optimizer(object):

    def __init__(self, optimizer=None, parameters=None):
        self.optimizer = optimizer
        self.parameters = parameters or dict()

    def todict(self):
        return dict(vars(self)) def random_initial_param_value(param, mutation_std=0.1):
    if isinstance(param, dict):
        return _random_dict_param_value(param)
    if isinstance(param, list) or isinstance(param, tuple):
        return _random_list_param_value(param)
    if not isinstance(param, Parameter):
        return param
    if param.values:
        return random_list_element(param.values)

    initial_value = None
    if param.default is not None:
        initial_value = param.default
    elif param.lo is not None:
        initial_value = param.lo
    else:
        initial_value = 0
    return mutate_param(param, initial_value, mutation_std) def random_list_element(elements):
    if len(elements) == 0:
        return None
    if len(elements) == 1:
        return elements[0]
    return elements[rand.randint(0, len(elements) - 1)] def mutate_param(param, value, mutation_std=0.1):
    if isinstance(param, dict):
        return {
            name: mutate_param(nested, value.get(name, None))
            for name, nested in param.items()}
    if not isinstance(param, Parameter):
        return value
    if param.optional and value is not None and rand.random() < 0.5:
        return None
    if param.values:
        if len(set(param.values)) == 1:
            return param.values[0]
        element = random_list_element(param.values)
        while element == value:
            element = random_list_element(param.values)
        return element
    elif param.lo == param.hi:
        return value
    value = value or 0
    std = mutation_std * (param.hi - param.lo)
    if param.param_type == int:
        std = max(1, std)
    new_value = value + param.param_type(numpy.random.normal(0, std, 1)[0])
    while not is_valid_param_value(param, new_value) or new_value == value:
        new_value = value + param.param_type(numpy.random.normal(0, std, 1)[0])
    return new_value
from copy import deepcopy
from random import Random, shuffle
from minos.experiment.experiment import Blueprint
from minos.experiment.training import Training
from minos.model.model import Layout, Row, Layer, Block,\
    Optimizer
from minos.model.parameter import random_initial_param_value,\
    random_list_element, mutate_param
    if len(block.input_layers) > 0:
        return
    template = layout.block_input
    if not template:
        template = [('Merge', dict(mode='concat'))]
    block.input_layers = _create_template_layers(template, experiment_parameters)
    for layer in block.input_layers:
        layer.apply_constraints()


def _get_layer_type(layer):
    layer_type = layer.layer_type\
        if isinstance(layer, Layer) else layer[0]
    return layer_type


def _is_first_layer_merge(layers):
    if len(layers) == 0:
        return False
    return _is_merge_layer(layers[0])


def _is_merge_layer(layer):
    layer_type = _get_layer_type(layer)
    return layer_type == 'Merge'


def _is_first_layers_match_template(layers, template):
    for idx in range(len(template)):
        if idx >= len(layers):
            return False
        layer_type = layers[idx].layer_type\
            if isinstance(layers[idx], Layer) else layers[idx][0]
        if template[idx][0] != layer_type:
            return False
    return True


def _create_template_layers(template, experiment_parameters, init_layer_parameters=False):
    layers = list()
    for layer_definition in template:
        layer_type = layer_definition[0]\
            if isinstance(layer_definition, tuple) else layer_definition
        parameter_constraints = deepcopy(layer_definition[1])\
            if isinstance(layer_definition, tuple) else dict()
        layer = Layer(
            layer_type,
            parameter_constraints=parameter_constraints)
        if init_layer_parameters:
            _set_layer_random_parameters(layer, experiment_parameters)
        layers.append(layer)
    return layers


def is_allowed_block_layer(layers, new_layer, parameters):
    previous_layer_type = None
    if len(layers) > 0:
        if isinstance(layers[-1], Layer):
            previous_layer_type = layers[-1].layer_type
        elif isinstance(layers[-1], tuple):
            previous_layer_type = layers[-1][0]
        else:
            previous_layer_type = layers[-1]
    if new_layer == 'BatchNormalization':
        return previous_layer_type == 'Dense'
    stackable_layers = parameters.get_layout_parameter('layer.stackable')
    is_stackable = stackable_layers and new_layer in stackable_layers.values
    return is_stackable or new_layer != previous_layer_type


def get_allowed_new_block_layers(layers, parameters):
    return [
        new_layer
        for new_layer in parameters.get_layer_types()
        if is_allowed_block_layer(layers, new_layer, parameters)]


def mutate_blueprint(blueprint, parameters,
                     p_mutate_layout=0.25, p_mutate_param=0.25, mutation_std=0.1,
                     mutate_layout=True, mutate_params=True, mutate_optimizer=True,
                     layout_mutation_count=1, layout_mutables=None, mutate_in_place=True):
    if not mutate_in_place:
        blueprint = deepcopy(blueprint)
    if mutate_layout and rand.random() < p_mutate_layout:
        _mutate_layout(
            blueprint.layout,
            parameters,
            mutables=layout_mutables,
            mutation_count=layout_mutation_count)
    if mutate_params:
        _mutate_layer_parameters(
            blueprint.layout,
            parameters,
            p_mutate_param=p_mutate_param,
            mutation_std=mutation_std)
    if mutate_optimizer:
        _mutate_optimizer(
            blueprint.training.optimizer,
            parameters,
            p_mutate_param=p_mutate_param,
            mutation_std=mutation_std)
    return blueprint


def _mutate_layout(layout, parameters,
                   mutables=None, mutation_count=1):
    mutables = mutables or ['rows', 'blocks', 'layers']
    shuffle(mutables)
    for mutation in mutables[:mutation_count]:
        if mutation == 'rows':
            _mutate_layout_rows(layout, parameters)
        if mutation == 'blocks':
            _mutate_layout_blocks(layout, parameters)
        if mutation == 'layers':
            _mutate_layout_layers(layout, parameters)
    _setup_block_inputs(layout, parameters)


def _mutate_layout_rows(layout, parameters):
    current_rows = len(layout.get_rows())

    new_rows = mutate_param(