From e478cc72e07a90c5cb848d02298a51e2e05e3c90 Mon Sep 17 00:00:00 2001 From: Luciano Silva do Lago Date: Sun, 7 Jun 2026 20:41:17 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20adiciona=20quadro=20de=20hor=C3=A1rios?= =?UTF-8?q?=20formatado=20com=20tabulate=20ao=20Modelo=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- create_nb.py | 76 +++++++++++++++++++++ modelos.ipynb | 179 +++++++++++++++++++++++++++++++++++++------------ pyproject.toml | 1 + uv.lock | 11 +++ 4 files changed, 223 insertions(+), 44 deletions(-) diff --git a/create_nb.py b/create_nb.py index fe1fc8e..0cf90e8 100644 --- a/create_nb.py +++ b/create_nb.py @@ -371,6 +371,82 @@ for i in aeroportos_malha: gerou_vazio = True if not gerou_vazio: print(" Nenhum voo de reposicionamento vazio foi necessário! A malha já é circular/balanceada.") + +# GERAR TABELA DE HORÁRIOS COM TABULATE E DATETIME +from tabulate import tabulate +import datetime + +# 1. Construir a lista de todos os voos a serem realizados +voos_para_fazer = [] + +# Cópia para saber quantos voos de passageiros faltam alocar +D_copy = {k: v for k, v in D.items()} + +for i in aeroportos_malha: + for j in aeroportos_malha: + total_voos = D[(i, j)] + int(Y[(i, j)].varValue) + for _ in range(total_voos): + if D_copy[(i, j)] > 0: + tipo = 'Passageiro' + D_copy[(i, j)] -= 1 + else: + tipo = 'Vazio (Reposicionamento)' + voos_para_fazer.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo[(i, j)]}) + +# 2. Simular a alocação sequencial de horários +tabela_horarios = [] +aeronave_id = 1 + +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']) + hora_chegada = hora_atual + tempo_delta + + tabela_horarios.append([ + f"Aeronave {aeronave_id}", + voo_atual['origem'], + voo_atual['destino'], + voo_atual['tipo'], + hora_atual.strftime('%H:%M'), + hora_chegada.strftime('%H:%M'), + f"{voo_atual['tempo']:.2f} h" + ]) + + hora_atual = hora_chegada + local_atual = voo_atual['destino'] + + # Continuar traçando o caminho para esta aeronave até que não caiba mais nas 24h + while True: + 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: + prox_voo = voos_para_fazer.pop(idx) + break + + if prox_voo: + tempo_delta = datetime.timedelta(hours=prox_voo['tempo']) + hora_chegada = hora_atual + tempo_delta + tabela_horarios.append([ + f"Aeronave {aeronave_id}", + prox_voo['origem'], + prox_voo['destino'], + prox_voo['tipo'], + hora_atual.strftime('%H:%M'), + hora_chegada.strftime('%H:%M'), + f"{prox_voo['tempo']:.2f} h" + ]) + hora_atual = hora_chegada + local_atual = prox_voo['destino'] + else: + break + + aeronave_id += 1 + +print("\\n== QUADRO DE HORÁRIOS DIÁRIOS ==") +print(tabulate(tabela_horarios, headers=["Aeronave", "Origem", "Destino", "Tipo", "Partida", "Chegada", "Duração"], tablefmt="github")) """)) nb.cells = cells diff --git a/modelos.ipynb b/modelos.ipynb index bd762d4..265188b 100644 --- a/modelos.ipynb +++ b/modelos.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "07d7e46d", + "id": "d163e704", "metadata": {}, "source": [ "# Alocação de Frotas (Fleet Scheduling) - Aeronaves C-97\n", @@ -21,13 +21,13 @@ { "cell_type": "code", "execution_count": 1, - "id": "3f9fa4c6", + "id": "1cda871c", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:37:16.312196Z", - "iopub.status.busy": "2026-06-07T23:37:16.311487Z", - "iopub.status.idle": "2026-06-07T23:37:16.933888Z", - "shell.execute_reply": "2026-06-07T23:37:16.933301Z" + "iopub.execute_input": "2026-06-07T23:41:13.265917Z", + "iopub.status.busy": "2026-06-07T23:41:13.265674Z", + "iopub.status.idle": "2026-06-07T23:41:13.946892Z", + "shell.execute_reply": "2026-06-07T23:41:13.946177Z" } }, "outputs": [], @@ -53,7 +53,7 @@ }, { "cell_type": "markdown", - "id": "6054dba5", + "id": "3aa25feb", "metadata": {}, "source": [ "## 1. Leitura e Limpeza dos Dados\n", @@ -63,13 +63,13 @@ { "cell_type": "code", "execution_count": 2, - "id": "da258d7f", + "id": "5f12213f", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:37:16.935502Z", - "iopub.status.busy": "2026-06-07T23:37:16.935296Z", - "iopub.status.idle": "2026-06-07T23:37:17.297220Z", - "shell.execute_reply": "2026-06-07T23:37:17.296482Z" + "iopub.execute_input": "2026-06-07T23:41:13.949035Z", + "iopub.status.busy": "2026-06-07T23:41:13.948756Z", + "iopub.status.idle": "2026-06-07T23:41:14.387479Z", + "shell.execute_reply": "2026-06-07T23:41:14.386681Z" } }, "outputs": [], @@ -101,7 +101,7 @@ }, { "cell_type": "markdown", - "id": "2424db2d", + "id": "29a370cf", "metadata": {}, "source": [ "## 2. Análise dos Esquadrões e Matrículas (Aeronaves)\n", @@ -111,13 +111,13 @@ { "cell_type": "code", "execution_count": 3, - "id": "8b308e12", + "id": "4f559f60", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:37:17.298752Z", - "iopub.status.busy": "2026-06-07T23:37:17.298630Z", - "iopub.status.idle": "2026-06-07T23:37:17.310776Z", - "shell.execute_reply": "2026-06-07T23:37:17.310206Z" + "iopub.execute_input": "2026-06-07T23:41:14.388969Z", + "iopub.status.busy": "2026-06-07T23:41:14.388784Z", + "iopub.status.idle": "2026-06-07T23:41:14.401597Z", + "shell.execute_reply": "2026-06-07T23:41:14.401016Z" } }, "outputs": [ @@ -176,7 +176,7 @@ }, { "cell_type": "markdown", - "id": "4909bfef", + "id": "3d637614", "metadata": {}, "source": [ "## 3. Análise Estatística das Demandas Diárias\n", @@ -187,13 +187,13 @@ { "cell_type": "code", "execution_count": 4, - "id": "b38abd07", + "id": "ca8b936f", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:37:17.312196Z", - "iopub.status.busy": "2026-06-07T23:37:17.312078Z", - "iopub.status.idle": "2026-06-07T23:37:17.621631Z", - "shell.execute_reply": "2026-06-07T23:37:17.620923Z" + "iopub.execute_input": "2026-06-07T23:41:14.403000Z", + "iopub.status.busy": "2026-06-07T23:41:14.402870Z", + "iopub.status.idle": "2026-06-07T23:41:14.726896Z", + "shell.execute_reply": "2026-06-07T23:41:14.726161Z" } }, "outputs": [ @@ -424,7 +424,7 @@ }, { "cell_type": "markdown", - "id": "6bab0068", + "id": "782a4736", "metadata": {}, "source": [ "## Diagrama de Rede dos Trechos\n", @@ -434,13 +434,13 @@ { "cell_type": "code", "execution_count": 5, - "id": "6198127c", + "id": "9fedd535", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:37:17.623083Z", - "iopub.status.busy": "2026-06-07T23:37:17.622958Z", - "iopub.status.idle": "2026-06-07T23:37:17.740324Z", - "shell.execute_reply": "2026-06-07T23:37:17.739229Z" + "iopub.execute_input": "2026-06-07T23:41:14.728337Z", + "iopub.status.busy": "2026-06-07T23:41:14.728199Z", + "iopub.status.idle": "2026-06-07T23:41:14.849420Z", + "shell.execute_reply": "2026-06-07T23:41:14.848842Z" } }, "outputs": [ @@ -497,7 +497,7 @@ }, { "cell_type": "markdown", - "id": "35633067", + "id": "551febf3", "metadata": {}, "source": [ "## 4. Fleet Assignment Model (FAM) - Nível Brasil\n", @@ -514,13 +514,13 @@ { "cell_type": "code", "execution_count": 6, - "id": "c9a2018d", + "id": "bb15ee0b", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:37:17.742588Z", - "iopub.status.busy": "2026-06-07T23:37:17.742459Z", - "iopub.status.idle": "2026-06-07T23:37:17.764890Z", - "shell.execute_reply": "2026-06-07T23:37:17.764350Z" + "iopub.execute_input": "2026-06-07T23:41:14.850971Z", + "iopub.status.busy": "2026-06-07T23:41:14.850776Z", + "iopub.status.idle": "2026-06-07T23:41:14.876417Z", + "shell.execute_reply": "2026-06-07T23:41:14.875894Z" } }, "outputs": [ @@ -609,7 +609,7 @@ }, { "cell_type": "markdown", - "id": "666ba740", + "id": "37f9e364", "metadata": {}, "source": [ "## 5. Modelo 2: Minimização de Aeronaves\n", @@ -623,13 +623,13 @@ { "cell_type": "code", "execution_count": 7, - "id": "7b9b8019", + "id": "08433efd", "metadata": { "execution": { - "iopub.execute_input": "2026-06-07T23:37:17.766458Z", - "iopub.status.busy": "2026-06-07T23:37:17.766338Z", - "iopub.status.idle": "2026-06-07T23:37:19.072202Z", - "shell.execute_reply": "2026-06-07T23:37:19.071522Z" + "iopub.execute_input": "2026-06-07T23:41:14.877926Z", + "iopub.status.busy": "2026-06-07T23:41:14.877797Z", + "iopub.status.idle": "2026-06-07T23:41:16.250728Z", + "shell.execute_reply": "2026-06-07T23:41:16.250087Z" } }, "outputs": [ @@ -646,7 +646,22 @@ "Taxa de ocupação da frota (Tempo Voo / Tempo Disponível): 69.6%\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: 2.1h\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 | SBSJ | Passageiro | 01:43 | 02:24 | 0.69 h |\n", + "| Aeronave 1 | SBSJ | SBGL | Passageiro | 02:24 | 03:06 | 0.69 h |\n", + "| Aeronave 1 | SBGL | SBBR | Passageiro | 03:06 | 05:26 | 2.33 h |\n", + "| Aeronave 1 | SBBR | SBGL | Passageiro | 05:26 | 07:45 | 2.33 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 | SBAN | SBSJ | Vazio (Reposicionamento) | 00:00 | 02:08 | 2.14 h |\n", + "| Aeronave 3 | SBSJ | SBBR | Passageiro | 02:08 | 04:17 | 2.15 h |\n", + "| Aeronave 3 | SBBR | SBAN | Passageiro | 04:17 | 04:35 | 0.30 h |\n" ] } ], @@ -739,7 +754,83 @@ " 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", " 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ário! A malha já é circular/balanceada.\")\n", + "\n", + "# GERAR TABELA DE HORÁRIOS 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", + "D_copy = {k: v for k, v in D.items()}\n", + "\n", + "for i in aeroportos_malha:\n", + " for j in aeroportos_malha:\n", + " total_voos = D[(i, j)] + int(Y[(i, j)].varValue)\n", + " for _ in range(total_voos):\n", + " if D_copy[(i, j)] > 0:\n", + " tipo = 'Passageiro'\n", + " 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", + "\n", + "# 2. Simular a alocação sequencial de horários\n", + "tabela_horarios = []\n", + "aeronave_id = 1\n", + "\n", + "while voos_para_fazer:\n", + " 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", + " hora_chegada = hora_atual + tempo_delta\n", + " \n", + " tabela_horarios.append([\n", + " f\"Aeronave {aeronave_id}\",\n", + " voo_atual['origem'],\n", + " voo_atual['destino'],\n", + " voo_atual['tipo'],\n", + " hora_atual.strftime('%H:%M'),\n", + " hora_chegada.strftime('%H:%M'),\n", + " f\"{voo_atual['tempo']:.2f} h\"\n", + " ])\n", + " \n", + " 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", + " while True:\n", + " 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", + " prox_voo = voos_para_fazer.pop(idx)\n", + " break\n", + " \n", + " if prox_voo:\n", + " tempo_delta = datetime.timedelta(hours=prox_voo['tempo'])\n", + " hora_chegada = hora_atual + tempo_delta\n", + " tabela_horarios.append([\n", + " f\"Aeronave {aeronave_id}\",\n", + " prox_voo['origem'],\n", + " prox_voo['destino'],\n", + " prox_voo['tipo'],\n", + " hora_atual.strftime('%H:%M'),\n", + " hora_chegada.strftime('%H:%M'),\n", + " f\"{prox_voo['tempo']:.2f} h\"\n", + " ])\n", + " hora_atual = hora_chegada\n", + " local_atual = prox_voo['destino']\n", + " else:\n", + " break\n", + " \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" ] } ], diff --git a/pyproject.toml b/pyproject.toml index 09677e0..04b848c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,5 +13,6 @@ dependencies = [ "pulp>=3.3.2", "pymap3d>=3.2.0", "seaborn>=0.13.2", + "tabulate>=0.10.0", "vincenty>=0.1.4", ] diff --git a/uv.lock b/uv.lock index 1deca76..feac99b 100644 --- a/uv.lock +++ b/uv.lock @@ -176,6 +176,7 @@ dependencies = [ { name = "pulp" }, { name = "pymap3d" }, { name = "seaborn" }, + { name = "tabulate" }, { name = "vincenty" }, ] @@ -189,6 +190,7 @@ requires-dist = [ { name = "pulp", specifier = ">=3.3.2" }, { name = "pymap3d", specifier = ">=3.2.0" }, { name = "seaborn", specifier = ">=0.13.2" }, + { name = "tabulate", specifier = ">=0.10.0" }, { name = "vincenty", specifier = ">=0.1.4" }, ] @@ -1965,6 +1967,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + [[package]] name = "terminado" version = "0.18.1"