from random import random

long_cromosoma = 28
tam_poblacion = 300
num_generaciones = 10
tasa_cruzamiento = 0.85
tasa_mutacion = 0.02

num_plantas = 4

class blank(object):
    pass

def mk_planta(lim_superior, longitud, a, b, c):
    p = blank()
    p.lim_superior = lim_superior
    p.longitud = longitud
    p.a = a
    p.b = b
    p.c = c
    return p

def mk_individuo():
    c = blank()
    c.cromosoma = [False for _ in range(long_cromosoma)]
    c.aptitud = 0
    c.soluciones = [0 for _ in range(num_plantas)]
    c.falla = 0
    c.costo = 0
    return c

pob_anterior = [mk_individuo() for _ in range(tam_poblacion)]
pob_actual = [mk_individuo() for _ in range(tam_poblacion)]
aptitud_pob = 0

plantas = [
    mk_planta(100, 7, 0.3, -18, 738),
    mk_planta(100, 7, 0.166, -6.66, 616),
    mk_planta(100, 7, 0.208, -12.08, 733.3),
    mk_planta(100, 7, 0.255, -15.4, 685)
]
capacidad = 180

def crear_poblacion_inicial():
    global pob_anterior
    for i in range(tam_poblacion):
        for j in range(long_cromosoma):
            pob_anterior[i].cromosoma[j] = random() < 0.5

def decodificar_cromosoma(cromosoma, soluciones):
    posicion = 0
    for i in range(num_plantas):
        acumulado = 0
        potencia = 1
        for j in range(plantas[i].longitud):
            if cromosoma[posicion]:
                acumulado += potencia
            potencia *= 2
            posicion += 1
        soluciones[i] = ajustar_solucion(acumulado, plantas[i].lim_superior)

def ajustar_solucion(valor, limite):
    return valor % (limite + 1)

def evaluar_poblacion(poblacion):
    global aptitud_pob
    aptitud_pob = 0
    for i in range(tam_poblacion):
        decodificar_cromosoma(poblacion[i].cromosoma, poblacion[i].soluciones)
        
        evaluar_individuo(poblacion[i])
        
        #evaluar_cromosoma(poblacion[i].soluciones, poblacion[i].aptitud, poblacion[i].falla, poblacion[i].costo)
        
        aptitud_pob += poblacion[i].aptitud

def evaluar_individuo(individuo):
    falla = capacidad
    costo = 0
    for i in range(num_plantas):
        x = individuo.soluciones[i]
        costo += plantas[i].a * x * x + plantas[i].b * x + plantas[i].c
        falla -= x
    falla = abs(falla)
    if falla == capacidad:
        aptitud = 0
    else:
        aptitud = 1 / (costo * (falla + 1))
    individuo.costo = costo
    individuo.aptitud = aptitud
    individuo.falla

def evaluar_cromosoma(soluciones, aptitud, falla, costo):
    falla = capacidad
    costo = 0
    for i in range(num_plantas):
        x = soluciones[i]
        costo += plantas[i].a * x * x + plantas[i].b * x + plantas[i].c
        falla -= x
    falla = abs(falla)
    if falla == capacidad:
        aptitud = 0
    else:
        aptitud = 1 / (costo * (falla + 1))

def seleccion():
    global pob_anterior, pob_actual, aptitud_pob
    for i in range(tam_poblacion):
        randomico = random() * aptitud_pob
        acumulado = 0
        for j in range(tam_poblacion):
            acumulado += pob_anterior[j].aptitud
            if randomico <= acumulado:
                break
        pob_actual[i].cromosoma = pob_anterior[j].cromosoma

def cruzamiento():
    global pob_actual
    vector_rand = [random() for _ in range(tam_poblacion)]
    for i in range(tam_poblacion):
        if vector_rand[i] >= tasa_cruzamiento:
            continue
        for j in range(i+1, tam_poblacion):
            if vector_rand[j] >= tasa_cruzamiento:
                continue
            puntocruz = int(long_cromosoma * random() + 1)
            if puntocruz > 0 and puntocruz < long_cromosoma:
                for k in range(puntocruz+1, long_cromosoma):
                    aux = pob_actual[i].cromosoma[k]
                    pob_actual[i].cromosoma[k] = pob_actual[j].cromosoma[k]
                    pob_actual[j].cromosoma[k] = aux
        # i = j

def mutacion():
    global pob_actual
    for i in range(tam_poblacion):
        for j in range(long_cromosoma):
            rnd = random()
            if rnd < tasa_mutacion:
                pob_actual[i].cromosoma[j] = not pob_actual[i].cromosoma[j]

def resultados_poblacion(poblacion):
    aptitud = 0
    j = 0
    for i in range(tam_poblacion):
        if poblacion[i].aptitud > aptitud:
            aptitud = poblacion[i].aptitud
            j = i
    val_cromosoma = ''
    for i in range(long_cromosoma):
        val_cromosoma += str(int(poblacion[j].cromosoma[i]))
    val_solucion = ''
    for i in range(num_plantas):
        val_solucion += str(poblacion[j].soluciones[i]) + ' '
    print(val_solucion)
    print(poblacion[j].costo)

def simular():
    global pob_anterior, pob_actual
    generacion = 0
    crear_poblacion_inicial()
    evaluar_poblacion(pob_anterior)
    resultados_poblacion(pob_anterior)
    for generacion in range(num_generaciones):
        seleccion()
        cruzamiento()
        mutacion()
        evaluar_poblacion(pob_actual)
        resultados_poblacion(pob_actual)
        pob_anterior = pob_actual

simular()