refactor: simplifica Fleet Assignment para nível Brasil sem restrição de esquadrões (Modelo 1)
This commit is contained in:
60
create_nb.py
60
create_nb.py
@@ -202,22 +202,22 @@ plt.show()
|
|||||||
"""))
|
"""))
|
||||||
|
|
||||||
# Cell 9: Markdown FAM
|
# Cell 9: Markdown FAM
|
||||||
cells.append(nbf.v4.new_markdown_cell("""## 4. Fleet Assignment Model (FAM)
|
cells.append(nbf.v4.new_markdown_cell("""## 4. Fleet Assignment Model (FAM) - Nível Brasil
|
||||||
Conforme o livro base *Airline Operations and Scheduling*, o problema de alocação de frotas busca atribuir os tipos de aeronaves às pernas de voo visando minimizar o custo e as perdas de receita (spill). Como temos apenas um tipo (C-97), nosso problema adapta-se a uma **alocação de esquadrões (e suas respectivas bases)** aos voos requeridos para minimizar o custo total de operação, que é proporcional à distância voada.
|
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.
|
||||||
|
|
||||||
**Modelo Matemático:**
|
**Modelo Matemático:**
|
||||||
- **Variáveis de decisão:** $X_{s, r}$ (Quantidade de voos operados pelo esquadrão $s$ no trecho $r$)
|
- **Variáveis de decisão:** $X_r$ (Quantidade de voos alocados no trecho $r$)
|
||||||
- **Função Objetivo:** Minimizar a distância total (Distância de posicionamento da base + Distância do trecho + Retorno para a base).
|
- **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.
|
||||||
- **Restrições:**
|
- **Restrições:**
|
||||||
1. O número de voos alocados para o trecho deve suprir a demanda média (Média PAX / Capacidade do C-97).
|
1. O número de voos alocados para o trecho deve suprir a demanda diária operada daquela rota.
|
||||||
2. O total de voos atribuídos a um esquadrão não pode exceder o número de aeronaves disponíveis naquele esquadrão no dia.
|
2. O total de voos atribuídos no dia não pode exceder o total da frota de C-97 disponível (14 aeronaves).
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
# Cell 10: Code FAM
|
# Cell 10: Code FAM
|
||||||
cells.append(nbf.v4.new_code_cell("""# Preparação dos dados para o modelo
|
cells.append(nbf.v4.new_code_cell("""# Preparação dos dados para o modelo
|
||||||
rotas_lista = top_trechos['Trecho'].tolist()
|
rotas_lista = top_trechos['Trecho'].tolist()
|
||||||
|
|
||||||
# Dicionário de distâncias
|
# Dicionário de distâncias apenas dos trechos diretos
|
||||||
def calc_distancia(icao1, icao2):
|
def calc_distancia(icao1, icao2):
|
||||||
try:
|
try:
|
||||||
lat1, lon1, alt1 = df_aeroportos.loc[icao1, ['lat', 'lon', 'elevation']]
|
lat1, lon1, alt1 = df_aeroportos.loc[icao1, ['lat', 'lon', 'elevation']]
|
||||||
@@ -230,52 +230,46 @@ def calc_distancia(icao1, icao2):
|
|||||||
distancias_voo = {}
|
distancias_voo = {}
|
||||||
for r in rotas_lista:
|
for r in rotas_lista:
|
||||||
origem, destino = r.split('-')
|
origem, destino = r.split('-')
|
||||||
dist_trecho = calc_distancia(origem, destino)
|
distancias_voo[r] = calc_distancia(origem, destino)
|
||||||
for esq, base in bases_esquadroes.items():
|
|
||||||
dist_ida = calc_distancia(base, origem) if base != origem else 0
|
|
||||||
dist_volta = calc_distancia(destino, base) if base != destino else 0
|
|
||||||
distancias_voo[(esq, r)] = dist_ida + dist_trecho + dist_volta
|
|
||||||
|
|
||||||
# Demanda de voos por rota (Média diária)
|
# Demanda de voos por rota
|
||||||
voos_requeridos = {}
|
voos_requeridos = {}
|
||||||
for _, row in top_trechos.iterrows():
|
for _, row in top_trechos.iterrows():
|
||||||
# Arredondamento para cima da (demanda / capacidade)
|
|
||||||
voos = math.ceil(row['Media_PAX_Dias_Operados'] / CAPACIDADE_C97)
|
voos = math.ceil(row['Media_PAX_Dias_Operados'] / CAPACIDADE_C97)
|
||||||
# Pelo menos 1 voo para suprir a demanda da rota selecionada
|
|
||||||
voos_requeridos[row['Trecho']] = max(1, voos)
|
voos_requeridos[row['Trecho']] = max(1, voos)
|
||||||
|
|
||||||
# MODELAGEM COM PULP
|
# MODELAGEM COM PULP
|
||||||
prob = pulp.LpProblem("Fleet_Assignment_C97", pulp.LpMinimize)
|
prob = pulp.LpProblem("Fleet_Assignment_C97_Brasil", pulp.LpMinimize)
|
||||||
|
|
||||||
# Variáveis
|
# Variáveis (número de voos em cada rota)
|
||||||
X = pulp.LpVariable.dicts("X", [(esq, r) for esq in aeronaves_por_esquadrao.keys() for r in rotas_lista], lowBound=0, cat='Integer')
|
X = pulp.LpVariable.dicts("X", rotas_lista, lowBound=0, cat='Integer')
|
||||||
|
|
||||||
# Função Objetivo
|
# Função Objetivo: Minimizar distância voada nos trechos
|
||||||
prob += pulp.lpSum([distancias_voo[(esq, r)] * X[(esq, r)] for esq in aeronaves_por_esquadrao.keys() for r in rotas_lista])
|
prob += pulp.lpSum([distancias_voo[r] * X[r] for r in rotas_lista])
|
||||||
|
|
||||||
# Restrição 1: Suprir a demanda da rota
|
# Restrição 1: Suprir a demanda
|
||||||
for r in rotas_lista:
|
for r in rotas_lista:
|
||||||
prob += pulp.lpSum([X[(esq, r)] for esq in aeronaves_por_esquadrao.keys()]) >= voos_requeridos[r]
|
prob += X[r] >= voos_requeridos[r]
|
||||||
|
|
||||||
# Restrição 2: Limite de aeronaves por esquadrão
|
# Restrição 2: Limite global de frota (nível Brasil)
|
||||||
for esq in aeronaves_por_esquadrao.keys():
|
prob += pulp.lpSum([X[r] for r in rotas_lista]) <= total_aeronaves
|
||||||
prob += pulp.lpSum([X[(esq, r)] for r in rotas_lista]) <= aeronaves_por_esquadrao[esq]
|
|
||||||
|
|
||||||
# Solução
|
# Solução
|
||||||
prob.solve(pulp.PULP_CBC_CMD(msg=False))
|
prob.solve(pulp.PULP_CBC_CMD(msg=False))
|
||||||
|
|
||||||
# Exibição dos resultados
|
# Exibição dos resultados
|
||||||
print("== RESULTADO DO FLEET ASSIGNMENT ==")
|
print("== RESULTADO DO FLEET ASSIGNMENT (NÍVEL BRASIL) ==")
|
||||||
print(f"Status: {pulp.LpStatus[prob.status]}")
|
print(f"Status: {pulp.LpStatus[prob.status]}")
|
||||||
print(f"Distância Total Minimizada: {pulp.value(prob.objective):.2f} km\\n")
|
print(f"Distância Total da Operação: {pulp.value(prob.objective):.2f} km\\n")
|
||||||
|
|
||||||
print("Alocações (Voos Diários):")
|
print("Alocações (Voos Diários Globais):")
|
||||||
|
voos_totais = 0
|
||||||
for r in rotas_lista:
|
for r in rotas_lista:
|
||||||
print(f"\\nTrecho: {r} (Voos necessários: {voos_requeridos[r]})")
|
qtd = int(X[r].varValue)
|
||||||
for esq in aeronaves_por_esquadrao.keys():
|
voos_totais += qtd
|
||||||
qtd = int(X[(esq, r)].varValue)
|
print(f"Trecho: {r:10} | Demanda Média PAX: {voos_requeridos[r]*CAPACIDADE_C97:3.0f} | Alocados: {qtd} voo(s)")
|
||||||
if qtd > 0:
|
|
||||||
print(f" -> {esq} (Base {bases_esquadroes[esq]}): {qtd} voo(s)")
|
print(f"\\nTotal de voos alocados na malha diária: {voos_totais} de {total_aeronaves} aeronaves disponíveis.")
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
nb.cells = cells
|
nb.cells = cells
|
||||||
|
|||||||
178
modelos.ipynb
178
modelos.ipynb
@@ -2,7 +2,7 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"id": "6be31f50",
|
"id": "3057dead",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"# Alocação de Frotas (Fleet Scheduling) - Aeronaves C-97\n",
|
"# Alocação de Frotas (Fleet Scheduling) - Aeronaves C-97\n",
|
||||||
@@ -21,13 +21,13 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 1,
|
"execution_count": 1,
|
||||||
"id": "f4b8c44d",
|
"id": "8c9a45e2",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"execution": {
|
"execution": {
|
||||||
"iopub.execute_input": "2026-06-07T23:27:29.153892Z",
|
"iopub.execute_input": "2026-06-07T23:32:32.992543Z",
|
||||||
"iopub.status.busy": "2026-06-07T23:27:29.153192Z",
|
"iopub.status.busy": "2026-06-07T23:32:32.992366Z",
|
||||||
"iopub.status.idle": "2026-06-07T23:27:29.811580Z",
|
"iopub.status.idle": "2026-06-07T23:32:33.601354Z",
|
||||||
"shell.execute_reply": "2026-06-07T23:27:29.810502Z"
|
"shell.execute_reply": "2026-06-07T23:32:33.600709Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"id": "4a3f8284",
|
"id": "cfe85206",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## 1. Leitura e Limpeza dos Dados\n",
|
"## 1. Leitura e Limpeza dos Dados\n",
|
||||||
@@ -63,13 +63,13 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 2,
|
"execution_count": 2,
|
||||||
"id": "43261510",
|
"id": "32d7c973",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"execution": {
|
"execution": {
|
||||||
"iopub.execute_input": "2026-06-07T23:27:29.813511Z",
|
"iopub.execute_input": "2026-06-07T23:32:33.603095Z",
|
||||||
"iopub.status.busy": "2026-06-07T23:27:29.813233Z",
|
"iopub.status.busy": "2026-06-07T23:32:33.602885Z",
|
||||||
"iopub.status.idle": "2026-06-07T23:27:30.183686Z",
|
"iopub.status.idle": "2026-06-07T23:32:33.976197Z",
|
||||||
"shell.execute_reply": "2026-06-07T23:27:30.182986Z"
|
"shell.execute_reply": "2026-06-07T23:32:33.975310Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"id": "bffd4e0c",
|
"id": "e1b1fe07",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## 2. Análise dos Esquadrões e Matrículas (Aeronaves)\n",
|
"## 2. Análise dos Esquadrões e Matrículas (Aeronaves)\n",
|
||||||
@@ -111,13 +111,13 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 3,
|
"execution_count": 3,
|
||||||
"id": "40061c45",
|
"id": "5016861c",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"execution": {
|
"execution": {
|
||||||
"iopub.execute_input": "2026-06-07T23:27:30.185144Z",
|
"iopub.execute_input": "2026-06-07T23:32:33.977763Z",
|
||||||
"iopub.status.busy": "2026-06-07T23:27:30.185022Z",
|
"iopub.status.busy": "2026-06-07T23:32:33.977632Z",
|
||||||
"iopub.status.idle": "2026-06-07T23:27:30.196292Z",
|
"iopub.status.idle": "2026-06-07T23:32:33.989318Z",
|
||||||
"shell.execute_reply": "2026-06-07T23:27:30.195756Z"
|
"shell.execute_reply": "2026-06-07T23:32:33.988806Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"id": "94ec1687",
|
"id": "2fe94947",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## 3. Análise Estatística das Demandas Diárias\n",
|
"## 3. Análise Estatística das Demandas Diárias\n",
|
||||||
@@ -187,13 +187,13 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 4,
|
"execution_count": 4,
|
||||||
"id": "9e2bb9ab",
|
"id": "73a0064f",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"execution": {
|
"execution": {
|
||||||
"iopub.execute_input": "2026-06-07T23:27:30.197674Z",
|
"iopub.execute_input": "2026-06-07T23:32:33.992417Z",
|
||||||
"iopub.status.busy": "2026-06-07T23:27:30.197555Z",
|
"iopub.status.busy": "2026-06-07T23:32:33.992279Z",
|
||||||
"iopub.status.idle": "2026-06-07T23:27:30.501774Z",
|
"iopub.status.idle": "2026-06-07T23:32:34.310958Z",
|
||||||
"shell.execute_reply": "2026-06-07T23:27:30.501194Z"
|
"shell.execute_reply": "2026-06-07T23:32:34.310336Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -424,7 +424,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"id": "d6f1f668",
|
"id": "c81003e5",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## Diagrama de Rede dos Trechos\n",
|
"## Diagrama de Rede dos Trechos\n",
|
||||||
@@ -434,13 +434,13 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 5,
|
"execution_count": 5,
|
||||||
"id": "4929bba6",
|
"id": "bb72e82d",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"execution": {
|
"execution": {
|
||||||
"iopub.execute_input": "2026-06-07T23:27:30.503463Z",
|
"iopub.execute_input": "2026-06-07T23:32:34.312383Z",
|
||||||
"iopub.status.busy": "2026-06-07T23:27:30.503337Z",
|
"iopub.status.busy": "2026-06-07T23:32:34.312250Z",
|
||||||
"iopub.status.idle": "2026-06-07T23:27:30.631863Z",
|
"iopub.status.idle": "2026-06-07T23:32:34.430777Z",
|
||||||
"shell.execute_reply": "2026-06-07T23:27:30.631298Z"
|
"shell.execute_reply": "2026-06-07T23:32:34.430189Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -497,30 +497,30 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"id": "59991398",
|
"id": "e5daac1f",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## 4. Fleet Assignment Model (FAM)\n",
|
"## 4. Fleet Assignment Model (FAM) - Nível Brasil\n",
|
||||||
"Conforme o livro base *Airline Operations and Scheduling*, o problema de alocação de frotas busca atribuir os tipos de aeronaves às pernas de voo visando minimizar o custo e as perdas de receita (spill). Como temos apenas um tipo (C-97), nosso problema adapta-se a uma **alocação de esquadrões (e suas respectivas bases)** aos voos requeridos para minimizar o custo total de operação, que é proporcional à distância voada.\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",
|
||||||
"\n",
|
"\n",
|
||||||
"**Modelo Matemático:**\n",
|
"**Modelo Matemático:**\n",
|
||||||
"- **Variáveis de decisão:** $X_{s, r}$ (Quantidade de voos operados pelo esquadrão $s$ no trecho $r$)\n",
|
"- **Variáveis de decisão:** $X_r$ (Quantidade de voos alocados no trecho $r$)\n",
|
||||||
"- **Função Objetivo:** Minimizar a distância total (Distância de posicionamento da base + Distância do trecho + Retorno para a base).\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",
|
"- **Restrições:**\n",
|
||||||
" 1. O número de voos alocados para o trecho deve suprir a demanda média (Média PAX / Capacidade do C-97).\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 a um esquadrão não pode exceder o número de aeronaves disponíveis naquele esquadrão no dia.\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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 6,
|
"execution_count": 6,
|
||||||
"id": "be919c7c",
|
"id": "26694904",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"execution": {
|
"execution": {
|
||||||
"iopub.execute_input": "2026-06-07T23:27:30.633209Z",
|
"iopub.execute_input": "2026-06-07T23:32:34.432272Z",
|
||||||
"iopub.status.busy": "2026-06-07T23:27:30.633086Z",
|
"iopub.status.busy": "2026-06-07T23:32:34.432145Z",
|
||||||
"iopub.status.idle": "2026-06-07T23:27:30.740716Z",
|
"iopub.status.idle": "2026-06-07T23:32:34.456734Z",
|
||||||
"shell.execute_reply": "2026-06-07T23:27:30.739554Z"
|
"shell.execute_reply": "2026-06-07T23:32:34.456266Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -528,41 +528,23 @@
|
|||||||
"name": "stdout",
|
"name": "stdout",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"== RESULTADO DO FLEET ASSIGNMENT ==\n",
|
"== RESULTADO DO FLEET ASSIGNMENT (NÍVEL BRASIL) ==\n",
|
||||||
"Status: Optimal\n",
|
"Status: Optimal\n",
|
||||||
"Distância Total Minimizada: 15903.36 km\n",
|
"Distância Total da Operação: 5703.02 km\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Alocações (Voos Diários):\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",
|
||||||
"\n",
|
"\n",
|
||||||
"Trecho: SBGL-SBBR (Voos necessários: 1)\n",
|
"Total de voos alocados na malha diária: 10 de 14 aeronaves disponíveis.\n"
|
||||||
" -> ETA6 (Base SBBR): 1 voo(s)\n",
|
|
||||||
"\n",
|
|
||||||
"Trecho: SBBR-SBGL (Voos necessários: 1)\n",
|
|
||||||
" -> ETA2 (Base SBNT): 1 voo(s)\n",
|
|
||||||
"\n",
|
|
||||||
"Trecho: SBBR-SBGR (Voos necessários: 1)\n",
|
|
||||||
" -> ETA5 (Base SBCO): 1 voo(s)\n",
|
|
||||||
"\n",
|
|
||||||
"Trecho: SBGR-SBBR (Voos necessários: 1)\n",
|
|
||||||
" -> ETA6 (Base SBBR): 1 voo(s)\n",
|
|
||||||
"\n",
|
|
||||||
"Trecho: SBGL-SBGR (Voos necessários: 1)\n",
|
|
||||||
" -> ETA3 (Base SBGL): 1 voo(s)\n",
|
|
||||||
"\n",
|
|
||||||
"Trecho: SBSJ-SBBR (Voos necessários: 1)\n",
|
|
||||||
" -> ETA6 (Base SBBR): 1 voo(s)\n",
|
|
||||||
"\n",
|
|
||||||
"Trecho: SBGR-SBGL (Voos necessários: 1)\n",
|
|
||||||
" -> PAMA SP (Base SBGR): 1 voo(s)\n",
|
|
||||||
"\n",
|
|
||||||
"Trecho: SBSJ-SBGL (Voos necessários: 1)\n",
|
|
||||||
" -> ETA3 (Base SBGL): 1 voo(s)\n",
|
|
||||||
"\n",
|
|
||||||
"Trecho: SBGL-SBSJ (Voos necessários: 1)\n",
|
|
||||||
" -> ETA3 (Base SBGL): 1 voo(s)\n",
|
|
||||||
"\n",
|
|
||||||
"Trecho: SBBR-SBAN (Voos necessários: 1)\n",
|
|
||||||
" -> ETA6 (Base SBBR): 1 voo(s)\n"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -570,7 +552,7 @@
|
|||||||
"# Preparação dos dados para o modelo\n",
|
"# Preparação dos dados para o modelo\n",
|
||||||
"rotas_lista = top_trechos['Trecho'].tolist()\n",
|
"rotas_lista = top_trechos['Trecho'].tolist()\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Dicionário de distâncias\n",
|
"# Dicionário de distâncias apenas dos trechos diretos\n",
|
||||||
"def calc_distancia(icao1, icao2):\n",
|
"def calc_distancia(icao1, icao2):\n",
|
||||||
" try:\n",
|
" try:\n",
|
||||||
" lat1, lon1, alt1 = df_aeroportos.loc[icao1, ['lat', 'lon', 'elevation']]\n",
|
" lat1, lon1, alt1 = df_aeroportos.loc[icao1, ['lat', 'lon', 'elevation']]\n",
|
||||||
@@ -583,52 +565,46 @@
|
|||||||
"distancias_voo = {}\n",
|
"distancias_voo = {}\n",
|
||||||
"for r in rotas_lista:\n",
|
"for r in rotas_lista:\n",
|
||||||
" origem, destino = r.split('-')\n",
|
" origem, destino = r.split('-')\n",
|
||||||
" dist_trecho = calc_distancia(origem, destino)\n",
|
" distancias_voo[r] = calc_distancia(origem, destino)\n",
|
||||||
" for esq, base in bases_esquadroes.items():\n",
|
|
||||||
" dist_ida = calc_distancia(base, origem) if base != origem else 0\n",
|
|
||||||
" dist_volta = calc_distancia(destino, base) if base != destino else 0\n",
|
|
||||||
" distancias_voo[(esq, r)] = dist_ida + dist_trecho + dist_volta\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"# Demanda de voos por rota (Média diária)\n",
|
"# Demanda de voos por rota\n",
|
||||||
"voos_requeridos = {}\n",
|
"voos_requeridos = {}\n",
|
||||||
"for _, row in top_trechos.iterrows():\n",
|
"for _, row in top_trechos.iterrows():\n",
|
||||||
" # Arredondamento para cima da (demanda / capacidade)\n",
|
|
||||||
" voos = math.ceil(row['Media_PAX_Dias_Operados'] / CAPACIDADE_C97)\n",
|
" voos = math.ceil(row['Media_PAX_Dias_Operados'] / CAPACIDADE_C97)\n",
|
||||||
" # Pelo menos 1 voo para suprir a demanda da rota selecionada\n",
|
|
||||||
" voos_requeridos[row['Trecho']] = max(1, voos)\n",
|
" voos_requeridos[row['Trecho']] = max(1, voos)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# MODELAGEM COM PULP\n",
|
"# MODELAGEM COM PULP\n",
|
||||||
"prob = pulp.LpProblem(\"Fleet_Assignment_C97\", pulp.LpMinimize)\n",
|
"prob = pulp.LpProblem(\"Fleet_Assignment_C97_Brasil\", pulp.LpMinimize)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Variáveis\n",
|
"# Variáveis (número de voos em cada rota)\n",
|
||||||
"X = pulp.LpVariable.dicts(\"X\", [(esq, r) for esq in aeronaves_por_esquadrao.keys() for r in rotas_lista], lowBound=0, cat='Integer')\n",
|
"X = pulp.LpVariable.dicts(\"X\", rotas_lista, lowBound=0, cat='Integer')\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Função Objetivo\n",
|
"# Função Objetivo: Minimizar distância voada nos trechos\n",
|
||||||
"prob += pulp.lpSum([distancias_voo[(esq, r)] * X[(esq, r)] for esq in aeronaves_por_esquadrao.keys() for r in rotas_lista])\n",
|
"prob += pulp.lpSum([distancias_voo[r] * X[r] for r in rotas_lista])\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Restrição 1: Suprir a demanda da rota\n",
|
"# Restrição 1: Suprir a demanda\n",
|
||||||
"for r in rotas_lista:\n",
|
"for r in rotas_lista:\n",
|
||||||
" prob += pulp.lpSum([X[(esq, r)] for esq in aeronaves_por_esquadrao.keys()]) >= voos_requeridos[r]\n",
|
" prob += X[r] >= voos_requeridos[r]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Restrição 2: Limite de aeronaves por esquadrão\n",
|
"# Restrição 2: Limite global de frota (nível Brasil)\n",
|
||||||
"for esq in aeronaves_por_esquadrao.keys():\n",
|
"prob += pulp.lpSum([X[r] for r in rotas_lista]) <= total_aeronaves\n",
|
||||||
" prob += pulp.lpSum([X[(esq, r)] for r in rotas_lista]) <= aeronaves_por_esquadrao[esq]\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"# Solução\n",
|
"# Solução\n",
|
||||||
"prob.solve(pulp.PULP_CBC_CMD(msg=False))\n",
|
"prob.solve(pulp.PULP_CBC_CMD(msg=False))\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Exibição dos resultados\n",
|
"# Exibição dos resultados\n",
|
||||||
"print(\"== RESULTADO DO FLEET ASSIGNMENT ==\")\n",
|
"print(\"== RESULTADO DO FLEET ASSIGNMENT (NÍVEL BRASIL) ==\")\n",
|
||||||
"print(f\"Status: {pulp.LpStatus[prob.status]}\")\n",
|
"print(f\"Status: {pulp.LpStatus[prob.status]}\")\n",
|
||||||
"print(f\"Distância Total Minimizada: {pulp.value(prob.objective):.2f} km\\n\")\n",
|
"print(f\"Distância Total da Operação: {pulp.value(prob.objective):.2f} km\\n\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(\"Alocações (Voos Diários):\")\n",
|
"print(\"Alocações (Voos Diários Globais):\")\n",
|
||||||
|
"voos_totais = 0\n",
|
||||||
"for r in rotas_lista:\n",
|
"for r in rotas_lista:\n",
|
||||||
" print(f\"\\nTrecho: {r} (Voos necessários: {voos_requeridos[r]})\")\n",
|
" qtd = int(X[r].varValue)\n",
|
||||||
" for esq in aeronaves_por_esquadrao.keys():\n",
|
" voos_totais += qtd\n",
|
||||||
" qtd = int(X[(esq, r)].varValue)\n",
|
" print(f\"Trecho: {r:10} | Demanda Média PAX: {voos_requeridos[r]*CAPACIDADE_C97:3.0f} | Alocados: {qtd} voo(s)\")\n",
|
||||||
" if qtd > 0:\n",
|
"\n",
|
||||||
" print(f\" -> {esq} (Base {bases_esquadroes[esq]}): {qtd} voo(s)\")\n"
|
"print(f\"\\nTotal de voos alocados na malha diária: {voos_totais} de {total_aeronaves} aeronaves disponíveis.\")\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user