diff --git a/create_nb.py b/create_nb.py index 0635558..d110799 100644 --- a/create_nb.py +++ b/create_nb.py @@ -311,15 +311,15 @@ for r in rotas_lista: 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 = {} +# Matriz de tempo de voo entre todos os nós da malha em MINUTOS (inteiro) +tempos_voo_min = {} for o in aeroportos_malha: for d in aeroportos_malha: if o == d: - tempos_voo[(o, d)] = 0 + tempos_voo_min[(o, d)] = 0 else: dist = calc_distancia(o, d) - tempos_voo[(o, d)] = dist / vel_media_c97 + tempos_voo_min[(o, d)] = int(round((dist / vel_media_c97) * 60)) # Demanda fixa D_{i,j} D = {(o, d): 0 for o in aeroportos_malha for d in aeroportos_malha} @@ -338,7 +338,7 @@ 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]) +prob2 += N + 0.0001 * pulp.lpSum([Y[(i, j)] * tempos_voo_min[(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: @@ -346,9 +346,9 @@ for no 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 +# Restrição de tempo (O tempo total de voo da malha não pode exceder N * 1440 min) +tempo_total_voo = pulp.lpSum([(D[(i, j)] + Y[(i, j)]) * tempos_voo_min[(i, j)] for i in aeroportos_malha for j in aeroportos_malha]) +prob2 += tempo_total_voo <= N * 1440 # Solução prob2.solve(pulp.PULP_CBC_CMD(msg=False)) @@ -357,17 +357,17 @@ 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") +tempo_gasto = sum([(D[(i, j)] + int(Y[(i, j)].varValue)) * tempos_voo_min[(i, j)] for i in aeroportos_malha for j in aeroportos_malha]) +print(f"Tempo total de voo diário (incluindo reposicionamento vazio): {int(tempo_gasto)} min ({tempo_gasto/60:.2f} h)") 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(f"Taxa de ocupação da frota (Tempo Voo / Tempo Disponível): {(tempo_gasto / (N.varValue * 1440)) * 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") + print(f" -> De {i} para {j}: {int(Y[(i, j)].varValue)} voo(s) vazio(s) | Tempo de voo: {tempos_voo_min[(i,j)]*int(Y[(i,j)].varValue)} min") gerou_vazio = True if not gerou_vazio: print(" Nenhum voo de reposicionamento vazio foi necessário! A malha já é circular/balanceada.") @@ -391,7 +391,7 @@ for i in aeroportos_malha: D_copy[(i, j)] -= 1 else: tipo = 'Vazio (Reposicionamento)' - voos_para_fazer.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo[(i, j)]}) + voos_para_fazer.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo_min[(i, j)]}) # 2. Simular a alocação sequencial de horários tabela_horarios = [] @@ -401,7 +401,7 @@ while voos_para_fazer: voo_atual = voos_para_fazer.pop(0) hora_atual = datetime.datetime(2025, 1, 1, 0, 0, 0) - tempo_delta = datetime.timedelta(hours=voo_atual['tempo']) + tempo_delta = datetime.timedelta(minutes=int(voo_atual['tempo'])) hora_chegada = hora_atual + tempo_delta tabela_horarios.append([ @@ -411,7 +411,7 @@ while voos_para_fazer: voo_atual['tipo'], hora_atual.strftime('%H:%M'), hora_chegada.strftime('%H:%M'), - f"{voo_atual['tempo']:.2f} h" + f"{voo_atual['tempo']} min" ]) hora_atual = hora_chegada @@ -422,12 +422,12 @@ while voos_para_fazer: prox_voo = None for idx, v in enumerate(voos_para_fazer): if v['origem'] == local_atual: - if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(hours=v['tempo'])).total_seconds() <= 24 * 3600: + if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(minutes=int(v['tempo']))).total_seconds() <= 1440 * 60: prox_voo = voos_para_fazer.pop(idx) break if prox_voo: - tempo_delta = datetime.timedelta(hours=prox_voo['tempo']) + tempo_delta = datetime.timedelta(minutes=int(prox_voo['tempo'])) hora_chegada = hora_atual + tempo_delta tabela_horarios.append([ f"Aeronave {aeronave_id}", @@ -436,7 +436,7 @@ while voos_para_fazer: prox_voo['tipo'], hora_atual.strftime('%H:%M'), hora_chegada.strftime('%H:%M'), - f"{prox_voo['tempo']:.2f} h" + f"{prox_voo['tempo']} min" ]) hora_atual = hora_chegada local_atual = prox_voo['destino'] @@ -465,10 +465,10 @@ prob3 = pulp.LpProblem("Minimizar_Aeronaves_TAT", pulp.LpMinimize) Y3 = pulp.LpVariable.dicts("Y3", [(o, d) for o in aeroportos_malha for d in aeroportos_malha], lowBound=0, cat='Integer') N3 = pulp.LpVariable("N3", lowBound=0, cat='Integer') -TAT_horas = 40.0 / 60.0 +TAT_min = 40 # Função Objetivo -prob3 += N3 + 0.0001 * pulp.lpSum([Y3[(i, j)] * tempos_voo[(i, j)] for i in aeroportos_malha for j in aeroportos_malha]) +prob3 += N3 + 0.0001 * pulp.lpSum([Y3[(i, j)] * tempos_voo_min[(i, j)] for i in aeroportos_malha for j in aeroportos_malha]) # Restrição de fluxo for no in aeroportos_malha: @@ -478,8 +478,8 @@ for no in aeroportos_malha: # Restrição de tempo com TAT # Cada voo (D ou Y) consome tempo de voo + TAT -tempo_total_operacional = pulp.lpSum([(D[(i, j)] + Y3[(i, j)]) * (tempos_voo[(i, j)] + TAT_horas) for i in aeroportos_malha for j in aeroportos_malha]) -prob3 += tempo_total_operacional <= N3 * 24.0 +tempo_total_operacional = pulp.lpSum([(D[(i, j)] + Y3[(i, j)]) * (tempos_voo_min[(i, j)] + TAT_min) for i in aeroportos_malha for j in aeroportos_malha]) +prob3 += tempo_total_operacional <= N3 * 1440 # Solução prob3.solve(pulp.PULP_CBC_CMD(msg=False)) @@ -488,15 +488,15 @@ print("\\n== RESULTADO DO MODELO 3 (COM 40 MIN DE SOLO) ==") print(f"Status: {pulp.LpStatus[prob3.status]}") print(f"Número mínimo absoluto de aeronaves C-97 necessárias: {int(N3.varValue)}") -tempo_voo_puro = sum([(D[(i, j)] + Y3[(i, j)].varValue) * tempos_voo[(i, j)] for i in aeroportos_malha for j in aeroportos_malha]) -total_voos_realizados = sum([(D[(i, j)] + Y3[(i, j)].varValue) for i in aeroportos_malha for j in aeroportos_malha]) -tempo_solo_total = total_voos_realizados * TAT_horas +tempo_voo_puro = sum([(D[(i, j)] + int(Y3[(i, j)].varValue)) * tempos_voo_min[(i, j)] for i in aeroportos_malha for j in aeroportos_malha]) +total_voos_realizados = sum([(D[(i, j)] + int(Y3[(i, j)].varValue)) for i in aeroportos_malha for j in aeroportos_malha]) +tempo_solo_total = total_voos_realizados * TAT_min -print(f"Tempo total de voo puro: {tempo_voo_puro:.2f} horas") -print(f"Tempo total gasto em solo (TAT): {tempo_solo_total:.2f} horas") -print(f"Tempo total operacional comprometido: {tempo_voo_puro + tempo_solo_total:.2f} horas") +print(f"Tempo total de voo puro: {tempo_voo_puro} min ({tempo_voo_puro/60:.2f} h)") +print(f"Tempo total gasto em solo (TAT): {tempo_solo_total} min ({tempo_solo_total/60:.2f} h)") +print(f"Tempo total operacional comprometido: {tempo_voo_puro + tempo_solo_total} min ({(tempo_voo_puro + tempo_solo_total)/60:.2f} h)") if N3.varValue > 0: - print(f"Taxa de ocupação da frota (Tempo Operacional / Tempo Disponível): {((tempo_voo_puro + tempo_solo_total) / (N3.varValue * 24)) * 100:.1f}%\\n") + print(f"Taxa de ocupação da frota (Tempo Operacional / Tempo Disponível): {((tempo_voo_puro + tempo_solo_total) / (N3.varValue * 1440)) * 100:.1f}%\\n") # GERAR TABELA DE HORÁRIOS COM TABULATE E DATETIME voos_para_fazer3 = [] @@ -511,7 +511,7 @@ for i in aeroportos_malha: D_copy3[(i, j)] -= 1 else: tipo = 'Vazio (Reposicionamento)' - voos_para_fazer3.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo[(i, j)]}) + voos_para_fazer3.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo_min[(i, j)]}) tabela_horarios3 = [] aeronave_id3 = 1 @@ -520,7 +520,7 @@ while voos_para_fazer3: voo_atual = voos_para_fazer3.pop(0) hora_atual = datetime.datetime(2025, 1, 1, 0, 0, 0) - tempo_delta = datetime.timedelta(hours=voo_atual['tempo']) + tempo_delta = datetime.timedelta(minutes=int(voo_atual['tempo'])) hora_chegada = hora_atual + tempo_delta tabela_horarios3.append([ @@ -530,7 +530,7 @@ while voos_para_fazer3: voo_atual['tipo'], hora_atual.strftime('%H:%M'), hora_chegada.strftime('%H:%M'), - f"{voo_atual['tempo']:.2f} h" + f"{voo_atual['tempo']} min" ]) # Próxima partida só depois do TAT de 40 min @@ -541,12 +541,12 @@ while voos_para_fazer3: prox_voo = None for idx, v in enumerate(voos_para_fazer3): if v['origem'] == local_atual: - if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(hours=v['tempo'])).total_seconds() <= 24 * 3600: + if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(minutes=int(v['tempo']))).total_seconds() <= 1440 * 60: prox_voo = voos_para_fazer3.pop(idx) break if prox_voo: - tempo_delta = datetime.timedelta(hours=prox_voo['tempo']) + tempo_delta = datetime.timedelta(minutes=int(prox_voo['tempo'])) hora_chegada = hora_atual + tempo_delta tabela_horarios3.append([ f"Aeronave {aeronave_id3}", @@ -555,7 +555,7 @@ while voos_para_fazer3: prox_voo['tipo'], hora_atual.strftime('%H:%M'), hora_chegada.strftime('%H:%M'), - f"{prox_voo['tempo']:.2f} h" + f"{prox_voo['tempo']} min" ]) hora_atual = hora_chegada + datetime.timedelta(minutes=40) local_atual = prox_voo['destino'] diff --git a/modelos.ipynb b/modelos.ipynb index f5be2ff..4d754b7 100644 --- a/modelos.ipynb +++ b/modelos.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "eeb50313", + "id": "79f9c4cd", "metadata": {}, "source": [ "# Alocação de Frotas (Fleet Scheduling) - Aeronaves C-97\n", @@ -21,13 +21,13 @@ { "cell_type": "code", "execution_count": 1, - "id": "3345f6bf", + "id": "54f43903", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:44:10.042075Z", - "iopub.status.busy": "2026-06-07T23:44:10.041422Z", - "iopub.status.idle": "2026-06-07T23:44:10.717819Z", - "shell.execute_reply": "2026-06-07T23:44:10.717190Z" + "iopub.execute_input": "2026-06-07T23:48:22.236217Z", + "iopub.status.busy": "2026-06-07T23:48:22.236030Z", + "iopub.status.idle": "2026-06-07T23:48:22.868108Z", + "shell.execute_reply": "2026-06-07T23:48:22.867334Z" } }, "outputs": [], @@ -53,7 +53,7 @@ }, { "cell_type": "markdown", - "id": "bb838c8d", + "id": "56cb48ea", "metadata": {}, "source": [ "## 1. Leitura e Limpeza dos Dados\n", @@ -63,13 +63,13 @@ { "cell_type": "code", "execution_count": 2, - "id": "af65863f", + "id": "47598c7a", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:44:10.719344Z", - "iopub.status.busy": "2026-06-07T23:44:10.719122Z", - "iopub.status.idle": "2026-06-07T23:44:11.177567Z", - "shell.execute_reply": "2026-06-07T23:44:11.176910Z" + "iopub.execute_input": "2026-06-07T23:48:22.869683Z", + "iopub.status.busy": "2026-06-07T23:48:22.869463Z", + "iopub.status.idle": "2026-06-07T23:48:23.259672Z", + "shell.execute_reply": "2026-06-07T23:48:23.259069Z" } }, "outputs": [], @@ -101,7 +101,7 @@ }, { "cell_type": "markdown", - "id": "f4e097f0", + "id": "6c58dbc1", "metadata": {}, "source": [ "## 2. Análise dos Esquadrões e Matrículas (Aeronaves)\n", @@ -111,13 +111,13 @@ { "cell_type": "code", "execution_count": 3, - "id": "01c6de00", + "id": "5a0bbad7", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:44:11.179085Z", - "iopub.status.busy": "2026-06-07T23:44:11.178943Z", - "iopub.status.idle": "2026-06-07T23:44:11.193498Z", - "shell.execute_reply": "2026-06-07T23:44:11.192832Z" + "iopub.execute_input": "2026-06-07T23:48:23.261299Z", + "iopub.status.busy": "2026-06-07T23:48:23.261168Z", + "iopub.status.idle": "2026-06-07T23:48:23.273090Z", + "shell.execute_reply": "2026-06-07T23:48:23.272489Z" } }, "outputs": [ @@ -176,7 +176,7 @@ }, { "cell_type": "markdown", - "id": "03d676ac", + "id": "112d4888", "metadata": {}, "source": [ "## 3. Análise Estatística das Demandas Diárias\n", @@ -187,13 +187,13 @@ { "cell_type": "code", "execution_count": 4, - "id": "d1d53c92", + "id": "fcd3f8cd", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:44:11.195008Z", - "iopub.status.busy": "2026-06-07T23:44:11.194881Z", - "iopub.status.idle": "2026-06-07T23:44:11.521316Z", - "shell.execute_reply": "2026-06-07T23:44:11.520564Z" + "iopub.execute_input": "2026-06-07T23:48:23.274563Z", + "iopub.status.busy": "2026-06-07T23:48:23.274434Z", + "iopub.status.idle": "2026-06-07T23:48:23.596693Z", + "shell.execute_reply": "2026-06-07T23:48:23.596151Z" } }, "outputs": [ @@ -424,7 +424,7 @@ }, { "cell_type": "markdown", - "id": "5a340e61", + "id": "13a07f72", "metadata": {}, "source": [ "## Diagrama de Rede dos Trechos\n", @@ -434,13 +434,13 @@ { "cell_type": "code", "execution_count": 5, - "id": "233d1236", + "id": "cebc71a4", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:44:11.522783Z", - "iopub.status.busy": "2026-06-07T23:44:11.522643Z", - "iopub.status.idle": "2026-06-07T23:44:11.652315Z", - "shell.execute_reply": "2026-06-07T23:44:11.651816Z" + "iopub.execute_input": "2026-06-07T23:48:23.598286Z", + "iopub.status.busy": "2026-06-07T23:48:23.598153Z", + "iopub.status.idle": "2026-06-07T23:48:23.718347Z", + "shell.execute_reply": "2026-06-07T23:48:23.717643Z" } }, "outputs": [ @@ -497,7 +497,7 @@ }, { "cell_type": "markdown", - "id": "19a69fa7", + "id": "561a020b", "metadata": {}, "source": [ "## 4. Fleet Assignment Model (FAM) - Nível Brasil\n", @@ -514,13 +514,13 @@ { "cell_type": "code", "execution_count": 6, - "id": "77122760", + "id": "191b2c03", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:44:11.654036Z", - "iopub.status.busy": "2026-06-07T23:44:11.653900Z", - "iopub.status.idle": "2026-06-07T23:44:11.681677Z", - "shell.execute_reply": "2026-06-07T23:44:11.681090Z" + "iopub.execute_input": "2026-06-07T23:48:23.719928Z", + "iopub.status.busy": "2026-06-07T23:48:23.719652Z", + "iopub.status.idle": "2026-06-07T23:48:23.746255Z", + "shell.execute_reply": "2026-06-07T23:48:23.745701Z" } }, "outputs": [ @@ -609,7 +609,7 @@ }, { "cell_type": "markdown", - "id": "d52acdc3", + "id": "b16a0622", "metadata": {}, "source": [ "## 5. Modelo 2: Minimização de Aeronaves\n", @@ -623,13 +623,13 @@ { "cell_type": "code", "execution_count": 7, - "id": "e4289b56", + "id": "c24cc47b", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:44:11.683322Z", - "iopub.status.busy": "2026-06-07T23:44:11.683175Z", - "iopub.status.idle": "2026-06-07T23:44:13.105611Z", - "shell.execute_reply": "2026-06-07T23:44:13.105024Z" + "iopub.execute_input": "2026-06-07T23:48:23.747882Z", + "iopub.status.busy": "2026-06-07T23:48:23.747749Z", + "iopub.status.idle": "2026-06-07T23:48:25.090530Z", + "shell.execute_reply": "2026-06-07T23:48:25.089931Z" } }, "outputs": [ @@ -642,26 +642,26 @@ "== RESULTADO DO MODELO 2 ==\n", "Status: Optimal\n", "Número mínimo absoluto de aeronaves C-97 necessárias: 1\n", - "Tempo total de voo diário (incluindo reposicionamento vazio): 16.70 horas\n", - "Taxa de ocupação da frota (Tempo Voo / Tempo Disponível): 69.6%\n", + "Tempo total de voo diário (incluindo reposicionamento vazio): 1004 min (16.73 h)\n", + "Taxa de ocupação da frota (Tempo Voo / Tempo Disponível): 69.7%\n", "\n", "Voos de Reposicionamento Vazio Gerados (Y) para fechar o ciclo diário:\n", - " -> De SBAN para SBSJ: 1 voo(s) vazio(s) | Tempo de voo: 2.1h\n", + " -> De SBAN para SBSJ: 1 voo(s) vazio(s) | Tempo de voo: 129 min\n", "\n", "== QUADRO DE HORÁRIOS DIÁRIOS ==\n", "| Aeronave | Origem | Destino | Tipo | Partida | Chegada | Duração |\n", "|------------|----------|-----------|--------------------------|-----------|-----------|-----------|\n", - "| Aeronave 1 | SBGL | SBGR | Passageiro | 00:00 | 00:51 | 0.86 h |\n", - "| Aeronave 1 | SBGR | SBGL | Passageiro | 00:51 | 01:43 | 0.86 h |\n", - "| Aeronave 1 | SBGL | SBBR | Passageiro | 01:43 | 04:02 | 2.33 h |\n", - "| Aeronave 1 | SBBR | SBGL | Passageiro | 04:02 | 06:22 | 2.33 h |\n", - "| Aeronave 1 | SBGL | SBSJ | Passageiro | 06:22 | 07:03 | 0.69 h |\n", - "| Aeronave 1 | SBSJ | SBGL | Passageiro | 07:03 | 07:45 | 0.69 h |\n", - "| Aeronave 2 | SBGR | SBBR | Passageiro | 00:00 | 02:10 | 2.17 h |\n", - "| Aeronave 2 | SBBR | SBGR | Passageiro | 02:10 | 04:20 | 2.17 h |\n", - "| Aeronave 3 | SBBR | SBAN | Passageiro | 00:00 | 00:18 | 0.30 h |\n", - "| Aeronave 3 | SBAN | SBSJ | Vazio (Reposicionamento) | 00:18 | 02:26 | 2.14 h |\n", - "| Aeronave 3 | SBSJ | SBBR | Passageiro | 02:26 | 04:35 | 2.15 h |\n" + "| Aeronave 1 | SBSJ | SBGL | Passageiro | 00:00 | 00:42 | 42 min |\n", + "| Aeronave 1 | SBGL | SBSJ | Passageiro | 00:42 | 01:24 | 42 min |\n", + "| Aeronave 1 | SBSJ | SBBR | Passageiro | 01:24 | 03:33 | 129 min |\n", + "| Aeronave 1 | SBBR | SBGL | Passageiro | 03:33 | 05:53 | 140 min |\n", + "| Aeronave 1 | SBGL | SBBR | Passageiro | 05:53 | 08:13 | 140 min |\n", + "| Aeronave 1 | SBBR | SBAN | Passageiro | 08:13 | 08:31 | 18 min |\n", + "| Aeronave 1 | SBAN | SBSJ | Vazio (Reposicionamento) | 08:31 | 10:40 | 129 min |\n", + "| Aeronave 2 | SBGL | SBGR | Passageiro | 00:00 | 00:52 | 52 min |\n", + "| Aeronave 2 | SBGR | SBGL | Passageiro | 00:52 | 01:44 | 52 min |\n", + "| Aeronave 3 | SBBR | SBGR | Passageiro | 00:00 | 02:10 | 130 min |\n", + "| Aeronave 3 | SBGR | SBBR | Passageiro | 02:10 | 04:20 | 130 min |\n" ] } ], @@ -695,15 +695,15 @@ " aeroportos_malha.add(d)\n", "aeroportos_malha = list(aeroportos_malha)\n", "\n", - "# Matriz de tempo de voo entre todos os nós da malha (para reposicionamento)\n", - "tempos_voo = {}\n", + "# Matriz de tempo de voo entre todos os nós da malha em MINUTOS (inteiro)\n", + "tempos_voo_min = {}\n", "for o in aeroportos_malha:\n", " for d in aeroportos_malha:\n", " if o == d:\n", - " tempos_voo[(o, d)] = 0\n", + " tempos_voo_min[(o, d)] = 0\n", " else:\n", " dist = calc_distancia(o, d)\n", - " tempos_voo[(o, d)] = dist / vel_media_c97\n", + " tempos_voo_min[(o, d)] = int(round((dist / vel_media_c97) * 60))\n", "\n", "# Demanda fixa D_{i,j}\n", "D = {(o, d): 0 for o in aeroportos_malha for d in aeroportos_malha}\n", @@ -722,7 +722,7 @@ "\n", "# Função Objetivo: Minimizar N\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)\n", - "prob2 += N + 0.0001 * pulp.lpSum([Y[(i, j)] * tempos_voo[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])\n", + "prob2 += N + 0.0001 * pulp.lpSum([Y[(i, j)] * tempos_voo_min[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])\n", "\n", "# Restrição de conservação de fluxo para cada aeroporto (Chegadas == Partidas)\n", "for no in aeroportos_malha:\n", @@ -730,9 +730,9 @@ " partidas = pulp.lpSum([D[(no, j)] + Y[(no, j)] for j in aeroportos_malha])\n", " prob2 += chegadas == partidas\n", "\n", - "# Restrição de tempo (O tempo total de voo da malha não pode exceder N * 24h)\n", - "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])\n", - "prob2 += tempo_total_voo <= N * 24.0\n", + "# Restrição de tempo (O tempo total de voo da malha não pode exceder N * 1440 min)\n", + "tempo_total_voo = pulp.lpSum([(D[(i, j)] + Y[(i, j)]) * tempos_voo_min[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])\n", + "prob2 += tempo_total_voo <= N * 1440\n", "\n", "# Solução\n", "prob2.solve(pulp.PULP_CBC_CMD(msg=False))\n", @@ -741,17 +741,17 @@ "print(f\"Status: {pulp.LpStatus[prob2.status]}\")\n", "print(f\"Número mínimo absoluto de aeronaves C-97 necessárias: {int(N.varValue)}\")\n", "\n", - "tempo_gasto = sum([(D[(i, j)] + Y[(i, j)].varValue) * tempos_voo[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])\n", - "print(f\"Tempo total de voo diário (incluindo reposicionamento vazio): {tempo_gasto:.2f} horas\")\n", + "tempo_gasto = sum([(D[(i, j)] + int(Y[(i, j)].varValue)) * tempos_voo_min[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])\n", + "print(f\"Tempo total de voo diário (incluindo reposicionamento vazio): {int(tempo_gasto)} min ({tempo_gasto/60:.2f} h)\")\n", "if N.varValue > 0:\n", - " print(f\"Taxa de ocupação da frota (Tempo Voo / Tempo Disponível): {(tempo_gasto / (N.varValue * 24)) * 100:.1f}%\\n\")\n", + " print(f\"Taxa de ocupação da frota (Tempo Voo / Tempo Disponível): {(tempo_gasto / (N.varValue * 1440)) * 100:.1f}%\\n\")\n", "\n", "print(\"Voos de Reposicionamento Vazio Gerados (Y) para fechar o ciclo diário:\")\n", "gerou_vazio = False\n", "for i in aeroportos_malha:\n", " for j in aeroportos_malha:\n", " if Y[(i, j)].varValue > 0:\n", - " 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\")\n", + " print(f\" -> De {i} para {j}: {int(Y[(i, j)].varValue)} voo(s) vazio(s) | Tempo de voo: {tempos_voo_min[(i,j)]*int(Y[(i,j)].varValue)} min\")\n", " gerou_vazio = True\n", "if not gerou_vazio:\n", " print(\" Nenhum voo de reposicionamento vazio foi necessário! A malha já é circular/balanceada.\")\n", @@ -775,7 +775,7 @@ " D_copy[(i, j)] -= 1\n", " else:\n", " tipo = 'Vazio (Reposicionamento)'\n", - " voos_para_fazer.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo[(i, j)]})\n", + " voos_para_fazer.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo_min[(i, j)]})\n", "\n", "# 2. Simular a alocação sequencial de horários\n", "tabela_horarios = []\n", @@ -785,7 +785,7 @@ " voo_atual = voos_para_fazer.pop(0)\n", " hora_atual = datetime.datetime(2025, 1, 1, 0, 0, 0)\n", " \n", - " tempo_delta = datetime.timedelta(hours=voo_atual['tempo'])\n", + " tempo_delta = datetime.timedelta(minutes=int(voo_atual['tempo']))\n", " hora_chegada = hora_atual + tempo_delta\n", " \n", " tabela_horarios.append([\n", @@ -795,7 +795,7 @@ " voo_atual['tipo'],\n", " hora_atual.strftime('%H:%M'),\n", " hora_chegada.strftime('%H:%M'),\n", - " f\"{voo_atual['tempo']:.2f} h\"\n", + " f\"{voo_atual['tempo']} min\"\n", " ])\n", " \n", " hora_atual = hora_chegada\n", @@ -806,12 +806,12 @@ " prox_voo = None\n", " for idx, v in enumerate(voos_para_fazer):\n", " if v['origem'] == local_atual:\n", - " if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(hours=v['tempo'])).total_seconds() <= 24 * 3600:\n", + " if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(minutes=int(v['tempo']))).total_seconds() <= 1440 * 60:\n", " prox_voo = voos_para_fazer.pop(idx)\n", " break\n", " \n", " if prox_voo:\n", - " tempo_delta = datetime.timedelta(hours=prox_voo['tempo'])\n", + " tempo_delta = datetime.timedelta(minutes=int(prox_voo['tempo']))\n", " hora_chegada = hora_atual + tempo_delta\n", " tabela_horarios.append([\n", " f\"Aeronave {aeronave_id}\",\n", @@ -820,7 +820,7 @@ " prox_voo['tipo'],\n", " hora_atual.strftime('%H:%M'),\n", " hora_chegada.strftime('%H:%M'),\n", - " f\"{prox_voo['tempo']:.2f} h\"\n", + " f\"{prox_voo['tempo']} min\"\n", " ])\n", " hora_atual = hora_chegada\n", " local_atual = prox_voo['destino']\n", @@ -835,7 +835,7 @@ }, { "cell_type": "markdown", - "id": "2daf591f", + "id": "f18a2ffb", "metadata": {}, "source": [ "## 6. Modelo 3: Minimização de Aeronaves com TAT (Turnaround Time)\n", @@ -848,13 +848,13 @@ { "cell_type": "code", "execution_count": 8, - "id": "4d54330a", + "id": "13888b4d", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:44:13.108816Z", - "iopub.status.busy": "2026-06-07T23:44:13.108593Z", - "iopub.status.idle": "2026-06-07T23:44:13.126370Z", - "shell.execute_reply": "2026-06-07T23:44:13.125849Z" + "iopub.execute_input": "2026-06-07T23:48:25.091858Z", + "iopub.status.busy": "2026-06-07T23:48:25.091719Z", + "iopub.status.idle": "2026-06-07T23:48:25.108636Z", + "shell.execute_reply": "2026-06-07T23:48:25.108109Z" } }, "outputs": [ @@ -866,25 +866,25 @@ "== RESULTADO DO MODELO 3 (COM 40 MIN DE SOLO) ==\n", "Status: Optimal\n", "Número mínimo absoluto de aeronaves C-97 necessárias: 2\n", - "Tempo total de voo puro: 16.70 horas\n", - "Tempo total gasto em solo (TAT): 7.33 horas\n", - "Tempo total operacional comprometido: 24.04 horas\n", + "Tempo total de voo puro: 1004 min (16.73 h)\n", + "Tempo total gasto em solo (TAT): 440 min (7.33 h)\n", + "Tempo total operacional comprometido: 1444 min (24.07 h)\n", "Taxa de ocupação da frota (Tempo Operacional / Tempo Disponível): 50.1%\n", "\n", "== QUADRO DE HORÁRIOS DIÁRIOS (COM 40 MIN TAT) ==\n", "| Aeronave | Origem | Destino | Tipo | Partida | Chegada | Duração Voo |\n", "|------------|----------|-----------|--------------------------|-----------|-----------|---------------|\n", - "| Aeronave 1 | SBGL | SBGR | Passageiro | 00:00 | 00:51 | 0.86 h |\n", - "| Aeronave 1 | SBGR | SBGL | Passageiro | 01:31 | 02:23 | 0.86 h |\n", - "| Aeronave 1 | SBGL | SBBR | Passageiro | 03:03 | 05:22 | 2.33 h |\n", - "| Aeronave 1 | SBBR | SBGL | Passageiro | 06:02 | 08:22 | 2.33 h |\n", - "| Aeronave 1 | SBGL | SBSJ | Passageiro | 09:02 | 09:43 | 0.69 h |\n", - "| Aeronave 1 | SBSJ | SBGL | Passageiro | 10:23 | 11:05 | 0.69 h |\n", - "| Aeronave 2 | SBGR | SBBR | Passageiro | 00:00 | 02:10 | 2.17 h |\n", - "| Aeronave 2 | SBBR | SBGR | Passageiro | 02:50 | 05:00 | 2.17 h |\n", - "| Aeronave 3 | SBBR | SBAN | Passageiro | 00:00 | 00:18 | 0.30 h |\n", - "| Aeronave 3 | SBAN | SBSJ | Vazio (Reposicionamento) | 00:58 | 03:06 | 2.14 h |\n", - "| Aeronave 3 | SBSJ | SBBR | Passageiro | 03:46 | 05:55 | 2.15 h |\n" + "| Aeronave 1 | SBSJ | SBGL | Passageiro | 00:00 | 00:42 | 42 min |\n", + "| Aeronave 1 | SBGL | SBSJ | Passageiro | 01:22 | 02:04 | 42 min |\n", + "| Aeronave 1 | SBSJ | SBBR | Passageiro | 02:44 | 04:53 | 129 min |\n", + "| Aeronave 1 | SBBR | SBGL | Passageiro | 05:33 | 07:53 | 140 min |\n", + "| Aeronave 1 | SBGL | SBBR | Passageiro | 08:33 | 10:53 | 140 min |\n", + "| Aeronave 1 | SBBR | SBAN | Passageiro | 11:33 | 11:51 | 18 min |\n", + "| Aeronave 1 | SBAN | SBSJ | Vazio (Reposicionamento) | 12:31 | 14:40 | 129 min |\n", + "| Aeronave 2 | SBGL | SBGR | Passageiro | 00:00 | 00:52 | 52 min |\n", + "| Aeronave 2 | SBGR | SBGL | Passageiro | 01:32 | 02:24 | 52 min |\n", + "| Aeronave 3 | SBBR | SBGR | Passageiro | 00:00 | 02:10 | 130 min |\n", + "| Aeronave 3 | SBGR | SBBR | Passageiro | 02:50 | 05:00 | 130 min |\n" ] } ], @@ -896,10 +896,10 @@ "Y3 = pulp.LpVariable.dicts(\"Y3\", [(o, d) for o in aeroportos_malha for d in aeroportos_malha], lowBound=0, cat='Integer')\n", "N3 = pulp.LpVariable(\"N3\", lowBound=0, cat='Integer')\n", "\n", - "TAT_horas = 40.0 / 60.0\n", + "TAT_min = 40\n", "\n", "# Função Objetivo\n", - "prob3 += N3 + 0.0001 * pulp.lpSum([Y3[(i, j)] * tempos_voo[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])\n", + "prob3 += N3 + 0.0001 * pulp.lpSum([Y3[(i, j)] * tempos_voo_min[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])\n", "\n", "# Restrição de fluxo\n", "for no in aeroportos_malha:\n", @@ -909,8 +909,8 @@ "\n", "# Restrição de tempo com TAT\n", "# Cada voo (D ou Y) consome tempo de voo + TAT\n", - "tempo_total_operacional = pulp.lpSum([(D[(i, j)] + Y3[(i, j)]) * (tempos_voo[(i, j)] + TAT_horas) for i in aeroportos_malha for j in aeroportos_malha])\n", - "prob3 += tempo_total_operacional <= N3 * 24.0\n", + "tempo_total_operacional = pulp.lpSum([(D[(i, j)] + Y3[(i, j)]) * (tempos_voo_min[(i, j)] + TAT_min) for i in aeroportos_malha for j in aeroportos_malha])\n", + "prob3 += tempo_total_operacional <= N3 * 1440\n", "\n", "# Solução\n", "prob3.solve(pulp.PULP_CBC_CMD(msg=False))\n", @@ -919,15 +919,15 @@ "print(f\"Status: {pulp.LpStatus[prob3.status]}\")\n", "print(f\"Número mínimo absoluto de aeronaves C-97 necessárias: {int(N3.varValue)}\")\n", "\n", - "tempo_voo_puro = sum([(D[(i, j)] + Y3[(i, j)].varValue) * tempos_voo[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])\n", - "total_voos_realizados = sum([(D[(i, j)] + Y3[(i, j)].varValue) for i in aeroportos_malha for j in aeroportos_malha])\n", - "tempo_solo_total = total_voos_realizados * TAT_horas\n", + "tempo_voo_puro = sum([(D[(i, j)] + int(Y3[(i, j)].varValue)) * tempos_voo_min[(i, j)] for i in aeroportos_malha for j in aeroportos_malha])\n", + "total_voos_realizados = sum([(D[(i, j)] + int(Y3[(i, j)].varValue)) for i in aeroportos_malha for j in aeroportos_malha])\n", + "tempo_solo_total = total_voos_realizados * TAT_min\n", "\n", - "print(f\"Tempo total de voo puro: {tempo_voo_puro:.2f} horas\")\n", - "print(f\"Tempo total gasto em solo (TAT): {tempo_solo_total:.2f} horas\")\n", - "print(f\"Tempo total operacional comprometido: {tempo_voo_puro + tempo_solo_total:.2f} horas\")\n", + "print(f\"Tempo total de voo puro: {tempo_voo_puro} min ({tempo_voo_puro/60:.2f} h)\")\n", + "print(f\"Tempo total gasto em solo (TAT): {tempo_solo_total} min ({tempo_solo_total/60:.2f} h)\")\n", + "print(f\"Tempo total operacional comprometido: {tempo_voo_puro + tempo_solo_total} min ({(tempo_voo_puro + tempo_solo_total)/60:.2f} h)\")\n", "if N3.varValue > 0:\n", - " print(f\"Taxa de ocupação da frota (Tempo Operacional / Tempo Disponível): {((tempo_voo_puro + tempo_solo_total) / (N3.varValue * 24)) * 100:.1f}%\\n\")\n", + " print(f\"Taxa de ocupação da frota (Tempo Operacional / Tempo Disponível): {((tempo_voo_puro + tempo_solo_total) / (N3.varValue * 1440)) * 100:.1f}%\\n\")\n", "\n", "# GERAR TABELA DE HORÁRIOS COM TABULATE E DATETIME\n", "voos_para_fazer3 = []\n", @@ -942,7 +942,7 @@ " D_copy3[(i, j)] -= 1\n", " else:\n", " tipo = 'Vazio (Reposicionamento)'\n", - " voos_para_fazer3.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo[(i, j)]})\n", + " voos_para_fazer3.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo_min[(i, j)]})\n", "\n", "tabela_horarios3 = []\n", "aeronave_id3 = 1\n", @@ -951,7 +951,7 @@ " voo_atual = voos_para_fazer3.pop(0)\n", " hora_atual = datetime.datetime(2025, 1, 1, 0, 0, 0)\n", " \n", - " tempo_delta = datetime.timedelta(hours=voo_atual['tempo'])\n", + " tempo_delta = datetime.timedelta(minutes=int(voo_atual['tempo']))\n", " hora_chegada = hora_atual + tempo_delta\n", " \n", " tabela_horarios3.append([\n", @@ -961,7 +961,7 @@ " voo_atual['tipo'],\n", " hora_atual.strftime('%H:%M'),\n", " hora_chegada.strftime('%H:%M'),\n", - " f\"{voo_atual['tempo']:.2f} h\"\n", + " f\"{voo_atual['tempo']} min\"\n", " ])\n", " \n", " # Próxima partida só depois do TAT de 40 min\n", @@ -972,12 +972,12 @@ " prox_voo = None\n", " for idx, v in enumerate(voos_para_fazer3):\n", " if v['origem'] == local_atual:\n", - " if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(hours=v['tempo'])).total_seconds() <= 24 * 3600:\n", + " if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(minutes=int(v['tempo']))).total_seconds() <= 1440 * 60:\n", " prox_voo = voos_para_fazer3.pop(idx)\n", " break\n", " \n", " if prox_voo:\n", - " tempo_delta = datetime.timedelta(hours=prox_voo['tempo'])\n", + " tempo_delta = datetime.timedelta(minutes=int(prox_voo['tempo']))\n", " hora_chegada = hora_atual + tempo_delta\n", " tabela_horarios3.append([\n", " f\"Aeronave {aeronave_id3}\",\n", @@ -986,7 +986,7 @@ " prox_voo['tipo'],\n", " hora_atual.strftime('%H:%M'),\n", " hora_chegada.strftime('%H:%M'),\n", - " f\"{prox_voo['tempo']:.2f} h\"\n", + " f\"{prox_voo['tempo']} min\"\n", " ])\n", " hora_atual = hora_chegada + datetime.timedelta(minutes=40)\n", " local_atual = prox_voo['destino']\n",