feat: adiciona Modelo 2 para minimização do número absoluto de aeronaves

This commit is contained in:
2026-06-07 20:37:19 -03:00
parent edf6ef9d8f
commit 1cab2bbe36
2 changed files with 272 additions and 36 deletions

View File

@@ -272,6 +272,107 @@ for r in rotas_lista:
print(f"\\nTotal de voos alocados na malha diária: {voos_totais} de {total_aeronaves} aeronaves disponíveis.")
"""))
# Cell 11: Markdown Modelo 2
cells.append(nbf.v4.new_markdown_cell("""## 5. Modelo 2: Minimização de Aeronaves
Neste modelo, o objetivo é encontrar o **número mínimo absoluto de aeronaves** necessário para operar a malha diária estabelecida (os top trechos).
Considerações solicitadas:
- O tempo de voo é estimado usando a velocidade média real do C-97 em 2025.
- As aeronaves podem voar até 24 horas por dia (não consideraremos o tempo de solo no chão nesta versão).
- Se houver desbalanceamento de malha (mais voos saindo de A do que chegando em A), o modelo criará voos de reposicionamento vazios.
"""))
# Cell 12: Code Modelo 2
cells.append(nbf.v4.new_code_cell("""# 1. Calcular a velocidade média do C-97 em 2025
# Converte a string 'HH:MM:SS' para timedelta e extrai horas
df_c97_spd = df_c97.copy()
df_c97_spd['Tempo_Horas'] = pd.to_timedelta(df_c97_spd['Tempo de Voo']).dt.total_seconds() / 3600
def get_dist_row(row):
try:
lat1, lon1, alt1 = df_aeroportos.loc[row['Localidade Decolagem'], ['lat', 'lon', 'elevation']]
lat2, lon2, alt2 = df_aeroportos.loc[row['Localidade Pouso'], ['lat', 'lon', 'elevation']]
return pm.geodetic2aer(lat1, lon1, alt1, lat2, lon2, alt2)[2] / 1000.0
except:
return np.nan
df_c97_spd['Dist_km'] = df_c97_spd.apply(get_dist_row, axis=1)
# Filtra voos válidos para calcular a velocidade média
df_valid = df_c97_spd[(df_c97_spd['Tempo_Horas'] > 0) & (df_c97_spd['Dist_km'] > 0)].copy()
df_valid['Velocidade'] = df_valid['Dist_km'] / df_valid['Tempo_Horas']
vel_media_c97 = df_valid['Velocidade'].mean()
print(f"Velocidade média calculada para o C-97: {vel_media_c97:.2f} km/h")
# 2. Configurar os nós (aeroportos) e demandas da malha das rotas top
aeroportos_malha = set()
for r in rotas_lista:
o, d = r.split('-')
aeroportos_malha.add(o)
aeroportos_malha.add(d)
aeroportos_malha = list(aeroportos_malha)
# Matriz de tempo de voo entre todos os nós da malha (para reposicionamento)
tempos_voo = {}
for o in aeroportos_malha:
for d in aeroportos_malha:
if o == d:
tempos_voo[(o, d)] = 0
else:
dist = calc_distancia(o, d)
tempos_voo[(o, d)] = dist / vel_media_c97
# Demanda fixa D_{i,j}
D = {(o, d): 0 for o in aeroportos_malha for d in aeroportos_malha}
for r in rotas_lista:
o, d = r.split('-')
D[(o, d)] = voos_requeridos[r]
# 3. Modelagem Matemática (Pulp)
prob2 = pulp.LpProblem("Minimizar_Aeronaves", pulp.LpMinimize)
# Variáveis de reposicionamento vazio Y_{i,j}
Y = pulp.LpVariable.dicts("Y", [(o, d) for o in aeroportos_malha for d in aeroportos_malha], lowBound=0, cat='Integer')
# Variável de número de aeronaves N
N = pulp.LpVariable("N", lowBound=0, cat='Integer')
# Função Objetivo: Minimizar N
# Para desempatar soluções com mesmo N, damos um pequeno peso pro tempo de reposicionamento vazio (para pegar o roteamento mais curto também)
prob2 += N + 0.0001 * pulp.lpSum([Y[(i, j)] * tempos_voo[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])
# Restrição de conservação de fluxo para cada aeroporto (Chegadas == Partidas)
for no in aeroportos_malha:
chegadas = pulp.lpSum([D[(i, no)] + Y[(i, no)] for i in aeroportos_malha])
partidas = pulp.lpSum([D[(no, j)] + Y[(no, j)] for j in aeroportos_malha])
prob2 += chegadas == partidas
# Restrição de tempo (O tempo total de voo da malha não pode exceder N * 24h)
tempo_total_voo = pulp.lpSum([(D[(i, j)] + Y[(i, j)]) * tempos_voo[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])
prob2 += tempo_total_voo <= N * 24.0
# Solução
prob2.solve(pulp.PULP_CBC_CMD(msg=False))
print("\\n== RESULTADO DO MODELO 2 ==")
print(f"Status: {pulp.LpStatus[prob2.status]}")
print(f"Número mínimo absoluto de aeronaves C-97 necessárias: {int(N.varValue)}")
tempo_gasto = sum([(D[(i, j)] + Y[(i, j)].varValue) * tempos_voo[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])
print(f"Tempo total de voo diário (incluindo reposicionamento vazio): {tempo_gasto:.2f} horas")
if N.varValue > 0:
print(f"Taxa de ocupação da frota (Tempo Voo / Tempo Disponível): {(tempo_gasto / (N.varValue * 24)) * 100:.1f}%\\n")
print("Voos de Reposicionamento Vazio Gerados (Y) para fechar o ciclo diário:")
gerou_vazio = False
for i in aeroportos_malha:
for j in aeroportos_malha:
if Y[(i, j)].varValue > 0:
print(f" -> De {i} para {j}: {int(Y[(i, j)].varValue)} voo(s) vazio(s) | Tempo de voo: {tempos_voo[(i,j)]*Y[(i,j)].varValue:.1f}h")
gerou_vazio = True
if not gerou_vazio:
print(" Nenhum voo de reposicionamento vazio foi necessário! A malha já é circular/balanceada.")
"""))
nb.cells = cells
with open('modelos.ipynb', 'w', encoding='utf-8') as f: