From 6cadb51646496b46e99687add72543143b2cb703 Mon Sep 17 00:00:00 2001 From: Luciano Silva do Lago Date: Mon, 8 Jun 2026 15:32:48 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20Adiciona=20modelos=2010,=2011=20e=2012?= =?UTF-8?q?=20com=20an=C3=A1lise=20semanal=20para=20mix=20de=20C-97=20e=20?= =?UTF-8?q?C-99A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modelos.ipynb | 653 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 452 insertions(+), 201 deletions(-) diff --git a/modelos.ipynb b/modelos.ipynb index 566c177..68b1dbd 100644 --- a/modelos.ipynb +++ b/modelos.ipynb @@ -5,17 +5,17 @@ "id": "3da11ed7", "metadata": {}, "source": [ - "# Alocação de Frotas (Fleet Scheduling) - Aeronaves C-97\n", + "# Aloca\u00e7\u00e3o de Frotas (Fleet Scheduling) - Aeronaves C-97\n", "\n", - "Este projeto visa realizar a alocação de frota (Fleet Assignment) baseada nos princípios do livro referenciado *Airline Operations and Scheduling*.\n", + "Este projeto visa realizar a aloca\u00e7\u00e3o de frota (Fleet Assignment) baseada nos princ\u00edpios do livro referenciado *Airline Operations and Scheduling*.\n", "Diferentemente do modelo anterior que utilizava aeronaves de carga, este foca exclusivamente na aeronave de passageiros **C-97**.\n", "\n", "## Objetivos:\n", - "1. Remover cálculos de aeronaves de carga.\n", - "2. Analisar as demandas diárias para a aeronave C-97.\n", - "3. Identificar os esquadrões detentores dessas aeronaves e a quantidade de aeronaves (matrículas únicas).\n", - "4. Calcular os 5 trechos mais relevantes do ano de 2025 utilizando estatística e gráficos.\n", - "5. Realizar o Fleet Assignment para os 5 trechos, visando minimizar o custo total da operação (Modelo 1 proposto).\n" + "1. Remover c\u00e1lculos de aeronaves de carga.\n", + "2. Analisar as demandas di\u00e1rias para a aeronave C-97.\n", + "3. Identificar os esquadr\u00f5es detentores dessas aeronaves e a quantidade de aeronaves (matr\u00edculas \u00fanicas).\n", + "4. Calcular os 5 trechos mais relevantes do ano de 2025 utilizando estat\u00edstica e gr\u00e1ficos.\n", + "5. Realizar o Fleet Assignment para os 5 trechos, visando minimizar o custo total da opera\u00e7\u00e3o (Modelo 1 proposto).\n" ] }, { @@ -32,7 +32,7 @@ }, "outputs": [], "source": [ - "# Importação de bibliotecas\n", + "# Importa\u00e7\u00e3o de bibliotecas\n", "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", @@ -42,13 +42,13 @@ "import math\n", "import networkx as nx\n", "\n", - "# Configuração de estilo para os gráficos\n", + "# Configura\u00e7\u00e3o de estilo para os gr\u00e1ficos\n", "sns.set_theme(style=\"whitegrid\")\n", "\n", "# Constantes globais\n", "CSV_FILEPATH = \"SISCO_AEREA_01-01-2025_A_31-12-2025_1911251714Z.csv\"\n", "JSON_AEROPORTOS = \"airports.json\"\n", - "CAPACIDADE_C97 = 30 # Capacidade média de passageiros do C-97 (Brasília)\n" + "CAPACIDADE_C97 = 30 # Capacidade m\u00e9dia de passageiros do C-97 (Bras\u00edlia)\n" ] }, { @@ -78,20 +78,20 @@ "df = pd.read_csv(CSV_FILEPATH, low_memory=False)\n", "df_aeroportos = pd.read_json(JSON_AEROPORTOS, orient='index')\n", "\n", - "# Tratamento básico de valores nulos expressos como 'NIL'\n", + "# Tratamento b\u00e1sico de valores nulos expressos como 'NIL'\n", "df.replace('NIL', 0, inplace=True)\n", "\n", - "# Filtro para a aeronave C-97 (Passageiros) e remoção de registros de carga\n", + "# Filtro para a aeronave C-97 (Passageiros) e remo\u00e7\u00e3o de registros de carga\n", "df_c97 = df[df['Modelo'] == 'C-97'].copy()\n", "\n", "# Filtrando decolagens e pousos conhecidos\n", "df_c97 = df_c97[df_c97['Localidade Decolagem'] != 0]\n", "df_c97 = df_c97[df_c97['Localidade Pouso'] != 0]\n", "\n", - "# Criação da coluna Trecho\n", + "# Cria\u00e7\u00e3o da coluna Trecho\n", "df_c97['Trecho'] = df_c97['Localidade Decolagem'] + '-' + df_c97['Localidade Pouso']\n", "\n", - "# Conversão da coluna PAX para numérico\n", + "# Convers\u00e3o da coluna PAX para num\u00e9rico\n", "df_c97['PAX'] = pd.to_numeric(df_c97['PAX']).fillna(0)\n", "\n", "# Tratamento de datas\n", @@ -104,8 +104,8 @@ "id": "c8fb80a2", "metadata": {}, "source": [ - "## 2. Análise dos Esquadrões e Matrículas (Aeronaves)\n", - "Identificaremos quais esquadrões operam o C-97 e qual a disponibilidade de aeronaves (número de matrículas únicas), pois isso definirá as restrições de frota.\n" + "## 2. An\u00e1lise dos Esquadr\u00f5es e Matr\u00edculas (Aeronaves)\n", + "Identificaremos quais esquadr\u00f5es operam o C-97 e qual a disponibilidade de aeronaves (n\u00famero de matr\u00edculas \u00fanicas), pois isso definir\u00e1 as restri\u00e7\u00f5es de frota.\n" ] }, { @@ -125,10 +125,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Esquadrões que operam o C-97: ETA1, ETA3, ETA5, ETA6, ETA7, ETA2, PAMA SP\n", - "Total de matrículas únicas (aeronaves disponíveis): 14\n", + "Esquadr\u00f5es que operam o C-97: ETA1, ETA3, ETA5, ETA6, ETA7, ETA2, PAMA SP\n", + "Total de matr\u00edculas \u00fanicas (aeronaves dispon\u00edveis): 14\n", "\n", - "Aeronaves por esquadrão:\n", + "Aeronaves por esquadr\u00e3o:\n", " - ETA1: 1 aeronaves\n", " - ETA2: 1 aeronaves\n", " - ETA3: 3 aeronaves\n", @@ -137,7 +137,7 @@ " - ETA7: 3 aeronaves\n", " - PAMA SP: 1 aeronaves\n", "\n", - "Bases inferidas por esquadrão (pela moda de decolagens):\n", + "Bases inferidas por esquadr\u00e3o (pela moda de decolagens):\n", " - ETA1: SBBE\n", " - ETA2: SBNT\n", " - ETA3: SBGL\n", @@ -149,27 +149,27 @@ } ], "source": [ - "# Obtendo esquadrões e matrículas únicas\n", + "# Obtendo esquadr\u00f5es e matr\u00edculas \u00fanicas\n", "esquadroes = df_c97['Emissor'].unique()\n", - "matriculas_unicas = df_c97['Matrícula'].unique()\n", + "matriculas_unicas = df_c97['Matr\u00edcula'].unique()\n", "total_aeronaves = len(matriculas_unicas)\n", "\n", - "print(f\"Esquadrões que operam o C-97: {', '.join(esquadroes)}\")\n", - "print(f\"Total de matrículas únicas (aeronaves disponíveis): {total_aeronaves}\")\n", + "print(f\"Esquadr\u00f5es que operam o C-97: {', '.join(esquadroes)}\")\n", + "print(f\"Total de matr\u00edculas \u00fanicas (aeronaves dispon\u00edveis): {total_aeronaves}\")\n", "\n", - "# Aeronaves por esquadrão\n", - "aeronaves_por_esquadrao = df_c97.groupby('Emissor')['Matrícula'].nunique().to_dict()\n", - "print(\"\\nAeronaves por esquadrão:\")\n", + "# Aeronaves por esquadr\u00e3o\n", + "aeronaves_por_esquadrao = df_c97.groupby('Emissor')['Matr\u00edcula'].nunique().to_dict()\n", + "print(\"\\nAeronaves por esquadr\u00e3o:\")\n", "for esq, qtd in aeronaves_por_esquadrao.items():\n", " print(f\" - {esq}: {qtd} aeronaves\")\n", " \n", - "# Identificar a base principal de cada esquadrão (aeroporto com mais decolagens)\n", + "# Identificar a base principal de cada esquadr\u00e3o (aeroporto com mais decolagens)\n", "bases_esquadroes = {}\n", "for esq in aeronaves_por_esquadrao.keys():\n", " base = df_c97[df_c97['Emissor'] == esq]['Localidade Decolagem'].mode()[0]\n", " bases_esquadroes[esq] = base\n", "\n", - "print(\"\\nBases inferidas por esquadrão (pela moda de decolagens):\")\n", + "print(\"\\nBases inferidas por esquadr\u00e3o (pela moda de decolagens):\")\n", "for esq, base in bases_esquadroes.items():\n", " print(f\" - {esq}: {base}\")\n" ] @@ -179,9 +179,9 @@ "id": "f845c354", "metadata": {}, "source": [ - "## 3. Análise Estatística das Demandas Diárias\n", - "Para definir os trechos mais relevantes do ano, calcularemos a demanda de passageiros (PAX) agregada diariamente. Em seguida, extraímos a **média de passageiros diários** e o **desvio padrão** para cada trecho. A relevância será determinada pela maior média de demanda diária de PAX.\n", - "Começaremos focando em 5 trechos (alvo variável).\n" + "## 3. An\u00e1lise Estat\u00edstica das Demandas Di\u00e1rias\n", + "Para definir os trechos mais relevantes do ano, calcularemos a demanda de passageiros (PAX) agregada diariamente. Em seguida, extra\u00edmos a **m\u00e9dia de passageiros di\u00e1rios** e o **desvio padr\u00e3o** para cada trecho. A relev\u00e2ncia ser\u00e1 determinada pela maior m\u00e9dia de demanda di\u00e1ria de PAX.\n", + "Come\u00e7aremos focando em 5 trechos (alvo vari\u00e1vel).\n" ] }, { @@ -373,7 +373,7 @@ } ], "source": [ - "# Demanda diária por trecho\n", + "# Demanda di\u00e1ria por trecho\n", "demanda_diaria = df_c97.groupby(['Trecho', 'Data Apenas'])['PAX'].sum().reset_index()\n", "\n", "# Criar um range de datas para todo o ano de 2025 para contabilizar os dias sem voo\n", @@ -386,7 +386,7 @@ "# Mesclar com os dados reais e preencher NaN com 0\n", "demanda_diaria_completa = pd.merge(demanda_diaria_completa, demanda_diaria, on=['Trecho', 'Data Apenas'], how='left').fillna({'PAX': 0})\n", "\n", - "# Estatísticas (Média e Desvio Padrão) por trecho\n", + "# Estat\u00edsticas (M\u00e9dia e Desvio Padr\u00e3o) por trecho\n", "estatisticas_trechos = demanda_diaria_completa.groupby('Trecho').agg(\n", " Media_PAX_Diario=('PAX', 'mean'),\n", " Desvio_Padrao_PAX=('PAX', 'std'),\n", @@ -394,28 +394,28 @@ " Dias_Operados=('PAX', lambda x: (x > 0).sum())\n", ").reset_index()\n", "\n", - "# Média PAX por dias operados (usado para o Fleet Assignment Model)\n", + "# M\u00e9dia PAX por dias operados (usado para o Fleet Assignment Model)\n", "estatisticas_trechos['Media_PAX_Dias_Operados'] = estatisticas_trechos.apply(\n", " lambda row: row['Total_PAX'] / row['Dias_Operados'] if row['Dias_Operados'] > 0 else 0, axis=1\n", ")\n", "\n", - "# Nova métrica solicitada: PAX * Dias_Operados\n", + "# Nova m\u00e9trica solicitada: PAX * Dias_Operados\n", "estatisticas_trechos['Score_Relevancia'] = estatisticas_trechos['Total_PAX'] * estatisticas_trechos['Dias_Operados']\n", "\n", - "# Ordenar pelas rotas com maior métrica (PAX * Dias_Operados)\n", + "# Ordenar pelas rotas com maior m\u00e9trica (PAX * Dias_Operados)\n", "estatisticas_trechos = estatisticas_trechos.sort_values(by='Score_Relevancia', ascending=False)\n", "\n", - "# Definir número de trechos alvo\n", + "# Definir n\u00famero de trechos alvo\n", "NUM_TRECHOS = 10\n", "top_trechos = estatisticas_trechos.head(NUM_TRECHOS)\n", "\n", "display(top_trechos)\n", "\n", - "# Gráfico\n", + "# Gr\u00e1fico\n", "plt.figure(figsize=(10, 6))\n", "sns.barplot(data=top_trechos, x='Trecho', y='Score_Relevancia', hue='Trecho', palette='viridis', legend=False)\n", "plt.title(f'Top {NUM_TRECHOS} Trechos Mais Relevantes (PAX * Dias Operados)')\n", - "plt.ylabel('Score de Relevância (PAX * Dias Operados)')\n", + "plt.ylabel('Score de Relev\u00e2ncia (PAX * Dias Operados)')\n", "plt.xlabel('Trecho (Origem - Destino)')\n", "plt.xticks(rotation=45)\n", "plt.tight_layout()\n", @@ -428,7 +428,7 @@ "metadata": {}, "source": [ "## Diagrama de Rede dos Trechos\n", - "Para melhor visualização dos trechos mais relevantes, abaixo temos um diagrama de rede conectando as origens aos destinos.\n" + "Para melhor visualiza\u00e7\u00e3o dos trechos mais relevantes, abaixo temos um diagrama de rede conectando as origens aos destinos.\n" ] }, { @@ -456,25 +456,25 @@ } ], "source": [ - "# Criação do grafo direcionado\n", + "# Cria\u00e7\u00e3o do grafo direcionado\n", "G = nx.DiGraph()\n", "\n", - "# Adiciona arestas com peso baseado no Score de Relevância\n", + "# Adiciona arestas com peso baseado no Score de Relev\u00e2ncia\n", "for _, row in top_trechos.iterrows():\n", " origem, destino = row['Trecho'].split('-')\n", " peso = row['Score_Relevancia']\n", " G.add_edge(origem, destino, weight=peso)\n", "\n", - "# Configuração do layout do grafo - k maior afasta mais os nós\n", + "# Configura\u00e7\u00e3o do layout do grafo - k maior afasta mais os n\u00f3s\n", "pos = nx.spring_layout(G, k=2.5, iterations=100, seed=42)\n", "\n", "plt.figure(figsize=(12, 8))\n", - "# Define o tamanho do nó\n", + "# Define o tamanho do n\u00f3\n", "tamanho_no = 1200\n", "\n", - "# Desenha os nós\n", + "# Desenha os n\u00f3s\n", "nx.draw_networkx_nodes(G, pos, node_size=tamanho_no, node_color='skyblue', edgecolors='black')\n", - "# Desenha as arestas, informando o node_size para que a seta não fique escondida sob o nó\n", + "# Desenha as arestas, informando o node_size para que a seta n\u00e3o fique escondida sob o n\u00f3\n", "nx.draw_networkx_edges(\n", " G, pos, \n", " edgelist=G.edges(), \n", @@ -486,7 +486,7 @@ " node_size=tamanho_no, \n", " connectionstyle='arc3,rad=0.15'\n", ")\n", - "# Desenha os rótulos (nomes dos aeroportos)\n", + "# Desenha os r\u00f3tulos (nomes dos aeroportos)\n", "nx.draw_networkx_labels(G, pos, font_size=12, font_family='sans-serif', font_weight='bold')\n", "\n", "plt.title('Diagrama de Rede - Top Trechos Relevantes', fontsize=14)\n", @@ -500,15 +500,15 @@ "id": "6417a6c5", "metadata": {}, "source": [ - "## 4. Fleet Assignment Model (FAM) - Nível Brasil\n", - "Conforme o MODELO 1 solicitado (aeronaves podem pernoitar em qualquer hub), não há restrição de base ou esquadrão específico. Toda a frota de C-97 opera a nível nacional.\n", + "## 4. Fleet Assignment Model (FAM) - N\u00edvel Brasil\n", + "Conforme o MODELO 1 solicitado (aeronaves podem pernoitar em qualquer hub), n\u00e3o h\u00e1 restri\u00e7\u00e3o de base ou esquadr\u00e3o espec\u00edfico. Toda a frota de C-97 opera a n\u00edvel nacional.\n", "\n", - "**Modelo Matemático:**\n", - "- **Variáveis de decisão:** $X_r$ (Quantidade de voos alocados no trecho $r$)\n", - "- **Função Objetivo:** Minimizar a distância total da operação (Distância do trecho $\\times$ voos no trecho). Como as aeronaves não precisam retornar a uma base fixa, desconsideramos voos de posicionamento.\n", - "- **Restrições:**\n", - " 1. O número de voos alocados para o trecho deve suprir a demanda diária operada daquela rota.\n", - " 2. O total de voos atribuídos no dia não pode exceder o total da frota de C-97 disponível (14 aeronaves).\n" + "**Modelo Matem\u00e1tico:**\n", + "- **Vari\u00e1veis de decis\u00e3o:** $X_r$ (Quantidade de voos alocados no trecho $r$)\n", + "- **Fun\u00e7\u00e3o Objetivo:** Minimizar a dist\u00e2ncia total da opera\u00e7\u00e3o (Dist\u00e2ncia do trecho $\\times$ voos no trecho). Como as aeronaves n\u00e3o precisam retornar a uma base fixa, desconsideramos voos de posicionamento.\n", + "- **Restri\u00e7\u00f5es:**\n", + " 1. O n\u00famero de voos alocados para o trecho deve suprir a demanda di\u00e1ria operada daquela rota.\n", + " 2. O total de voos atribu\u00eddos no dia n\u00e3o pode exceder o total da frota de C-97 dispon\u00edvel (14 aeronaves).\n" ] }, { @@ -528,39 +528,39 @@ "name": "stdout", "output_type": "stream", "text": [ - "== RESULTADO DO FLEET ASSIGNMENT (NÍVEL BRASIL) ==\n", + "== RESULTADO DO FLEET ASSIGNMENT (N\u00cdVEL BRASIL) ==\n", "Status: Optimal\n", - "Distância Total da Operação: 5703.02 km\n", + "Dist\u00e2ncia Total da Opera\u00e7\u00e3o: 5703.02 km\n", "\n", - "Alocações (Voos Diários Globais):\n", - "Trecho: SBGL-SBBR | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", - "Trecho: SBBR-SBGL | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", - "Trecho: SBBR-SBGR | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", - "Trecho: SBGR-SBBR | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", - "Trecho: SBGL-SBGR | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", - "Trecho: SBSJ-SBBR | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", - "Trecho: SBGR-SBGL | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", - "Trecho: SBSJ-SBGL | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", - "Trecho: SBGL-SBSJ | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", - "Trecho: SBBR-SBAN | Demanda Média PAX: 30 | Alocados: 1 voo(s)\n", + "Aloca\u00e7\u00f5es (Voos Di\u00e1rios Globais):\n", + "Trecho: SBGL-SBBR | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", + "Trecho: SBBR-SBGL | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", + "Trecho: SBBR-SBGR | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", + "Trecho: SBGR-SBBR | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", + "Trecho: SBGL-SBGR | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", + "Trecho: SBSJ-SBBR | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", + "Trecho: SBGR-SBGL | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", + "Trecho: SBSJ-SBGL | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", + "Trecho: SBGL-SBSJ | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", + "Trecho: SBBR-SBAN | Demanda M\u00e9dia PAX: 30 | Alocados: 1 voo(s)\n", "\n", - "Total de voos alocados na malha diária: 10 de 14 aeronaves disponíveis.\n" + "Total de voos alocados na malha di\u00e1ria: 10 de 14 aeronaves dispon\u00edveis.\n" ] } ], "source": [ - "# Preparação dos dados para o modelo\n", + "# Prepara\u00e7\u00e3o dos dados para o modelo\n", "rotas_lista = top_trechos['Trecho'].tolist()\n", "\n", - "# Dicionário de distâncias apenas dos trechos diretos\n", + "# Dicion\u00e1rio de dist\u00e2ncias apenas dos trechos diretos\n", "def calc_distancia(icao1, icao2):\n", " try:\n", " lat1, lon1, alt1 = df_aeroportos.loc[icao1, ['lat', 'lon', 'elevation']]\n", " lat2, lon2, alt2 = df_aeroportos.loc[icao2, ['lat', 'lon', 'elevation']]\n", - " # Retorna a distância oblíqua em km\n", + " # Retorna a dist\u00e2ncia obl\u00edqua em km\n", " return pm.geodetic2aer(lat1, lon1, alt1, lat2, lon2, alt2)[2] / 1000.0\n", " except KeyError:\n", - " return 9999.0 # Penalidade caso aeroporto não exista no json\n", + " return 9999.0 # Penalidade caso aeroporto n\u00e3o exista no json\n", "\n", "distancias_voo = {}\n", "for r in rotas_lista:\n", @@ -576,35 +576,35 @@ "# MODELAGEM COM PULP\n", "prob = pulp.LpProblem(\"Fleet_Assignment_C97_Brasil\", pulp.LpMinimize)\n", "\n", - "# Variáveis (número de voos em cada rota)\n", + "# Vari\u00e1veis (n\u00famero de voos em cada rota)\n", "X = pulp.LpVariable.dicts(\"X\", rotas_lista, lowBound=0, cat='Integer')\n", "\n", - "# Função Objetivo: Minimizar distância voada nos trechos\n", + "# Fun\u00e7\u00e3o Objetivo: Minimizar dist\u00e2ncia voada nos trechos\n", "prob += pulp.lpSum([distancias_voo[r] * X[r] for r in rotas_lista])\n", "\n", - "# Restrição 1: Suprir a demanda\n", + "# Restri\u00e7\u00e3o 1: Suprir a demanda\n", "for r in rotas_lista:\n", " prob += X[r] >= voos_requeridos[r]\n", "\n", - "# Restrição 2: Limite global de frota (nível Brasil)\n", + "# Restri\u00e7\u00e3o 2: Limite global de frota (n\u00edvel Brasil)\n", "prob += pulp.lpSum([X[r] for r in rotas_lista]) <= total_aeronaves\n", "\n", - "# Solução\n", + "# Solu\u00e7\u00e3o\n", "prob.solve(pulp.PULP_CBC_CMD(msg=False))\n", "\n", - "# Exibição dos resultados\n", - "print(\"== RESULTADO DO FLEET ASSIGNMENT (NÍVEL BRASIL) ==\")\n", + "# Exibi\u00e7\u00e3o dos resultados\n", + "print(\"== RESULTADO DO FLEET ASSIGNMENT (N\u00cdVEL BRASIL) ==\")\n", "print(f\"Status: {pulp.LpStatus[prob.status]}\")\n", - "print(f\"Distância Total da Operação: {pulp.value(prob.objective):.2f} km\\n\")\n", + "print(f\"Dist\u00e2ncia Total da Opera\u00e7\u00e3o: {pulp.value(prob.objective):.2f} km\\n\")\n", "\n", - "print(\"Alocações (Voos Diários Globais):\")\n", + "print(\"Aloca\u00e7\u00f5es (Voos Di\u00e1rios Globais):\")\n", "voos_totais = 0\n", "for r in rotas_lista:\n", " qtd = int(X[r].varValue)\n", " voos_totais += qtd\n", - " print(f\"Trecho: {r:10} | Demanda Média PAX: {voos_requeridos[r]*CAPACIDADE_C97:3.0f} | Alocados: {qtd} voo(s)\")\n", + " print(f\"Trecho: {r:10} | Demanda M\u00e9dia PAX: {voos_requeridos[r]*CAPACIDADE_C97:3.0f} | Alocados: {qtd} voo(s)\")\n", "\n", - "print(f\"\\nTotal de voos alocados na malha diária: {voos_totais} de {total_aeronaves} aeronaves disponíveis.\")\n" + "print(f\"\\nTotal de voos alocados na malha di\u00e1ria: {voos_totais} de {total_aeronaves} aeronaves dispon\u00edveis.\")\n" ] }, { @@ -612,12 +612,12 @@ "id": "bca983df", "metadata": {}, "source": [ - "## 5. Modelo 2: Minimização de Aeronaves\n", - "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).\n", - "Considerações solicitadas:\n", - "- O tempo de voo é estimado usando a velocidade média real do C-97 em 2025.\n", - "- As aeronaves podem voar até 24 horas por dia (não consideraremos o tempo de solo no chão nesta versão).\n", - "- Se houver desbalanceamento de malha (mais voos saindo de A do que chegando em A), o modelo criará voos de reposicionamento vazios.\n" + "## 5. Modelo 2: Minimiza\u00e7\u00e3o de Aeronaves\n", + "Neste modelo, o objetivo \u00e9 encontrar o **n\u00famero m\u00ednimo absoluto de aeronaves** necess\u00e1rio para operar a malha di\u00e1ria estabelecida (os top trechos).\n", + "Considera\u00e7\u00f5es solicitadas:\n", + "- O tempo de voo \u00e9 estimado usando a velocidade m\u00e9dia real do C-97 em 2025.\n", + "- As aeronaves podem voar at\u00e9 24 horas por dia (n\u00e3o consideraremos o tempo de solo no ch\u00e3o nesta vers\u00e3o).\n", + "- Se houver desbalanceamento de malha (mais voos saindo de A do que chegando em A), o modelo criar\u00e1 voos de reposicionamento vazios.\n" ] }, { @@ -637,19 +637,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "Velocidade média calculada para o C-97: 391.76 km/h\n", + "Velocidade m\u00e9dia calculada para o C-97: 391.76 km/h\n", "\n", "== 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): 1004 min (16.73 h)\n", - "Taxa de ocupação da frota (Tempo Voo / Tempo Disponível): 69.7%\n", + "N\u00famero m\u00ednimo absoluto de aeronaves C-97 necess\u00e1rias: 1\n", + "Tempo total de voo di\u00e1rio (incluindo reposicionamento vazio): 1004 min (16.73 h)\n", + "Taxa de ocupa\u00e7\u00e3o da frota (Tempo Voo / Tempo Dispon\u00edvel): 69.7%\n", "\n", - "Voos de Reposicionamento Vazio Gerados (Y) para fechar o ciclo diário:\n", + "Voos de Reposicionamento Vazio Gerados (Y) para fechar o ciclo di\u00e1rio:\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", + "== QUADRO DE HOR\u00c1RIOS DI\u00c1RIOS ==\n", + "| Aeronave | Origem | Destino | Tipo | Partida | Chegada | Dura\u00e7\u00e3o |\n", "|------------|----------|-----------|--------------------------|-----------|-----------|-----------|\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", @@ -666,7 +666,7 @@ } ], "source": [ - "# 1. Calcular a velocidade média do C-97 em 2025\n", + "# 1. Calcular a velocidade m\u00e9dia do C-97 em 2025\n", "# Converte a string 'HH:MM:SS' para timedelta e extrai horas\n", "df_c97_spd = df_c97.copy()\n", "df_c97_spd['Tempo_Horas'] = pd.to_timedelta(df_c97_spd['Tempo de Voo']).dt.total_seconds() / 3600\n", @@ -681,13 +681,13 @@ "\n", "df_c97_spd['Dist_km'] = df_c97_spd.apply(get_dist_row, axis=1)\n", "\n", - "# Filtra voos válidos para calcular a velocidade média\n", + "# Filtra voos v\u00e1lidos para calcular a velocidade m\u00e9dia\n", "df_valid = df_c97_spd[(df_c97_spd['Tempo_Horas'] > 0) & (df_c97_spd['Dist_km'] > 0)].copy()\n", "df_valid['Velocidade'] = df_valid['Dist_km'] / df_valid['Tempo_Horas']\n", "vel_media_c97 = df_valid['Velocidade'].mean()\n", - "print(f\"Velocidade média calculada para o C-97: {vel_media_c97:.2f} km/h\")\n", + "print(f\"Velocidade m\u00e9dia calculada para o C-97: {vel_media_c97:.2f} km/h\")\n", "\n", - "# 2. Configurar os nós (aeroportos) e demandas da malha das rotas top\n", + "# 2. Configurar os n\u00f3s (aeroportos) e demandas da malha das rotas top\n", "aeroportos_malha = set()\n", "for r in rotas_lista:\n", " o, d = r.split('-')\n", @@ -695,7 +695,7 @@ " aeroportos_malha.add(d)\n", "aeroportos_malha = list(aeroportos_malha)\n", "\n", - "# Matriz de tempo de voo entre todos os nós da malha em MINUTOS (inteiro)\n", + "# Matriz de tempo de voo entre todos os n\u00f3s da malha em MINUTOS (inteiro)\n", "tempos_voo_min = {}\n", "for o in aeroportos_malha:\n", " for d in aeroportos_malha:\n", @@ -711,42 +711,42 @@ " o, d = r.split('-')\n", " D[(o, d)] = voos_requeridos[r]\n", "\n", - "# 3. Modelagem Matemática (Pulp)\n", + "# 3. Modelagem Matem\u00e1tica (Pulp)\n", "prob2 = pulp.LpProblem(\"Minimizar_Aeronaves\", pulp.LpMinimize)\n", "\n", - "# Variáveis de reposicionamento vazio Y_{i,j}\n", + "# Vari\u00e1veis de reposicionamento vazio Y_{i,j}\n", "Y = pulp.LpVariable.dicts(\"Y\", [(o, d) for o in aeroportos_malha for d in aeroportos_malha], lowBound=0, cat='Integer')\n", "\n", - "# Variável de número de aeronaves N\n", + "# Vari\u00e1vel de n\u00famero de aeronaves N\n", "N = pulp.LpVariable(\"N\", lowBound=0, cat='Integer')\n", "\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", + "# Fun\u00e7\u00e3o Objetivo: Minimizar N\n", + "# Para desempatar solu\u00e7\u00f5es com mesmo N, damos um pequeno peso pro tempo de reposicionamento vazio (para pegar o roteamento mais curto tamb\u00e9m)\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", + "# Restri\u00e7\u00e3o de conserva\u00e7\u00e3o de fluxo para cada aeroporto (Chegadas == Partidas)\n", "for no in aeroportos_malha:\n", " chegadas = pulp.lpSum([D[(i, no)] + Y[(i, no)] for i in aeroportos_malha])\n", " 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 * 1440 min)\n", + "# Restri\u00e7\u00e3o de tempo (O tempo total de voo da malha n\u00e3o 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", + "# Solu\u00e7\u00e3o\n", "prob2.solve(pulp.PULP_CBC_CMD(msg=False))\n", "\n", "print(\"\\n== RESULTADO DO MODELO 2 ==\")\n", "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", + "print(f\"N\u00famero m\u00ednimo absoluto de aeronaves C-97 necess\u00e1rias: {int(N.varValue)}\")\n", "\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", + "print(f\"Tempo total de voo di\u00e1rio (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 * 1440)) * 100:.1f}%\\n\")\n", + " print(f\"Taxa de ocupa\u00e7\u00e3o da frota (Tempo Voo / Tempo Dispon\u00edvel): {(tempo_gasto / (N.varValue * 1440)) * 100:.1f}%\\n\")\n", "\n", - "print(\"Voos de Reposicionamento Vazio Gerados (Y) para fechar o ciclo diário:\")\n", + "print(\"Voos de Reposicionamento Vazio Gerados (Y) para fechar o ciclo di\u00e1rio:\")\n", "gerou_vazio = False\n", "for i in aeroportos_malha:\n", " for j in aeroportos_malha:\n", @@ -754,16 +754,16 @@ " 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", + " print(\" Nenhum voo de reposicionamento vazio foi necess\u00e1rio! A malha j\u00e1 \u00e9 circular/balanceada.\")\n", "\n", - "# GERAR TABELA DE HORÁRIOS COM TABULATE E DATETIME\n", + "# GERAR TABELA DE HOR\u00c1RIOS COM TABULATE E DATETIME\n", "from tabulate import tabulate\n", "import datetime\n", "\n", "# 1. Construir a lista de todos os voos a serem realizados\n", "voos_para_fazer = []\n", "\n", - "# Cópia para saber quantos voos de passageiros faltam alocar\n", + "# C\u00f3pia para saber quantos voos de passageiros faltam alocar\n", "D_copy = {k: v for k, v in D.items()}\n", "\n", "for i in aeroportos_malha:\n", @@ -777,7 +777,7 @@ " tipo = 'Vazio (Reposicionamento)'\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", + "# 2. Simular a aloca\u00e7\u00e3o sequencial de hor\u00e1rios\n", "tabela_horarios = []\n", "aeronave_id = 1\n", "\n", @@ -801,7 +801,7 @@ " hora_atual = hora_chegada\n", " local_atual = voo_atual['destino']\n", " \n", - " # Continuar traçando o caminho para esta aeronave até que não caiba mais nas 24h\n", + " # Continuar tra\u00e7ando o caminho para esta aeronave at\u00e9 que n\u00e3o caiba mais nas 24h\n", " while True:\n", " prox_voo = None\n", " for idx, v in enumerate(voos_para_fazer):\n", @@ -829,8 +829,8 @@ " \n", " aeronave_id += 1\n", "\n", - "print(\"\\n== QUADRO DE HORÁRIOS DIÁRIOS ==\")\n", - "print(tabulate(tabela_horarios, headers=[\"Aeronave\", \"Origem\", \"Destino\", \"Tipo\", \"Partida\", \"Chegada\", \"Duração\"], tablefmt=\"github\"))\n" + "print(\"\\n== QUADRO DE HOR\u00c1RIOS DI\u00c1RIOS ==\")\n", + "print(tabulate(tabela_horarios, headers=[\"Aeronave\", \"Origem\", \"Destino\", \"Tipo\", \"Partida\", \"Chegada\", \"Dura\u00e7\u00e3o\"], tablefmt=\"github\"))\n" ] }, { @@ -838,11 +838,11 @@ "id": "b655f18a", "metadata": {}, "source": [ - "## 6. Modelo 3: Minimização de Aeronaves com TAT (Turnaround Time)\n", - "Nesta variação do Modelo 2, adicionamos uma restrição crítica da vida real: o Tempo de Solo ou **Turnaround Time (TAT)**.\n", - "Considerações:\n", - "- TAT estipulado: 40 minutos (0.66 horas) por cada voo (tempo necessário para desembarque, reabastecimento, verificações e embarque).\n", - "- O tempo total de dedicação de uma aeronave por ciclo passa a ser a soma do seu Tempo de Voo + 40 minutos de TAT no aeroporto de destino.\n" + "## 6. Modelo 3: Minimiza\u00e7\u00e3o de Aeronaves com TAT (Turnaround Time)\n", + "Nesta varia\u00e7\u00e3o do Modelo 2, adicionamos uma restri\u00e7\u00e3o cr\u00edtica da vida real: o Tempo de Solo ou **Turnaround Time (TAT)**.\n", + "Considera\u00e7\u00f5es:\n", + "- TAT estipulado: 40 minutos (0.66 horas) por cada voo (tempo necess\u00e1rio para desembarque, reabastecimento, verifica\u00e7\u00f5es e embarque).\n", + "- O tempo total de dedica\u00e7\u00e3o de uma aeronave por ciclo passa a ser a soma do seu Tempo de Voo + 40 minutos de TAT no aeroporto de destino.\n" ] }, { @@ -865,14 +865,14 @@ "\n", "== 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", + "N\u00famero m\u00ednimo absoluto de aeronaves C-97 necess\u00e1rias: 2\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", + "Taxa de ocupa\u00e7\u00e3o da frota (Tempo Operacional / Tempo Dispon\u00edvel): 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", + "== QUADRO DE HOR\u00c1RIOS DI\u00c1RIOS (COM 40 MIN TAT) ==\n", + "| Aeronave | Origem | Destino | Tipo | Partida | Chegada | Dura\u00e7\u00e3o Voo |\n", "|------------|----------|-----------|--------------------------|-----------|-----------|---------------|\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", @@ -889,35 +889,35 @@ } ], "source": [ - "# 3. Modelagem Matemática Modelo 3 (Pulp) com TAT\n", + "# 3. Modelagem Matem\u00e1tica Modelo 3 (Pulp) com TAT\n", "prob3 = pulp.LpProblem(\"Minimizar_Aeronaves_TAT\", pulp.LpMinimize)\n", "\n", - "# Variáveis\n", + "# Vari\u00e1veis\n", "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_min = 40\n", "\n", - "# Função Objetivo\n", + "# Fun\u00e7\u00e3o Objetivo\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", + "# Restri\u00e7\u00e3o de fluxo\n", "for no in aeroportos_malha:\n", " chegadas = pulp.lpSum([D[(i, no)] + Y3[(i, no)] for i in aeroportos_malha])\n", " partidas = pulp.lpSum([D[(no, j)] + Y3[(no, j)] for j in aeroportos_malha])\n", " prob3 += chegadas == partidas\n", "\n", - "# Restrição de tempo com TAT\n", + "# Restri\u00e7\u00e3o 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_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", + "# Solu\u00e7\u00e3o\n", "prob3.solve(pulp.PULP_CBC_CMD(msg=False))\n", "\n", "print(\"\\n== RESULTADO DO MODELO 3 (COM 40 MIN DE SOLO) ==\")\n", "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", + "print(f\"N\u00famero m\u00ednimo absoluto de aeronaves C-97 necess\u00e1rias: {int(N3.varValue)}\")\n", "\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", @@ -927,9 +927,9 @@ "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 * 1440)) * 100:.1f}%\\n\")\n", + " print(f\"Taxa de ocupa\u00e7\u00e3o da frota (Tempo Operacional / Tempo Dispon\u00edvel): {((tempo_voo_puro + tempo_solo_total) / (N3.varValue * 1440)) * 100:.1f}%\\n\")\n", "\n", - "# GERAR TABELA DE HORÁRIOS COM TABULATE E DATETIME\n", + "# GERAR TABELA DE HOR\u00c1RIOS COM TABULATE E DATETIME\n", "voos_para_fazer3 = []\n", "D_copy3 = {k: v for k, v in D.items()}\n", "\n", @@ -964,7 +964,7 @@ " f\"{voo_atual['tempo']} min\"\n", " ])\n", " \n", - " # Próxima partida só depois do TAT de 40 min\n", + " # Pr\u00f3xima partida s\u00f3 depois do TAT de 40 min\n", " hora_atual = hora_chegada + datetime.timedelta(minutes=40)\n", " local_atual = voo_atual['destino']\n", " \n", @@ -995,8 +995,8 @@ " \n", " aeronave_id3 += 1\n", "\n", - "print(\"== QUADRO DE HORÁRIOS DIÁRIOS (COM 40 MIN TAT) ==\")\n", - "print(tabulate(tabela_horarios3, headers=[\"Aeronave\", \"Origem\", \"Destino\", \"Tipo\", \"Partida\", \"Chegada\", \"Duração Voo\"], tablefmt=\"github\"))\n" + "print(\"== QUADRO DE HOR\u00c1RIOS DI\u00c1RIOS (COM 40 MIN TAT) ==\")\n", + "print(tabulate(tabela_horarios3, headers=[\"Aeronave\", \"Origem\", \"Destino\", \"Tipo\", \"Partida\", \"Chegada\", \"Dura\u00e7\u00e3o Voo\"], tablefmt=\"github\"))\n" ] }, { @@ -1004,8 +1004,8 @@ "id": "1763af22", "metadata": {}, "source": [ - "# Alocação de Frotas - Aeronaves C-99A\n", - "Agora replicaremos a mesma lógica e os mesmos modelos (1, 2 e 3) para a aeronave C-99A (que será nomeada como Modelos 4, 5 e 6).\n" + "# Aloca\u00e7\u00e3o de Frotas - Aeronaves C-99A\n", + "Agora replicaremos a mesma l\u00f3gica e os mesmos modelos (1, 2 e 3) para a aeronave C-99A (que ser\u00e1 nomeada como Modelos 4, 5 e 6).\n" ] }, { @@ -1025,7 +1025,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Total de matrículas únicas C-99A (aeronaves disponíveis): 5\n" + "Total de matr\u00edculas \u00fanicas C-99A (aeronaves dispon\u00edveis): 5\n" ] }, { @@ -1224,12 +1224,12 @@ "df_c99['Data Apenas'] = df_c99['Dep TimeStamp'].dt.date\n", "\n", "CAPACIDADE_C99 = 50\n", - "matriculas_unicas_c99 = df_c99['Matrícula'].unique()\n", + "matriculas_unicas_c99 = df_c99['Matr\u00edcula'].unique()\n", "total_aeronaves_c99 = len(matriculas_unicas_c99)\n", "\n", - "print(f\"Total de matrículas únicas C-99A (aeronaves disponíveis): {total_aeronaves_c99}\")\n", + "print(f\"Total de matr\u00edculas \u00fanicas C-99A (aeronaves dispon\u00edveis): {total_aeronaves_c99}\")\n", "\n", - "# Demanda diária\n", + "# Demanda di\u00e1ria\n", "demanda_diaria_c99 = df_c99.groupby(['Trecho', 'Data Apenas'])['PAX'].sum().reset_index()\n", "\n", "import itertools\n", @@ -1283,7 +1283,7 @@ "id": "742232e8", "metadata": {}, "source": [ - "## MODELO 4: Minimização de Distância (Custo)\n", + "## MODELO 4: Minimiza\u00e7\u00e3o de Dist\u00e2ncia (Custo)\n", "Equivalente ao Modelo 1, mas para C-99A.\n" ] }, @@ -1306,7 +1306,7 @@ "text": [ "== MODELO 4 (C-99A) ==\n", "Status: Infeasible\n", - "Distância Total: 6280.78 km\n", + "Dist\u00e2ncia Total: 6280.78 km\n", "\n", "Trecho: SBBR-SBGL | Alocados: 1 voo(s)\n", "Trecho: SBGL-SBBR | Alocados: 1 voo(s)\n", @@ -1322,7 +1322,7 @@ } ], "source": [ - "# Preparação Modelo 4\n", + "# Prepara\u00e7\u00e3o Modelo 4\n", "rotas_lista_c99 = top_trechos_c99['Trecho'].tolist()\n", "\n", "distancias_voo_c99 = {}\n", @@ -1349,7 +1349,7 @@ "\n", "print(\"== MODELO 4 (C-99A) ==\")\n", "print(f\"Status: {pulp.LpStatus[prob4.status]}\")\n", - "print(f\"Distância Total: {pulp.value(prob4.objective):.2f} km\\n\")\n", + "print(f\"Dist\u00e2ncia Total: {pulp.value(prob4.objective):.2f} km\\n\")\n", "for r in rotas_lista_c99:\n", " print(f\"Trecho: {r:10} | Alocados: {int(X_c99[r].varValue)} voo(s)\")\n" ] @@ -1359,7 +1359,7 @@ "id": "3f293c10", "metadata": {}, "source": [ - "## MODELO 5: Minimização de Frota C-99A (S/ TAT)\n", + "## MODELO 5: Minimiza\u00e7\u00e3o de Frota C-99A (S/ TAT)\n", "Equivalente ao Modelo 2.\n" ] }, @@ -1380,14 +1380,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Velocidade média C-99A: 536.65 km/h\n", + "Velocidade m\u00e9dia C-99A: 536.65 km/h\n", "\n", "== RESULTADO DO MODELO 5 (C-99A) ==\n", "Status: Optimal\n", - "Aeronaves mínimas: 1\n", + "Aeronaves m\u00ednimas: 1\n", "Tempo total de voo: 922 min (15.37 h)\n", "\n", - "| Aeronave | Origem | Destino | Tipo | Partida | Chegada | Duração |\n", + "| Aeronave | Origem | Destino | Tipo | Partida | Chegada | Dura\u00e7\u00e3o |\n", "|------------|----------|-----------|------------|-----------|-----------|-----------|\n", "| Aeronave 1 | SBSJ | SBGL | Vazio | 00:00 | 00:30 | 30 min |\n", "| Aeronave 1 | SBGL | SBSJ | Passageiro | 00:30 | 01:00 | 30 min |\n", @@ -1405,14 +1405,14 @@ } ], "source": [ - "# Velocidade Média C-99A\n", + "# Velocidade M\u00e9dia C-99A\n", "df_c99_spd = df_c99.copy()\n", "df_c99_spd['Tempo_Horas'] = pd.to_timedelta(df_c99_spd['Tempo de Voo']).dt.total_seconds() / 3600\n", "df_c99_spd['Dist_km'] = df_c99_spd.apply(get_dist_row, axis=1)\n", "df_valid_c99 = df_c99_spd[(df_c99_spd['Tempo_Horas'] > 0) & (df_c99_spd['Dist_km'] > 0)].copy()\n", "df_valid_c99['Velocidade'] = df_valid_c99['Dist_km'] / df_valid_c99['Tempo_Horas']\n", "vel_media_c99 = df_valid_c99['Velocidade'].mean()\n", - "print(f\"Velocidade média C-99A: {vel_media_c99:.2f} km/h\")\n", + "print(f\"Velocidade m\u00e9dia C-99A: {vel_media_c99:.2f} km/h\")\n", "\n", "aeroportos_malha_c99 = set()\n", "for r in rotas_lista_c99:\n", @@ -1453,7 +1453,7 @@ "\n", "print(\"\\n== RESULTADO DO MODELO 5 (C-99A) ==\")\n", "print(f\"Status: {pulp.LpStatus[prob5.status]}\")\n", - "print(f\"Aeronaves mínimas: {int(N5.varValue)}\")\n", + "print(f\"Aeronaves m\u00ednimas: {int(N5.varValue)}\")\n", "\n", "tempo_gasto_c99 = sum([(D_c99[(i, j)] + int(Y5[(i, j)].varValue)) * tempos_voo_min_c99[(i, j)] for i in aeroportos_malha_c99 for j in aeroportos_malha_c99])\n", "print(f\"Tempo total de voo: {int(tempo_gasto_c99)} min ({tempo_gasto_c99/60:.2f} h)\\n\")\n", @@ -1501,7 +1501,7 @@ " break\n", " aeronave_id += 1\n", "\n", - "print(tabulate(tabela_horarios_c99, headers=[\"Aeronave\", \"Origem\", \"Destino\", \"Tipo\", \"Partida\", \"Chegada\", \"Duração\"], tablefmt=\"github\"))\n" + "print(tabulate(tabela_horarios_c99, headers=[\"Aeronave\", \"Origem\", \"Destino\", \"Tipo\", \"Partida\", \"Chegada\", \"Dura\u00e7\u00e3o\"], tablefmt=\"github\"))\n" ] }, { @@ -1509,7 +1509,7 @@ "id": "e218deab", "metadata": {}, "source": [ - "## MODELO 6: Minimização de Frota C-99A (C/ 40 min TAT)\n", + "## MODELO 6: Minimiza\u00e7\u00e3o de Frota C-99A (C/ 40 min TAT)\n", "Equivalente ao Modelo 3.\n" ] }, @@ -1533,10 +1533,10 @@ "\n", "== RESULTADO DO MODELO 6 (C-99A C/ TAT 40 MIN) ==\n", "Status: Optimal\n", - "Aeronaves mínimas: 1\n", + "Aeronaves m\u00ednimas: 1\n", "Tempo total operacional comprometido: 1402 min (23.37 h)\n", "\n", - "| Aeronave | Origem | Destino | Tipo | Partida | Chegada | Duração |\n", + "| Aeronave | Origem | Destino | Tipo | Partida | Chegada | Dura\u00e7\u00e3o |\n", "|------------|----------|-----------|------------|-----------|-----------|-----------|\n", "| Aeronave 1 | SBSJ | SBGL | Vazio | 00:00 | 00:30 | 30 min |\n", "| Aeronave 1 | SBGL | SBSJ | Passageiro | 01:10 | 01:40 | 30 min |\n", @@ -1575,7 +1575,7 @@ "\n", "print(\"\\n== RESULTADO DO MODELO 6 (C-99A C/ TAT 40 MIN) ==\")\n", "print(f\"Status: {pulp.LpStatus[prob6.status]}\")\n", - "print(f\"Aeronaves mínimas: {int(N6.varValue)}\")\n", + "print(f\"Aeronaves m\u00ednimas: {int(N6.varValue)}\")\n", "\n", "tempo_voo_c99_6 = sum([(D_c99[(i, j)] + int(Y6[(i, j)].varValue)) * tempos_voo_min_c99[(i, j)] for i in aeroportos_malha_c99 for j in aeroportos_malha_c99])\n", "tot_voos_6 = sum([(D_c99[(i, j)] + int(Y6[(i, j)].varValue)) for i in aeroportos_malha_c99 for j in aeroportos_malha_c99])\n", @@ -1626,7 +1626,7 @@ " break\n", " aeronave_id += 1\n", "\n", - "print(tabulate(tabela_horarios_c99_6, headers=[\"Aeronave\", \"Origem\", \"Destino\", \"Tipo\", \"Partida\", \"Chegada\", \"Duração\"], tablefmt=\"github\"))\n" + "print(tabulate(tabela_horarios_c99_6, headers=[\"Aeronave\", \"Origem\", \"Destino\", \"Tipo\", \"Partida\", \"Chegada\", \"Dura\u00e7\u00e3o\"], tablefmt=\"github\"))\n" ] }, { @@ -1634,7 +1634,7 @@ "id": "4845bd74", "metadata": {}, "source": [ - "# Alocação de Frotas - Mix (C-97 + C-99A)\n", + "# Aloca\u00e7\u00e3o de Frotas - Mix (C-97 + C-99A)\n", "Agora vamos analisar a demanda de forma unificada (soma dos passageiros transportados por C-97 e C-99A) e colocar as duas frotas para trabalhar em conjunto nos Modelos 7, 8 e 9.\n" ] }, @@ -1824,7 +1824,7 @@ "df_mix['Dep TimeStamp'] = pd.to_datetime(df_mix['Data de Decolagem'] + ' ' + df_mix['Hora de Decolagem'], format='mixed', dayfirst=True)\n", "df_mix['Data Apenas'] = df_mix['Dep TimeStamp'].dt.date\n", "\n", - "# Demanda diária\n", + "# Demanda di\u00e1ria\n", "demanda_diaria_mix = df_mix.groupby(['Trecho', 'Data Apenas'])['PAX'].sum().reset_index()\n", "\n", "todos_trechos_mix = demanda_diaria_mix['Trecho'].unique()\n", @@ -1875,8 +1875,8 @@ "id": "40dc1e44", "metadata": {}, "source": [ - "## MODELO 7: Minimização de Distância (Custo) com Mix de Frota\n", - "A malha será atendida pelas duas aeronaves. O modelo decide quem alocar para cobrir a **demanda em passageiros** de cada rota, visando minimizar o número total de voos * distância (simplificação de custo de combustível), e não exceder o tamanho da frota física de cada modelo.\n" + "## MODELO 7: Minimiza\u00e7\u00e3o de Dist\u00e2ncia (Custo) com Mix de Frota\n", + "A malha ser\u00e1 atendida pelas duas aeronaves. O modelo decide quem alocar para cobrir a **demanda em passageiros** de cada rota, visando minimizar o n\u00famero total de voos * dist\u00e2ncia (simplifica\u00e7\u00e3o de custo de combust\u00edvel), e n\u00e3o exceder o tamanho da frota f\u00edsica de cada modelo.\n" ] }, { @@ -1898,7 +1898,7 @@ "text": [ "== MODELO 7 (MIX C-97 e C-99A) ==\n", "Status: Optimal\n", - "Distância Total da Operação Combinada: 7064.88 km\n", + "Dist\u00e2ncia Total da Opera\u00e7\u00e3o Combinada: 7064.88 km\n", "\n", "Trecho: SBGL-SBBR | Demanda PAX: 18 | Oferecido: 30 | Alocados -> C-97: 1, C-99A: 0\n", "Trecho: SBBR-SBGL | Demanda PAX: 17 | Oferecido: 30 | Alocados -> C-97: 1, C-99A: 0\n", @@ -1914,7 +1914,7 @@ } ], "source": [ - "# Preparação Modelo 7\n", + "# Prepara\u00e7\u00e3o Modelo 7\n", "rotas_lista_mix = top_trechos_mix['Trecho'].tolist()\n", "\n", "distancias_voo_mix = {}\n", @@ -1931,14 +1931,14 @@ "X_97 = pulp.LpVariable.dicts(\"X_97\", rotas_lista_mix, lowBound=0, cat='Integer')\n", "X_99 = pulp.LpVariable.dicts(\"X_99\", rotas_lista_mix, lowBound=0, cat='Integer')\n", "\n", - "# Minimizar distância combinada\n", + "# Minimizar dist\u00e2ncia combinada\n", "prob7 += pulp.lpSum([distancias_voo_mix[r] * (X_97[r] + X_99[r]) for r in rotas_lista_mix])\n", "\n", - "# Restrição de demanda (Em PAX)\n", + "# Restri\u00e7\u00e3o de demanda (Em PAX)\n", "for r in rotas_lista_mix:\n", " prob7 += (X_97[r] * CAPACIDADE_C97) + (X_99[r] * CAPACIDADE_C99) >= demanda_pax_mix[r]\n", "\n", - "# Restrição de uso da frota global para o dia (simplificado a 1 perna por aeronave aqui)\n", + "# Restri\u00e7\u00e3o de uso da frota global para o dia (simplificado a 1 perna por aeronave aqui)\n", "prob7 += pulp.lpSum([X_97[r] for r in rotas_lista_mix]) <= total_aeronaves\n", "prob7 += pulp.lpSum([X_99[r] for r in rotas_lista_mix]) <= total_aeronaves_c99\n", "\n", @@ -1946,7 +1946,7 @@ "\n", "print(\"== MODELO 7 (MIX C-97 e C-99A) ==\")\n", "print(f\"Status: {pulp.LpStatus[prob7.status]}\")\n", - "print(f\"Distância Total da Operação Combinada: {pulp.value(prob7.objective):.2f} km\\n\")\n", + "print(f\"Dist\u00e2ncia Total da Opera\u00e7\u00e3o Combinada: {pulp.value(prob7.objective):.2f} km\\n\")\n", "\n", "for r in rotas_lista_mix:\n", " c97_alloc = int(X_97[r].varValue)\n", @@ -1960,8 +1960,8 @@ "id": "c8c7d162", "metadata": {}, "source": [ - "## MODELO 8: Minimização Absoluta de Frota Mix (S/ TAT)\n", - "Diferente do Modelo 7, permitimos ciclos completos (vários voos por aeronave no dia) para minimizar a quantidade real de fuselagens exigidas para a operação. O modelo fará os reposicionamentos vazios necessários e alocará o voo ao tipo ideal.\n" + "## MODELO 8: Minimiza\u00e7\u00e3o Absoluta de Frota Mix (S/ TAT)\n", + "Diferente do Modelo 7, permitimos ciclos completos (v\u00e1rios voos por aeronave no dia) para minimizar a quantidade real de fuselagens exigidas para a opera\u00e7\u00e3o. O modelo far\u00e1 os reposicionamentos vazios necess\u00e1rios e alocar\u00e1 o voo ao tipo ideal.\n" ] }, { @@ -1984,11 +1984,11 @@ "\n", "== RESULTADO DO MODELO 8 (MIX SEM TAT) ==\n", "Status: Optimal\n", - "Aeronaves C-97 necessárias: 0\n", - "Aeronaves C-99A necessárias: 1\n", + "Aeronaves C-97 necess\u00e1rias: 0\n", + "Aeronaves C-99A necess\u00e1rias: 1\n", "Frota total mobilizada: 1 aeronaves\n", "\n", - "Alocações para satisfazer a Demanda:\n", + "Aloca\u00e7\u00f5es para satisfazer a Demanda:\n", " -> Trecho SBGL-SBBR : C-97 faz 0 voo(s) | C-99A faz 1 voo(s)\n", " -> Trecho SBBR-SBGL : C-97 faz 0 voo(s) | C-99A faz 1 voo(s)\n", " -> Trecho SBGR-SBBR : C-97 faz 0 voo(s) | C-99A faz 1 voo(s)\n", @@ -2003,7 +2003,7 @@ } ], "source": [ - "# Malha Aeroportuária\n", + "# Malha Aeroportu\u00e1ria\n", "aeroportos_malha_mix = set()\n", "for r in rotas_lista_mix:\n", " o, d = r.split('-')\n", @@ -2026,7 +2026,7 @@ "\n", "prob8 = pulp.LpProblem(\"Minimizar_Frota_Mix\", pulp.LpMinimize)\n", "\n", - "# D = Voos de Demanda (agora variável)\n", + "# D = Voos de Demanda (agora vari\u00e1vel)\n", "D_97 = pulp.LpVariable.dicts(\"D_97\", [(o, d) for o in aeroportos_malha_mix for d in aeroportos_malha_mix], lowBound=0, cat='Integer')\n", "D_99 = pulp.LpVariable.dicts(\"D_99\", [(o, d) for o in aeroportos_malha_mix for d in aeroportos_malha_mix], lowBound=0, cat='Integer')\n", "\n", @@ -2040,20 +2040,20 @@ "# Objetivo: Minimizar a soma de aeronaves. \n", "# Adicionamos pequenas penalidades para desempate:\n", "# 1. Preferir tempo de voo menor nos reposicionamentos\n", - "# 2. C-99 é mais cara/maior, penalidade irrisória para preferir C-97 se ambas puderem (custo indireto)\n", + "# 2. C-99 \u00e9 mais cara/maior, penalidade irris\u00f3ria para preferir C-97 se ambas puderem (custo indireto)\n", "penalidade_voo_97 = pulp.lpSum([(D_97[(i, j)] + Y_97[(i, j)]) * t_voo_97[(i, j)] for i in aeroportos_malha_mix for j in aeroportos_malha_mix])\n", "penalidade_voo_99 = pulp.lpSum([(D_99[(i, j)] + Y_99[(i, j)]) * t_voo_99[(i, j)] for i in aeroportos_malha_mix for j in aeroportos_malha_mix])\n", "prob8 += N_97 + N_99 + 0.001 * N_99 + 0.00001 * (penalidade_voo_97 + penalidade_voo_99)\n", "\n", - "# Restrição de Demanda em PAX\n", - "# Todas as rotas que não são Top10 tem demanda = 0\n", + "# Restri\u00e7\u00e3o de Demanda em PAX\n", + "# Todas as rotas que n\u00e3o s\u00e3o Top10 tem demanda = 0\n", "for i in aeroportos_malha_mix:\n", " for j in aeroportos_malha_mix:\n", " r_name = f\"{i}-{j}\"\n", " demanda = demanda_pax_mix.get(r_name, 0)\n", " prob8 += (D_97[(i, j)] * CAPACIDADE_C97) + (D_99[(i, j)] * CAPACIDADE_C99) >= demanda\n", "\n", - "# Restrições de Fluxo Circulares (separados por tipo)\n", + "# Restri\u00e7\u00f5es de Fluxo Circulares (separados por tipo)\n", "for no in aeroportos_malha_mix:\n", " # C-97\n", " chegadas_97 = pulp.lpSum([D_97[(i, no)] + Y_97[(i, no)] for i in aeroportos_malha_mix])\n", @@ -2065,11 +2065,11 @@ " partidas_99 = pulp.lpSum([D_99[(no, j)] + Y_99[(no, j)] for j in aeroportos_malha_mix])\n", " prob8 += chegadas_99 == partidas_99\n", "\n", - "# Restrições de tempo operacional diário\n", + "# Restri\u00e7\u00f5es de tempo operacional di\u00e1rio\n", "prob8 += pulp.lpSum([(D_97[(i, j)] + Y_97[(i, j)]) * t_voo_97[(i, j)] for i in aeroportos_malha_mix for j in aeroportos_malha_mix]) <= N_97 * 1440\n", "prob8 += pulp.lpSum([(D_99[(i, j)] + Y_99[(i, j)]) * t_voo_99[(i, j)] for i in aeroportos_malha_mix for j in aeroportos_malha_mix]) <= N_99 * 1440\n", "\n", - "# Restrições Físicas\n", + "# Restri\u00e7\u00f5es F\u00edsicas\n", "prob8 += N_97 <= total_aeronaves\n", "prob8 += N_99 <= total_aeronaves_c99\n", "\n", @@ -2077,11 +2077,11 @@ "\n", "print(\"\\n== RESULTADO DO MODELO 8 (MIX SEM TAT) ==\")\n", "print(f\"Status: {pulp.LpStatus[prob8.status]}\")\n", - "print(f\"Aeronaves C-97 necessárias: {int(N_97.varValue)}\")\n", - "print(f\"Aeronaves C-99A necessárias: {int(N_99.varValue)}\")\n", + "print(f\"Aeronaves C-97 necess\u00e1rias: {int(N_97.varValue)}\")\n", + "print(f\"Aeronaves C-99A necess\u00e1rias: {int(N_99.varValue)}\")\n", "print(f\"Frota total mobilizada: {int(N_97.varValue) + int(N_99.varValue)} aeronaves\\n\")\n", "\n", - "print(\"Alocações para satisfazer a Demanda:\")\n", + "print(\"Aloca\u00e7\u00f5es para satisfazer a Demanda:\")\n", "for r in rotas_lista_mix:\n", " origem, destino = r.split('-')\n", " d97_val = int(D_97[(origem, destino)].varValue)\n", @@ -2094,8 +2094,8 @@ "id": "9fe80d00", "metadata": {}, "source": [ - "## MODELO 9: Minimização Absoluta de Frota Mix (C/ TAT 40 MIN)\n", - "Por fim, a versão que soma a demanda, usa as duas frotas simultaneamente, permite múltiplos voos por dia, gera reposicionamentos e pune **40 minutos** no chão (TAT) para cada perna voada por qualquer um dos aviões.\n" + "## MODELO 9: Minimiza\u00e7\u00e3o Absoluta de Frota Mix (C/ TAT 40 MIN)\n", + "Por fim, a vers\u00e3o que soma a demanda, usa as duas frotas simultaneamente, permite m\u00faltiplos voos por dia, gera reposicionamentos e pune **40 minutos** no ch\u00e3o (TAT) para cada perna voada por qualquer um dos avi\u00f5es.\n" ] }, { @@ -2118,12 +2118,12 @@ "\n", "== RESULTADO DO MODELO 9 (MIX COM 40 MIN TAT) ==\n", "Status: Optimal\n", - "Aeronaves C-97 necessárias: 0\n", - "Aeronaves C-99A necessárias: 1\n", + "Aeronaves C-97 necess\u00e1rias: 0\n", + "Aeronaves C-99A necess\u00e1rias: 1\n", "Frota total mobilizada: 1 aeronaves\n", "\n", "Tempo total de voo da frota combinada: 886 min (14.77 h)\n", - "Tempo de solo desperdiçado (TAT 40 min em 12 voos): 480 min (8.00 h)\n" + "Tempo de solo desperdi\u00e7ado (TAT 40 min em 12 voos): 480 min (8.00 h)\n" ] } ], @@ -2166,8 +2166,8 @@ "\n", "print(\"\\n== RESULTADO DO MODELO 9 (MIX COM 40 MIN TAT) ==\")\n", "print(f\"Status: {pulp.LpStatus[prob9.status]}\")\n", - "print(f\"Aeronaves C-97 necessárias: {int(N_97_t.varValue)}\")\n", - "print(f\"Aeronaves C-99A necessárias: {int(N_99_t.varValue)}\")\n", + "print(f\"Aeronaves C-97 necess\u00e1rias: {int(N_97_t.varValue)}\")\n", + "print(f\"Aeronaves C-99A necess\u00e1rias: {int(N_99_t.varValue)}\")\n", "print(f\"Frota total mobilizada: {int(N_97_t.varValue) + int(N_99_t.varValue)} aeronaves\\n\")\n", "\n", "tempo_97_voo = sum([(int(D_97_t[(i, j)].varValue) + int(Y_97_t[(i, j)].varValue)) * t_voo_97[(i, j)] for i in aeroportos_malha_mix for j in aeroportos_malha_mix])\n", @@ -2176,7 +2176,258 @@ "total_voos = sum([(int(D_97_t[(i, j)].varValue) + int(Y_97_t[(i, j)].varValue) + int(D_99_t[(i, j)].varValue) + int(Y_99_t[(i, j)].varValue)) for i in aeroportos_malha_mix for j in aeroportos_malha_mix])\n", "\n", "print(f\"Tempo total de voo da frota combinada: {tempo_97_voo + tempo_99_voo} min ({(tempo_97_voo + tempo_99_voo)/60:.2f} h)\")\n", - "print(f\"Tempo de solo desperdiçado (TAT 40 min em {total_voos} voos): {total_voos * TAT_min} min ({(total_voos * TAT_min)/60:.2f} h)\")\n" + "print(f\"Tempo de solo desperdi\u00e7ado (TAT 40 min em {total_voos} voos): {total_voos * TAT_min} min ({(total_voos * TAT_min)/60:.2f} h)\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## An\u00e1lise Semanal e Modelos 10, 11 e 12\n", + "A pedido, agora faremos uma an\u00e1lise semanal, onde calcularemos a m\u00e9dia de passageiros por semana nos trechos mais relevantes. Em seguida, os modelos de mix de frota (C-97 e C-99A) ser\u00e3o executados com a vis\u00e3o semanal (ciclo de 7 dias de opera\u00e7\u00e3o, ou seja, 1440 * 7 minutos dispon\u00edveis por aeronave por semana)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# C\u00e1lculos semanais\n", + "df_mix['Semana'] = pd.to_datetime(df_mix['Data Apenas']).dt.isocalendar().week\n", + "demanda_semanal_mix = df_mix.groupby(['Trecho', 'Semana'])['PAX'].sum().reset_index()\n", + "\n", + "import itertools\n", + "todos_trechos_mix = demanda_semanal_mix['Trecho'].unique()\n", + "todas_semanas = range(1, 54) # Semanas 1 a 53\n", + "idx_semana_mix = pd.MultiIndex.from_product([todos_trechos_mix, todas_semanas], names=['Trecho', 'Semana'])\n", + "demanda_semanal_completa_mix = pd.DataFrame(index=idx_semana_mix).reset_index()\n", + "demanda_semanal_completa_mix = pd.merge(demanda_semanal_completa_mix, demanda_semanal_mix, on=['Trecho', 'Semana'], how='left').fillna({'PAX': 0})\n", + "\n", + "estatisticas_trechos_semana = demanda_semanal_completa_mix.groupby('Trecho').agg(\n", + " Total_PAX=('PAX', 'sum'),\n", + " Semanas_Operadas=('PAX', lambda x: (x > 0).sum())\n", + ").reset_index()\n", + "\n", + "estatisticas_trechos_semana['Media_PAX_Semanas_Operadas'] = estatisticas_trechos_semana.apply(\n", + " lambda row: row['Total_PAX'] / row['Semanas_Operadas'] if row['Semanas_Operadas'] > 0 else 0, axis=1\n", + ")\n", + "\n", + "estatisticas_trechos_semana['Score_Relevancia'] = estatisticas_trechos_semana['Total_PAX'] * estatisticas_trechos_semana['Semanas_Operadas']\n", + "estatisticas_trechos_semana = estatisticas_trechos_semana.sort_values(by='Score_Relevancia', ascending=False)\n", + "\n", + "NUM_TRECHOS_MIX = 10\n", + "top_trechos_semana = estatisticas_trechos_semana.head(NUM_TRECHOS_MIX)\n", + "\n", + "display(top_trechos_semana)\n", + "\n", + "plt.figure(figsize=(10, 6))\n", + "sns.barplot(data=top_trechos_semana, x='Trecho', y='Score_Relevancia', hue='Trecho', palette='cubehelix', legend=False)\n", + "plt.title(f'Top {NUM_TRECHOS_MIX} Trechos MIX (C-97 + C-99A) - Demanda Semanal')\n", + "plt.xticks(rotation=45)\n", + "plt.tight_layout()\n", + "plt.show()\n", + "\n", + "rotas_lista_semana = top_trechos_semana['Trecho'].tolist()\n", + "\n", + "demanda_pax_semana = {}\n", + "for _, row in top_trechos_semana.iterrows():\n", + " demanda_pax_semana[row['Trecho']] = row['Media_PAX_Semanas_Operadas']\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MODELO 10: Minimiza\u00e7\u00e3o de Dist\u00e2ncia (Custo) com Mix de Frota (Semanal)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "distancias_voo_semana = {}\n", + "for r in rotas_lista_semana:\n", + " origem, destino = r.split('-')\n", + " distancias_voo_semana[r] = calc_distancia(origem, destino)\n", + "\n", + "prob10 = pulp.LpProblem(\"Fleet_Assignment_Mix_Semanal\", pulp.LpMinimize)\n", + "\n", + "X10_97 = pulp.LpVariable.dicts(\"X10_97\", rotas_lista_semana, lowBound=0, cat='Integer')\n", + "X10_99 = pulp.LpVariable.dicts(\"X10_99\", rotas_lista_semana, lowBound=0, cat='Integer')\n", + "\n", + "prob10 += pulp.lpSum([distancias_voo_semana[r] * (X10_97[r] + X10_99[r]) for r in rotas_lista_semana])\n", + "\n", + "for r in rotas_lista_semana:\n", + " prob10 += (X10_97[r] * CAPACIDADE_C97) + (X10_99[r] * CAPACIDADE_C99) >= demanda_pax_semana[r]\n", + "\n", + "# Restri\u00e7\u00e3o semanal global (simplificando: 7 voos num mesmo trecho podem ser feitos por 1 aeronave, \n", + "# mas vamos considerar apenas a disponibilidade da frota. Se no modelo di\u00e1rio era <= total_aeronaves,\n", + "# no semanal a restri\u00e7\u00e3o para cada aeronave \u00e9 poder fazer m\u00faltiplos voos. Como \u00e9 s\u00f3 fleet assignment,\n", + "# a restri\u00e7\u00e3o ser\u00e1 multiplicada por 7)\n", + "prob10 += pulp.lpSum([X10_97[r] for r in rotas_lista_semana]) <= total_aeronaves * 7\n", + "prob10 += pulp.lpSum([X10_99[r] for r in rotas_lista_semana]) <= total_aeronaves_c99 * 7\n", + "\n", + "prob10.solve(pulp.PULP_CBC_CMD(msg=False))\n", + "\n", + "print(\"== MODELO 10 (MIX C-97 e C-99A - SEMANAL) ==\")\n", + "print(f\"Status: {pulp.LpStatus[prob10.status]}\")\n", + "print(f\"Dist\u00e2ncia Total da Opera\u00e7\u00e3o Semanal: {pulp.value(prob10.objective):.2f} km\\n\")\n", + "\n", + "for r in rotas_lista_semana:\n", + " c97_alloc = int(X10_97[r].varValue)\n", + " c99_alloc = int(X10_99[r].varValue)\n", + " capacidade_gerada = (c97_alloc * CAPACIDADE_C97) + (c99_alloc * CAPACIDADE_C99)\n", + " print(f\"Trecho: {r:10} | Demanda Semanal PAX: {demanda_pax_semana[r]:.0f} | Oferecido: {capacidade_gerada} | Alocados na Semana -> C-97: {c97_alloc}, C-99A: {c99_alloc}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MODELO 11: Minimiza\u00e7\u00e3o Absoluta de Frota Mix Semanal (S/ TAT)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aeroportos_malha_semana = set()\n", + "for r in rotas_lista_semana:\n", + " o, d = r.split('-')\n", + " aeroportos_malha_semana.add(o)\n", + " aeroportos_malha_semana.add(d)\n", + "aeroportos_malha_semana = list(aeroportos_malha_semana)\n", + "\n", + "t11_voo_97 = {}\n", + "t11_voo_99 = {}\n", + "for o in aeroportos_malha_semana:\n", + " for d in aeroportos_malha_semana:\n", + " if o == d:\n", + " t11_voo_97[(o, d)] = 0\n", + " t11_voo_99[(o, d)] = 0\n", + " else:\n", + " dist = calc_distancia(o, d)\n", + " t11_voo_97[(o, d)] = int(round((dist / vel_media_c97) * 60))\n", + " t11_voo_99[(o, d)] = int(round((dist / vel_media_c99) * 60))\n", + "\n", + "prob11 = pulp.LpProblem(\"Minimizar_Frota_Mix_Semanal\", pulp.LpMinimize)\n", + "\n", + "D11_97 = pulp.LpVariable.dicts(\"D11_97\", [(o, d) for o in aeroportos_malha_semana for d in aeroportos_malha_semana], lowBound=0, cat='Integer')\n", + "D11_99 = pulp.LpVariable.dicts(\"D11_99\", [(o, d) for o in aeroportos_malha_semana for d in aeroportos_malha_semana], lowBound=0, cat='Integer')\n", + "Y11_97 = pulp.LpVariable.dicts(\"Y11_97\", [(o, d) for o in aeroportos_malha_semana for d in aeroportos_malha_semana], lowBound=0, cat='Integer')\n", + "Y11_99 = pulp.LpVariable.dicts(\"Y11_99\", [(o, d) for o in aeroportos_malha_semana for d in aeroportos_malha_semana], lowBound=0, cat='Integer')\n", + "\n", + "N11_97 = pulp.LpVariable(\"N11_97\", lowBound=0, cat='Integer')\n", + "N11_99 = pulp.LpVariable(\"N11_99\", lowBound=0, cat='Integer')\n", + "\n", + "penalidade11_97 = pulp.lpSum([(D11_97[(i, j)] + Y11_97[(i, j)]) * t11_voo_97[(i, j)] for i in aeroportos_malha_semana for j in aeroportos_malha_semana])\n", + "penalidade11_99 = pulp.lpSum([(D11_99[(i, j)] + Y11_99[(i, j)]) * t11_voo_99[(i, j)] for i in aeroportos_malha_semana for j in aeroportos_malha_semana])\n", + "prob11 += N11_97 + N11_99 + 0.001 * N11_99 + 0.00001 * (penalidade11_97 + penalidade11_99)\n", + "\n", + "for i in aeroportos_malha_semana:\n", + " for j in aeroportos_malha_semana:\n", + " r_name = f\"{i}-{j}\"\n", + " demanda = demanda_pax_semana.get(r_name, 0)\n", + " prob11 += (D11_97[(i, j)] * CAPACIDADE_C97) + (D11_99[(i, j)] * CAPACIDADE_C99) >= demanda\n", + "\n", + "for no in aeroportos_malha_semana:\n", + " chegadas_97 = pulp.lpSum([D11_97[(i, no)] + Y11_97[(i, no)] for i in aeroportos_malha_semana])\n", + " partidas_97 = pulp.lpSum([D11_97[(no, j)] + Y11_97[(no, j)] for j in aeroportos_malha_semana])\n", + " prob11 += chegadas_97 == partidas_97\n", + " \n", + " chegadas_99 = pulp.lpSum([D11_99[(i, no)] + Y11_99[(i, no)] for i in aeroportos_malha_semana])\n", + " partidas_99 = pulp.lpSum([D11_99[(no, j)] + Y11_99[(no, j)] for j in aeroportos_malha_semana])\n", + " prob11 += chegadas_99 == partidas_99\n", + "\n", + "# Restri\u00e7\u00e3o de tempo semanal (1440 * 7 = 10080 minutos por semana)\n", + "MINUTOS_SEMANA = 1440 * 7\n", + "prob11 += pulp.lpSum([(D11_97[(i, j)] + Y11_97[(i, j)]) * t11_voo_97[(i, j)] for i in aeroportos_malha_semana for j in aeroportos_malha_semana]) <= N11_97 * MINUTOS_SEMANA\n", + "prob11 += pulp.lpSum([(D11_99[(i, j)] + Y11_99[(i, j)]) * t11_voo_99[(i, j)] for i in aeroportos_malha_semana for j in aeroportos_malha_semana]) <= N11_99 * MINUTOS_SEMANA\n", + "\n", + "prob11 += N11_97 <= total_aeronaves\n", + "prob11 += N11_99 <= total_aeronaves_c99\n", + "\n", + "prob11.solve(pulp.PULP_CBC_CMD(msg=False))\n", + "\n", + "print(\"\\n== RESULTADO DO MODELO 11 (MIX SEMANAL SEM TAT) ==\")\n", + "print(f\"Status: {pulp.LpStatus[prob11.status]}\")\n", + "print(f\"Aeronaves C-97 necess\u00e1rias para operar a semana: {int(N11_97.varValue)}\")\n", + "print(f\"Aeronaves C-99A necess\u00e1rias para operar a semana: {int(N11_99.varValue)}\")\n", + "print(f\"Frota total mobilizada na semana: {int(N11_97.varValue) + int(N11_99.varValue)} aeronaves\\n\")\n", + "\n", + "for r in rotas_lista_semana:\n", + " origem, destino = r.split('-')\n", + " d97_val = int(D11_97[(origem, destino)].varValue)\n", + " d99_val = int(D11_99[(origem, destino)].varValue)\n", + " print(f\" -> Trecho {r:10}: C-97 faz {d97_val} voo(s) na semana | C-99A faz {d99_val} voo(s) na semana\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MODELO 12: Minimiza\u00e7\u00e3o Absoluta de Frota Mix Semanal (C/ TAT 40 MIN)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "prob12 = pulp.LpProblem(\"Minimizar_Frota_Mix_Semanal_TAT\", pulp.LpMinimize)\n", + "\n", + "D12_97 = pulp.LpVariable.dicts(\"D12_97\", [(o, d) for o in aeroportos_malha_semana for d in aeroportos_malha_semana], lowBound=0, cat='Integer')\n", + "D12_99 = pulp.LpVariable.dicts(\"D12_99\", [(o, d) for o in aeroportos_malha_semana for d in aeroportos_malha_semana], lowBound=0, cat='Integer')\n", + "Y12_97 = pulp.LpVariable.dicts(\"Y12_97\", [(o, d) for o in aeroportos_malha_semana for d in aeroportos_malha_semana], lowBound=0, cat='Integer')\n", + "Y12_99 = pulp.LpVariable.dicts(\"Y12_99\", [(o, d) for o in aeroportos_malha_semana for d in aeroportos_malha_semana], lowBound=0, cat='Integer')\n", + "\n", + "N12_97 = pulp.LpVariable(\"N12_97\", lowBound=0, cat='Integer')\n", + "N12_99 = pulp.LpVariable(\"N12_99\", lowBound=0, cat='Integer')\n", + "\n", + "TAT_min = 40\n", + "\n", + "penalidade12_97 = pulp.lpSum([(D12_97[(i, j)] + Y12_97[(i, j)]) * t11_voo_97[(i, j)] for i in aeroportos_malha_semana for j in aeroportos_malha_semana])\n", + "penalidade12_99 = pulp.lpSum([(D12_99[(i, j)] + Y12_99[(i, j)]) * t11_voo_99[(i, j)] for i in aeroportos_malha_semana for j in aeroportos_malha_semana])\n", + "prob12 += N12_97 + N12_99 + 0.001 * N12_99 + 0.00001 * (penalidade12_97 + penalidade12_99)\n", + "\n", + "for i in aeroportos_malha_semana:\n", + " for j in aeroportos_malha_semana:\n", + " r_name = f\"{i}-{j}\"\n", + " demanda = demanda_pax_semana.get(r_name, 0)\n", + " prob12 += (D12_97[(i, j)] * CAPACIDADE_C97) + (D12_99[(i, j)] * CAPACIDADE_C99) >= demanda\n", + "\n", + "for no in aeroportos_malha_semana:\n", + " prob12 += pulp.lpSum([D12_97[(i, no)] + Y12_97[(i, no)] for i in aeroportos_malha_semana]) == pulp.lpSum([D12_97[(no, j)] + Y12_97[(no, j)] for j in aeroportos_malha_semana])\n", + " prob12 += pulp.lpSum([D12_99[(i, no)] + Y12_99[(i, no)] for i in aeroportos_malha_semana]) == pulp.lpSum([D12_99[(no, j)] + Y12_99[(no, j)] for j in aeroportos_malha_semana])\n", + "\n", + "# TAT INCLUSO AQUI\n", + "prob12 += pulp.lpSum([(D12_97[(i, j)] + Y12_97[(i, j)]) * (t11_voo_97[(i, j)] + TAT_min) for i in aeroportos_malha_semana for j in aeroportos_malha_semana]) <= N12_97 * MINUTOS_SEMANA\n", + "prob12 += pulp.lpSum([(D12_99[(i, j)] + Y12_99[(i, j)]) * (t11_voo_99[(i, j)] + TAT_min) for i in aeroportos_malha_semana for j in aeroportos_malha_semana]) <= N12_99 * MINUTOS_SEMANA\n", + "\n", + "prob12 += N12_97 <= total_aeronaves\n", + "prob12 += N12_99 <= total_aeronaves_c99\n", + "\n", + "prob12.solve(pulp.PULP_CBC_CMD(msg=False))\n", + "\n", + "print(\"\\n== RESULTADO DO MODELO 12 (MIX SEMANAL COM 40 MIN TAT) ==\")\n", + "print(f\"Status: {pulp.LpStatus[prob12.status]}\")\n", + "print(f\"Aeronaves C-97 necess\u00e1rias para operar a semana: {int(N12_97.varValue)}\")\n", + "print(f\"Aeronaves C-99A necess\u00e1rias para operar a semana: {int(N12_99.varValue)}\")\n", + "print(f\"Frota total mobilizada na semana: {int(N12_97.varValue) + int(N12_99.varValue)} aeronaves\\n\")\n", + "\n", + "tempo12_97_voo = sum([(int(D12_97[(i, j)].varValue) + int(Y12_97[(i, j)].varValue)) * t11_voo_97[(i, j)] for i in aeroportos_malha_semana for j in aeroportos_malha_semana])\n", + "tempo12_99_voo = sum([(int(D12_99[(i, j)].varValue) + int(Y12_99[(i, j)].varValue)) * t11_voo_99[(i, j)] for i in aeroportos_malha_semana for j in aeroportos_malha_semana])\n", + "\n", + "total12_voos = sum([(int(D12_97[(i, j)].varValue) + int(Y12_97[(i, j)].varValue) + int(D12_99[(i, j)].varValue) + int(Y12_99[(i, j)].varValue)) for i in aeroportos_malha_semana for j in aeroportos_malha_semana])\n", + "\n", + "print(f\"Tempo total de voo da frota combinada na semana: {tempo12_97_voo + tempo12_99_voo} min ({(tempo12_97_voo + tempo12_99_voo)/60:.2f} h)\")\n", + "print(f\"Tempo de solo desperdi\u00e7ado (TAT 40 min em {total12_voos} voos): {total12_voos * TAT_min} min ({(total12_voos * TAT_min)/60:.2f} h)\")\n" ] } ], @@ -2196,4 +2447,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file