Reorganiza dados e adiciona modelo OAMRP

This commit is contained in:
Cesa-V
2026-06-15 19:00:45 -03:00
parent 32ad7c9f23
commit 1131267ea2
14 changed files with 192 additions and 0 deletions

188
software/oamrp_v1.py Normal file
View File

@@ -0,0 +1,188 @@
"""
============================================================================
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
# ---------------------------------------------------------------------------