""" ============================================================================ AIRCRAFT ROUTING - ESQUADRAO ARARA (Transporte C-95/C-105) ============================================================================ Modelo MIP compacto que ATRIBUI aeronaves a missoes de transporte, maximizando o numero (ponderado por prioridade) de missoes cumpridas, respeitando: - rota viavel (continuidade espacial e temporal por aeronave) - turnaround minimo entre missoes - limite de horas de celula ate a proxima inspecao (LRT) - base inicial e horas acumuladas reais de cada aeronave Diferenca chave vs. modelo civil (Bazargan): aqui a cobertura e' "<= 1" (missao OPCIONAL, selecionada se compensar) e nao "= 1". O objetivo e' prontidao/cumprimento de missao, nao custo. Solver: CBC (vem junto com o PuLP, zero instalacao). Para instancias maiores troque por HiGHS (ver nota no fim do arquivo). Autor: gerado como ponto de partida. pt-BR nos comentarios, codigo limpo. ============================================================================ """ import pulp # =========================================================================== # 1) DADOS DE ENTRADA (substitua pelos dados reais do ARARA) # =========================================================================== # Bases (codigos curtos ficticios). 'S' marca bases HABILITADAS a inspecao. # Na FAB o conjunto de bases habilitadas e' restrito -> isso aperta o routing. BASES_INSPECAO = {"BR", "GL"} # ex.: Brasilia (hub do esq.) e Galeao TURNAROUND_MIN_H = 1.0 # tempo minimo em solo entre 2 missoes (h) LIMITE_HORAS_F = 100.0 # horas de celula ate inspecao obrigatoria # (AJUSTE ao intervalo real da sua frota) # ----- Aeronaves (tail number, base inicial, horas acumuladas desde a ult. insp.) # 'horas_acum' = f_k. LRT inicial de cada aeronave = LIMITE_HORAS_F - f_k. AERONAVES = [ # id base horas_acum ("FAB-2301", "BR", 12.0), ("FAB-2302", "BR", 71.0), # ja gastou muito -> pouco LRT disponivel ("FAB-2303", "GL", 30.0), ("FAB-2304", "BR", 95.0), # quase no limite -> mal consegue voar ] # ----- Missoes de transporte: (id, origem, destino, partida_h, chegada_h, prioridade) # partida/chegada em horas decimais dentro do horizonte (ex.: 0..72 = 3 dias). # prioridade: peso da missao (missao critica pesa mais). MISSOES = [ # id orig dest part cheg prio ("M01", "BR", "MN", 6.0, 10.0, 5), # logistica Amazonia (alta prio) ("M02", "MN", "BR", 12.0, 16.0, 5), ("M03", "BR", "RF", 7.0, 10.0, 3), ("M04", "RF", "BR", 11.5, 14.5, 3), ("M05", "BR", "CG", 8.0, 10.0, 2), ("M06", "CG", "BR", 11.0, 13.0, 2), ("M07", "GL", "BR", 6.5, 8.0, 4), ("M08", "BR", "BE", 9.0, 12.5, 4), # Belem ("M09", "BE", "BR", 14.0, 17.5, 4), ("M10", "BR", "GL", 15.0, 16.5, 1), ("M11", "BR", "MN", 30.0, 34.0, 5), # dia 2 ("M12", "MN", "BR", 36.0, 40.0, 5), ] # =========================================================================== # 2) PRE-PROCESSAMENTO (gera conexoes viaveis) # =========================================================================== miss = {m[0]: dict(orig=m[1], dest=m[2], part=m[3], cheg=m[4], prio=m[5], dur=m[4] - m[3]) for m in MISSOES} acft = {a[0]: dict(base=a[1], f=a[2]) for a in AERONAVES} IDS = list(miss.keys()) KS = list(acft.keys()) # Conexao i->j valida: destino de i == origem de j e ha turnaround suficiente conex = [(i, j) for i in IDS for j in IDS if i != j and miss[i]["dest"] == miss[j]["orig"] and miss[j]["part"] >= miss[i]["cheg"] + TURNAROUND_MIN_H] # Aeronave k pode INICIAR pela missao i se i parte da base inicial de k def pode_iniciar(k, i): return miss[i]["orig"] == acft[k]["base"] # =========================================================================== # 3) MODELO MIP # =========================================================================== mdl = pulp.LpProblem("ARARA_Aircraft_Routing", pulp.LpMaximize) # --- Variaveis x = {(k, i): pulp.LpVariable(f"x_{k}_{i}", cat="Binary") for k in KS for i in IDS} s = {(k, i): pulp.LpVariable(f"s_{k}_{i}", cat="Binary") # k inicia por i for k in KS for i in IDS if pode_iniciar(k, i)} c = {(k, i, j): pulp.LpVariable(f"c_{k}_{i}_{j}", cat="Binary") # k voa i depois j for k in KS for (i, j) in conex} z = {i: pulp.LpVariable(f"z_{i}", cat="Binary") for i in IDS} # missao coberta # --- Objetivo: maximizar prioridade total das missoes cumpridas mdl += pulp.lpSum(miss[i]["prio"] * z[i] for i in IDS), "Missoes_cumpridas_ponderadas" # --- (R1) Cobertura: cada missao por NO MAXIMO uma aeronave for i in IDS: mdl += z[i] == pulp.lpSum(x[(k, i)] for k in KS), f"def_z_{i}" # z[i] ja e' binaria, entao automaticamente <= 1 # --- (R2) Fluxo de entrada: se k voa i, ele iniciou por i OU chegou de outra missao for k in KS: for i in IDS: entradas = pulp.lpSum(c[(k, jj, i)] for (jj, ii) in conex if ii == i) ini = s[(k, i)] if (k, i) in s else 0 mdl += ini + entradas == x[(k, i)], f"in_{k}_{i}" # --- (R3) Fluxo de saida: depois de i, k segue para no max uma proxima missao for k in KS: for i in IDS: saidas = pulp.lpSum(c[(k, i, jj)] for (ii, jj) in conex if ii == i) mdl += saidas <= x[(k, i)], f"out_{k}_{i}" # --- (R4) Cada aeronave inicia no maximo uma rota for k in KS: inis = [s[(k, i)] for i in IDS if (k, i) in s] if inis: mdl += pulp.lpSum(inis) <= 1, f"start_{k}" # --- (R5) Limite de horas de celula (LRT): total voado por k <= F - f_k # Esta e' a restricao CENTRAL de manutencao (versao "orcamento de horas"). # -> A inspecao com RESET de horas em base habilitada e' a extensao da Fase 3. for k in KS: mdl += pulp.lpSum(miss[i]["dur"] * x[(k, i)] for i in IDS) <= \ LIMITE_HORAS_F - acft[k]["f"], f"horas_{k}" # =========================================================================== # 4) RESOLVER # =========================================================================== solver = pulp.PULP_CBC_CMD(msg=False) # troque por HiGHS se instalado mdl.solve(solver) # =========================================================================== # 5) RELATORIO # =========================================================================== print("=" * 70) print(f" STATUS: {pulp.LpStatus[mdl.status]}") prio_tot = sum(miss[i]["prio"] for i in IDS) prio_obt = int(pulp.value(mdl.objective)) cob = [i for i in IDS if z[i].value() and z[i].value() > 0.5] print(f" Missoes cumpridas: {len(cob)}/{len(IDS)} " f"| Prioridade obtida: {prio_obt}/{prio_tot}") print("=" * 70) def rota_da_aeronave(k): """Reconstroi a sequencia de missoes da aeronave k seguindo as conexoes.""" inicio = [i for i in IDS if (k, i) in s and s[(k, i)].value() and s[(k, i)].value() > 0.5] if not inicio: return [] seq = [inicio[0]] while True: atual = seq[-1] prox = [j for (ii, jj) in conex if ii == atual for j in [jj] if c[(k, atual, j)].value() and c[(k, atual, j)].value() > 0.5] if not prox: break seq.append(prox[0]) return seq for k in KS: seq = rota_da_aeronave(k) horas = sum(miss[i]["dur"] for i in seq) lrt_ini = LIMITE_HORAS_F - acft[k]["f"] if seq: trechos = " -> ".join( f"{i}({miss[i]['orig']}-{miss[i]['dest']} {miss[i]['part']:g}h)" for i in seq) print(f"\n {k} [base {acft[k]['base']}, f={acft[k]['f']:g}h, LRT={lrt_ini:g}h]") print(f" {trechos}") print(f" horas voadas: {horas:g}h | LRT restante: {lrt_ini - horas:g}h") else: print(f"\n {k} [base {acft[k]['base']}, LRT={lrt_ini:g}h] -> sem missoes") descobertas = [i for i in IDS if i not in cob] if descobertas: print("\n " + "-" * 60) print(" Missoes NAO cumpridas:", ", ".join(f"{i}(prio {miss[i]['prio']})" for i in descobertas)) # --------------------------------------------------------------------------- # NOTA - usar HiGHS (recomendado p/ instancias maiores, sem licenca): # pip install highspy # solver = pulp.HiGHS(msg=False) # PuLP 3.x expoe HiGHS nativo # ---------------------------------------------------------------------------