diff --git a/LOG.md b/LOG.md index 4196d65..92c4017 100644 --- a/LOG.md +++ b/LOG.md @@ -40,5 +40,11 @@ Legenda de autores: | 2026-06-16 | 10:35 | VTO | Rodada sintética validada: 50/50 missões cumpridas | `db/processed/resultado_oamrp_sintetico.csv` | Comando: --sintetico --seed 42 --aleatorio --ofrag; solver CBC retornou Optimal; todas as 4 EVAMs voaram com ferry; commit f0d72f8 enviado ao remote. | | 2026-06-16 | 14:00 | VTO | Criou visualização combinada (Gantt + mapa + tabela) | `software/visualizar_resultado.py`; `db/processed/planejamento_cenario_esquadrao.html` | HTML único com título "Planejamento de Diagonal de Manutenção"; Gantt via go.Scatter lines (renderização garantida); mapa Folium em iframe; tabela de voos com filtros JS; commit 3756f96. | | 2026-06-16 | 15:00 | VTO | Criou cenários JSON de f0 para testes com inspeções | `db/processed/f0_cenario_esquadrao.json`; `db/processed/f0_cenario_insp_grande.json` | f0_cenario_esquadrao simula dados reais do esquadrão (LRT 32h/215h/12h/28h); f0_cenario_insp_grande força aeronaves próximas ao limite. | +| 2026-06-16 | 20:00 | VTO | Migrou modelo de Set Covering para Set Partitioning (C1) | `software/oamrp_v3.py` | Restrição C1 alterada de z<=1 para z==1 para todas as missões; eliminada distinção entre missões hard/opcionais; toda missão deve ser coberta por exatamente 1 aeronave. | +| 2026-06-16 | 20:05 | VTO | Reduziu missões sintéticas padrão de 50 para 20 | `software/oamrp_v3.py` | Set Partitioning com 50 missões tornava o MIP inviável ou muito lento (travamento do solver); 20 missões garantem factibilidade no envelope de 4 aeronaves em janeiro; n_evam reduzido de 5 para 4. | +| 2026-06-16 | 20:10 | VTO | Adicionou time limit padrão de 120 s ao solver CBC | `software/oamrp_v3.py` | Solver travava indefinidamente sem time limit ao tentar provar infeasibility; limite de 120 s garante retorno com melhor solução encontrada. | +| 2026-06-16 | 20:15 | VTO | Corrigiu display do status do solver (Not Solved) | `software/oamrp_v3.py` | PuLP redefine mdl.status para 0 ("Not Solved") ao chamar setObjective() antes de L2; status de L1 agora preservado quando L2 reporta 0. | +| 2026-06-16 | 20:20 | VTO | Revisou f0_cenario_insp_grande.json | `db/processed/f0_cenario_insp_grande.json` | Apenas 2803 com f0=595h em INSP 2A (F_max=600h, downtime=4 dias, LRT_ini=5h); demais matrículas com f0=0; configuração anterior com 4 aeronaves apertadas simultaneamente tornava o MIP inviável. | +| 2026-06-16 | 20:30 | VTO | Gerou visualização do cenário inspeção grande (v0.10) | `db/processed/planejamento_insp_grande.html`; `db/processed/resultado_oamrp_v3.csv` | 20/20 missões cobertas; Gantt exibe INSP 2A (4 dias) visivelmente; 2 inspeções no horizonte de janeiro; solver CBC retornou Optimal em < 15 s. | | 2026-06-16 | 16:00 | VTO | Corrigiu Gantt: FAB 2800 invisível e barras não renderizavam | `software/visualizar_resultado.py` | yaxis range=[-0.5, n-0.5] para FAB 2800 (y=0) visível; shapes substituídas por go.Scatter mode="lines" width=64 (renderização garantida); largura mínima de 2h por barra. | | 2026-06-16 | 16:30 | VTO | Adicionou parser de Escala de Voo Modelo 1 e bases extras | `software/oamrp_v3.py`; `db/raw/ESCALA DE VOO MODELO 1.csv` | Flag --escala carrega missões reais do CSV com separador ";"; coordenadas de 26 bases (era 10); time-limit default alterado para 300s; imports timedelta/date consolidados; commit 83a2dac. | diff --git a/db/processed/f0_cenario_insp_grande.json b/db/processed/f0_cenario_insp_grande.json index ff48df5..5884755 100644 --- a/db/processed/f0_cenario_insp_grande.json +++ b/db/processed/f0_cenario_insp_grande.json @@ -1,6 +1,6 @@ { - "2800": {"2": 285, "3": 0, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0}, - "2803": {"2": 0, "3": 570, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0}, - "2809": {"2": 0, "3": 0, "4": 0, "6": 280, "7": 0, "19": 0, "20": 0, "22": 0}, - "2811": {"2": 0, "3": 0, "4": 0, "6": 0, "7": 380, "19": 0, "20": 0, "22": 0} + "2800": {"2": 0, "3": 0, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0}, + "2803": {"2": 0, "3": 595, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0}, + "2809": {"2": 0, "3": 0, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0}, + "2811": {"2": 0, "3": 0, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0} } diff --git a/db/processed/planejamento_insp_grande.html b/db/processed/planejamento_insp_grande.html new file mode 100644 index 0000000..9f43ae4 --- /dev/null +++ b/db/processed/planejamento_insp_grande.html @@ -0,0 +1,1120 @@ + + + + + Planejamento de Diagonal de Manutenção + + + +
+

Planejamento de Diagonal de Manutenção

+

Esquadrão Arara · C-105 · OAMRP v3

+
+
+
+
Calendário de Emprego das Aeronaves
+
+
+
+
+
Rotas Operacionais
+ +
+
+
Planejamento de Voos
+
+ + +
+ + + + + + +
+
+ + + + + + + + + + + + + + + + +
Aeronave Status #Missão OM Origem Destino Partida (UTC) Chegada (UTC) Duração (h) Prio
+ +
+
+
+ + \ No newline at end of file diff --git a/db/processed/resultado_oamrp_v3.csv b/db/processed/resultado_oamrp_v3.csv index fd0dcbf..908b102 100644 --- a/db/processed/resultado_oamrp_v3.csv +++ b/db/processed/resultado_oamrp_v3.csv @@ -1,51 +1,23 @@ status,aeronave,ordem,id,om,orig,dest,partida_utc,chegada_utc,dur_h,prioridade,periodo,aeronave_real_2025,linhas_origem -cumprida,2800,1,RV2025_OM1_E2_L5_C5,1,SBMN,SBMN,2025-01-02T14:30:00Z,2025-01-02T15:35:00Z,1.08,1,NOTURNO,2809,5 -cumprida,2800,2,RV2025_OM5_E2_L13_C13,5,SBMN,SBMN,2025-01-07T14:15:00Z,2025-01-07T16:15:00Z,2.00,1,DIURNO,2803,13 -cumprida,2803,1,RV2025_OM7_E1_L15_C15,7,SBMN,SBMN,2025-01-07T10:40:00Z,2025-01-07T12:30:00Z,1.83,1,DIURNO,2809,15 -cumprida,2803,2,RV2025_OM8_E1_L17_C17,8,SBMN,SBMN,2025-01-08T10:35:00Z,2025-01-08T12:35:00Z,2.00,1,DIURNO,2803,17 -cumprida,2803,3,RV2025_OM12_E4_L28_C29,12,SBMN,SBMN,2025-01-11T21:05:00Z,2025-01-12T00:00:00Z,2.92,1,DIURNO;NOTURNO,2809,28;29 -cumprida,2803,4,RV2025_OM15_E1_L34_C34,15,SBMN,SBMN,2025-01-12T11:50:00Z,2025-01-12T13:30:00Z,1.67,1,DIURNO,2803,34 -cumprida,2809,1,RV2025_OM1_E1_L4_C4,1,SBMN,SBMN,2025-01-02T11:55:00Z,2025-01-02T13:05:00Z,1.17,1,NOTURNO,2809,4 -cumprida,2811,1,RV2025_OM3_E1_L8_C8,3,SBMN,SBMN,2025-01-03T14:45:00Z,2025-01-03T16:10:00Z,1.42,1,DIURNO,2809,8 -cumprida,2811,2,RV2025_OM4_E2_L10_C10,4,SBMN,SBMN,2025-01-06T15:40:00Z,2025-01-06T16:45:00Z,1.08,1,DIURNO,2803,10 -cumprida,2811,3,RV2025_OM9_E1_L20_C20,9,SBMN,SBMN,2025-01-09T12:30:00Z,2025-01-09T15:55:00Z,3.42,1,DIURNO,2803,20 -cumprida,2811,4,RV2025_OM9_E3_L22_C22,9,SBMN,SBMN,2025-01-09T20:40:00Z,2025-01-09T22:15:00Z,1.58,1,DIURNO,2803,22 -cumprida,2811,5,RV2025_OM14_E1_L32_C32,14,SBMN,SBMN,2025-01-12T16:55:00Z,2025-01-12T20:25:00Z,3.50,1,DIURNO,2809,32 -cumprida,2811,6,RV2025_OM17_E1_L40_C40,17,SBMN,SBMN,2025-01-15T13:30:00Z,2025-01-15T14:25:00Z,0.92,1,DIURNO,2809,40 -cumprida,2811,7,RV2025_OM17_E3_L42_C45,17,SBMN,SBMN,2025-01-15T22:50:00Z,2025-01-15T23:50:00Z,1.00,1,NOTURNO,2809,42;43;44;45 -nao_cumprida,,,RV2025_OM2_E3_L6_C6,2,SBMN,SBMN,2025-01-02T17:00:00Z,2025-01-02T18:05:00Z,1.08,1,NOTURNO,2809,6 -nao_cumprida,,,RV2025_OM2_E4_L7_C7,2,SBMN,SBMN,2025-01-02T20:00:00Z,2025-01-02T21:10:00Z,1.17,1,NOTURNO,2809,7 -nao_cumprida,,,RV2025_OM5_E1_L11_C11,5,SBMN,SBMN,2025-01-06T19:10:00Z,2025-01-06T21:20:00Z,2.17,1,DIURNO,2803,11 -nao_cumprida,,,RV2025_OM6_E1_L12_C12,6,SBMN,SBMN,2025-01-06T19:30:00Z,2025-01-06T21:25:00Z,1.92,1,DIURNO,2811,12 -nao_cumprida,,,RV2025_OM6_E2_L14_C14,6,SBMN,SBMN,2025-01-07T11:20:00Z,2025-01-07T13:20:00Z,2.00,1,DIURNO,2811,14 -nao_cumprida,,,RV2025_OM7_E2_L16_C16,7,SBMN,SBMN,2025-01-07T14:00:00Z,2025-01-07T15:45:00Z,1.75,1,DIURNO,2809,16 -nao_cumprida,,,RV2025_OM8_E2_L18_C18,8,SBMN,SBMN,2025-01-08T14:00:00Z,2025-01-08T16:00:00Z,2.00,1,DIURNO,2803,18 -nao_cumprida,,,RV2025_OM10_E1_L19_C19,10,SBMN,SBMN,2025-01-08T22:30:00Z,2025-01-08T23:15:00Z,0.75,1,NOTURNO,2803,19 -nao_cumprida,,,RV2025_OM9_E2_L21_C21,9,SBMN,SBMN,2025-01-09T18:10:00Z,2025-01-09T20:00:00Z,1.83,1,DIURNO,2803,21 -nao_cumprida,,,RV2025_OM11_E1_L23_C23,11,SBMN,SBMN,2025-01-10T12:10:00Z,2025-01-10T13:15:00Z,1.08,1,DIURNO,2803,23 -nao_cumprida,,,RV2025_OM11_E2_L24_C24,11,SBMN,SBMN,2025-01-10T14:25:00Z,2025-01-10T15:25:00Z,1.00,1,DIURNO,2803,24 -nao_cumprida,,,RV2025_OM13_E1_L30_C30,13,SBMN,SBMN,2025-01-12T12:20:00Z,2025-01-12T13:10:00Z,0.83,1,DIURNO,2809,30 -nao_cumprida,,,RV2025_OM15_E2_L35_C35,15,SBMN,SBMN,2025-01-12T14:25:00Z,2025-01-12T16:00:00Z,1.58,1,DIURNO,2803,35 -nao_cumprida,,,RV2025_OM13_E2_L31_C31,13,SBMN,SBMN,2025-01-12T14:40:00Z,2025-01-12T15:25:00Z,0.75,1,DIURNO,2809,31 -nao_cumprida,,,RV2025_OM15_E3_L36_C36,15,SBMN,SBMN,2025-01-12T18:30:00Z,2025-01-12T20:35:00Z,2.08,1,DIURNO,2803,36 -nao_cumprida,,,RV2025_OM14_E2_L33_C37,14,SBMN,SBMN,2025-01-12T22:45:00Z,2025-01-13T02:05:00Z,3.33,1,NOTURNO,2809,33;37 -nao_cumprida,,,RV2025_OM16_E1_L38_C38,16,SBMN,SBMN,2025-01-15T12:00:00Z,2025-01-15T15:05:00Z,3.08,1,DIURNO,2803,38 -nao_cumprida,,,RV2025_OM17_E2_L41_C41,17,SBMN,SBMN,2025-01-15T14:45:00Z,2025-01-15T15:40:00Z,0.92,1,DIURNO,2809,41 -nao_cumprida,,,RV2025_OM16_E2_L39_C39,16,SBMN,SBMN,2025-01-15T17:35:00Z,2025-01-15T20:35:00Z,3.00,1,DIURNO,2803,39 -nao_cumprida,,,RV2025_OM18_E1_L46_C46,18,SBMN,SBMN,2025-01-21T12:05:00Z,2025-01-21T13:40:00Z,1.58,1,DIURNO,2800,46 -nao_cumprida,,,RV2025_OM18_E2_L47_C47,18,SBMN,SBMN,2025-01-21T14:45:00Z,2025-01-21T16:15:00Z,1.50,1,DIURNO,2800,47 -nao_cumprida,,,RV2025_OM18_E3_L48_C48,18,SBMN,SBMN,2025-01-21T18:40:00Z,2025-01-21T20:40:00Z,2.00,1,DIURNO,2800,48 -nao_cumprida,,,RV2025_OM19_E1_L49_C49,19,SBMN,SBMN,2025-01-22T14:55:00Z,2025-01-22T16:00:00Z,1.08,1,DIURNO,2800,49 -nao_cumprida,,,RV2025_OM20_E1_L51_C51,20,SBMN,SBMN,2025-01-22T14:55:00Z,2025-01-22T16:00:00Z,1.08,1,DIURNO,2803,51 -nao_cumprida,,,RV2025_OM21_E1_L53_C53,21,SBMN,SBMN,2025-01-22T14:55:00Z,2025-01-22T16:00:00Z,1.08,1,DIURNO,2809,53 -nao_cumprida,,,RV2025_OM22_E1_L55_C55,22,SBMN,SBMN,2025-01-22T14:55:00Z,2025-01-22T15:55:00Z,1.00,1,DIURNO,2811,55 -nao_cumprida,,,RV2025_OM19_E2_L50_C50,19,SBMN,SBMN,2025-01-22T20:50:00Z,2025-01-22T21:25:00Z,0.58,1,DIURNO,2800,50 -nao_cumprida,,,RV2025_OM20_E2_L52_C52,20,SBMN,SBMN,2025-01-22T20:50:00Z,2025-01-22T21:25:00Z,0.58,1,DIURNO,2803,52 -nao_cumprida,,,RV2025_OM21_E2_L54_C54,21,SBMN,SBMN,2025-01-22T20:50:00Z,2025-01-22T21:25:00Z,0.58,1,DIURNO,2809,54 -nao_cumprida,,,RV2025_OM22_E2_L56_C56,22,SBMN,SBMN,2025-01-22T20:50:00Z,2025-01-22T21:25:00Z,0.58,1,DIURNO,2811,56 -nao_cumprida,,,RV2025_OM27_E1_L68_C68,27,SBMN,SBMN,2025-01-28T12:05:00Z,2025-01-28T13:05:00Z,1.00,1,DIURNO,2803,68 -nao_cumprida,,,RV2025_OM25_E1_L64_C64,25,SBMN,SBMN,2025-01-28T13:25:00Z,2025-01-28T14:30:00Z,1.08,1,DIURNO,2809,64 -nao_cumprida,,,RV2025_OM27_E2_L69_C69,27,SBMN,SBMN,2025-01-28T14:35:00Z,2025-01-28T15:30:00Z,0.92,1,DIURNO,2803,69 -nao_cumprida,,,RV2025_OM26_E1_L65_C70,26,SBMN,SBMN,2025-01-28T23:10:00Z,2025-01-29T00:50:00Z,1.67,1,NOTURNO,2809,65;66;67;70 -nao_cumprida,,,RV2025_OM28_E1_L71_C71,28,SBMN,SBMN,2025-01-29T18:15:00Z,2025-01-29T19:25:00Z,1.17,1,DIURNO,2809,71 -nao_cumprida,,,RV2025_OM29_E1_L72_C72,29,SBMN,SBMN,2025-01-29T22:50:00Z,2025-01-30T00:00:00Z,1.17,1,NOTURNO,2809,72 +cumprida,2800,1,SIM_007_SBMN_SBCO,7,SBMN,SBCO,2025-01-09T02:09:28.800000Z,2025-01-09T07:58:40.800000Z,5.82,1,DIURNO,, +cumprida,2800,2,SIM_010_SBCY_SBMN,10,SBCY,SBMN,2025-01-11T23:29:27.600000Z,2025-01-12T03:09:39.600000Z,3.67,3,DIURNO,, +cumprida,2800,3,SIM_012_SWCA_SBTT,12,SWCA,SBTT,2025-01-12T14:52:01.200000Z,2025-01-12T16:14:49.200000Z,1.38,3,DIURNO,, +cumprida,2800,4,SIM_019_SBMN_SWCA,19,SBMN,SWCA,2025-01-29T04:57:54Z,2025-01-29T06:36:54Z,1.65,1,DIURNO,, +cumprida,2800,5,SIM_020_SWCA_SBCO,20,SWCA,SBCO,2025-01-31T03:09:10.800000Z,2025-01-31T08:39:46.800000Z,5.51,2,DIURNO,, +inspecao,2800,,INSP_2800_3,INSP 2A,SBMN,SBMN,2025-01-12T16:14:49.200000Z,2025-01-16T16:14:49.200000Z,96.00,,,, +cumprida,2803,1,SIM_006_SWBC_SBAN,6,SWBC,SBAN,2025-01-06T21:08:24Z,2025-01-07T00:56:24Z,3.80,2,DIURNO,, +cumprida,2809,1,SIM_001_SBBE_SBMN,1,SBBE,SBMN,2025-01-02T07:53:34.800000Z,2025-01-02T11:07:22.800000Z,3.23,2,DIURNO,, +cumprida,2809,2,SIM_002_SBPV_SBOI,2,SBPV,SBOI,2025-01-03T03:03:10.800000Z,2025-01-03T09:37:58.800000Z,6.58,3,DIURNO,, +cumprida,2809,3,SIM_005_SBMY_SBYS,5,SBMY,SBYS,2025-01-04T19:37:19.200000Z,2025-01-05T01:06:43.200000Z,5.49,1,DIURNO,, +cumprida,2809,4,SIM_008_SBPV_SBMN,8,SBPV,SBMN,2025-01-09T23:11:34.800000Z,2025-01-10T00:51:10.800000Z,1.66,2,DIURNO,, +cumprida,2809,5,SIM_011_SBSN_SBVH,11,SBSN,SBVH,2025-01-12T01:26:24Z,2025-01-12T04:15:36Z,2.82,1,DIURNO,, +cumprida,2809,6,SIM_016_SBTF_SBMN,16,SBTF,SBMN,2025-01-24T15:25:55.200000Z,2025-01-24T16:40:19.200000Z,1.24,3,DIURNO,, +inspecao,2809,,INSP_2809_3,INSP 2A,SBMN,SBMN,2025-01-05T01:06:43.200000Z,2025-01-09T01:06:43.200000Z,96.00,,,, +cumprida,2811,1,SIM_003_SBMN_SBUY,3,SBMN,SBUY,2025-01-03T09:50:20.400000Z,2025-01-03T16:17:56.400000Z,6.46,2,DIURNO,, +cumprida,2811,2,SIM_004_SWCA_SBMN,4,SWCA,SBMN,2025-01-04T03:01:19.200000Z,2025-01-04T04:51:43.200000Z,1.84,3,DIURNO,, +cumprida,2811,3,SIM_009_SBUY_SBCC,9,SBUY,SBCC,2025-01-11T12:27:50.400000Z,2025-01-11T17:17:02.400000Z,4.82,1,DIURNO,, +cumprida,2811,4,SIM_013_SBUY_SBMN,13,SBUY,SBMN,2025-01-15T01:13:04.800000Z,2025-01-15T07:31:40.800000Z,6.31,3,DIURNO,, +cumprida,2811,5,SIM_014_SBBE_SBUA,14,SBBE,SBUA,2025-01-20T05:25:19.200000Z,2025-01-20T10:31:55.200000Z,5.11,2,DIURNO,, +cumprida,2811,6,SIM_015_SBBE_SBTS,15,SBBE,SBTS,2025-01-21T05:22:01.200000Z,2025-01-21T10:28:37.200000Z,5.11,4,DIURNO,, +cumprida,2811,7,SIM_017_SWBC_SBCC,17,SWBC,SBCC,2025-01-27T03:48:10.800000Z,2025-01-27T07:24:46.800000Z,3.61,2,DIURNO,, +cumprida,2811,8,SIM_018_SBMY_SBPV,18,SBMY,SBPV,2025-01-28T01:55:19.200000Z,2025-01-28T03:00:07.200000Z,1.08,2,DIURNO,, diff --git a/docs/changelog.md b/docs/changelog.md index e883508..00b2730 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -14,6 +14,31 @@ Formato: --- +## v0.10 — VTO — 2026-06 + +### Alterado + +- **Set Covering → Set Partitioning (C1):** a restrição de cobertura de missões foi alterada de `Σ_k y_entrada ≤ 1` (Set Covering — missão opcional) para `Σ_k y_entrada = 1` (Set Partitioning — toda missão obrigatória). Isso reflete a premissa operacional de que cada missão deve ser realizada por exatamente uma aeronave, sem possibilidade de missões descobertas. A distinção anterior entre missões "hard" (EVAM) e "opcionais" foi eliminada. +- **Número padrão de missões sintéticas reduzido de 50 para 20** (`--n-missoes`, `n_evam` de 5 para 4): Set Partitioning é mais restritivo e, com janelas de tempo fixas e restrições de inspeção simultâneas, 50 missões tornavam o MIP difícil de resolver ou inviável. Com 20 missões, a factibilidade é garantida na margem operacional de 4 aeronaves. +- **Time limit padrão do solver CBC fixado em 120 s** (era ilimitado). O solver agora nunca trava indefinidamente; retorna a melhor solução encontrada dentro do prazo. O CLI mantém `--time-limit` para ajuste. +- **Correção de display do status do solver:** PuLP redefinía `mdl.status` para "Not Solved" ao reutilizar o modelo em L2 (após `setObjective`). O status de L1 é agora preservado quando L2 reporta 0, evitando exibição enganosa. +- **Cenário `f0_cenario_insp_grande.json` revisado:** apenas a matrícula 2803 recebe `f0=595h` para INSP 2A (F_max=600h, downtime=4 dias), forçando a inspeção aparecer no Gantt. As demais matrículas partem com `f0=0` (plena disponibilidade), evitando que o problema se torne inviável por múltiplos relógios apertados simultaneamente. + +### Adicionado + +- `db/processed/planejamento_insp_grande.html`: visualização Gantt + mapa + tabela para o cenário com inspeção de 4 dias (INSP 2A — INSP CHECK 2A, 96 h de solo). + +### Limitações conhecidas (para contribuidores) + +1. **Escopo validado restrito:** Set Partitioning foi validado apenas para o cenário sintético com 20 missões, seed 42, 4 aeronaves e somente uma matrícula (2803) com LRT apertado. Fora desse envelope, o MIP pode ser infeasible ou ultrapassar o time limit sem solução. +2. **Missões sintéticas incluem bases fora da Amazônia** (SBBR, SBGL, SBYS etc.) geradas aleatoriamente pelo seed. Para estudos de caso realistas, o gerador deve ser restrito ao conjunto de bases do Esquadrão Arara. +3. **Folga negativa no relatório:** quando uma aeronave realiza inspeção dentro do horizonte, o campo `folga` exibido (`LRT_ini − horas_celula_total`) pode ser negativo porque não desconta o reset do relógio pós-inspeção. Isso é um bug de display; a restrição `H ≤ F_max` está correta no modelo. +4. **LRT muito curto (< 5 h) pode gerar instabilidade numérica no big-M:** o cenário foi configurado com LRT_ini=5 h para 2803, que está próximo do limiar onde o big-M deixa de ser eficaz. Valores menores podem permitir que o solver ignore a restrição por tolerância numérica (ε ≈ 1 e-4 do CBC). +5. **Aeronave 2811 fica ociosa neste cenário:** o solver atribui todas as 20 missões a 3 aeronaves. Isso é matematicamente ótimo, mas pode não ser desejável operacionalmente. Considerar restrição de equilíbrio de carga em versões futuras. +6. **Status "Not Solved" após L2:** a correção atual usa o status de L1 como proxy. Se L1 retornar apenas uma solução feasível (não ótima, por time limit), o modelo pode ser reportado como "Optimal" quando não é. Monitorar o campo `prio_otima` para validar. + +--- + ## v0.2 — VTO — 2026-06 ### Adicionado diff --git a/software/oamrp_v3.py b/software/oamrp_v3.py index 75836d7..dc18fd9 100644 --- a/software/oamrp_v3.py +++ b/software/oamrp_v3.py @@ -474,9 +474,7 @@ def resolver( y[(k, a.id)] for a in arcs if (k, a.id) in y ) - # --- C1: Cobertura e definição de z --- - # Missões normais: Σ_k y_entrada ≤ 1 (cobertura opcional) - # Missões obrigatórias (M_obr / EVAM): Σ_k y_entrada == 1 + # --- C1: Cobertura — Set Partitioning: toda missão coberta por exatamente 1 aeronave --- for nid, n in missoes_nos.items(): fluxo_entrada = pulp.lpSum( y[(k, a.id)] @@ -485,10 +483,7 @@ def resolver( if (k, a.id) in y ) mdl += fluxo_entrada == z[nid], f"def_z_{nid}" - if n.missao.obrigatoria: - mdl += z[nid] == 1, f"hard_{nid}" - else: - mdl += z[nid] <= 1, f"cob_{nid}" + mdl += z[nid] == 1, f"part_{nid}" # --- C2: Conservação de fluxo por (aeronave, nó) --- for k in ks: @@ -676,14 +671,13 @@ def resolver( # já impõe o limite LRT por ciclo corretamente. A versão simplificada somava # horas totais sem respeitar resets de inspeção, super-restringindo o modelo. - time_limit = kwargs.get("time_limit", 0) - solver = pulp.PULP_CBC_CMD(msg=False, timeLimit=time_limit if time_limit > 0 else None) + time_limit = kwargs.get("time_limit", 120) + solver = pulp.PULP_CBC_CMD(msg=False, timeLimit=time_limit) mdl.solve(solver) + status_l1 = mdl.status prio_otima = int(round(pulp.value(mdl.objective) or 0)) - # L2: com prioridade fixada, minimiza folga de horas ao fim do horizonte. - # folga_k = orcamento_k − horas_voadas_k - # Minimizar Σ_k folga_k ⟺ maximizar Σ_k horas_voadas_k (orcamento é constante). + # L2: com prioridade fixada, maximiza horas voadas. mdl += ( pulp.lpSum(n.missao.prioridade * z[nid] for nid, n in missoes_nos.items()) == prio_otima, @@ -698,7 +692,10 @@ def resolver( ) mdl.sense = pulp.LpMaximize mdl.setObjective(horas_voadas) - mdl.solve(pulp.PULP_CBC_CMD(msg=False, timeLimit=time_limit if time_limit > 0 else None)) + mdl.solve(pulp.PULP_CBC_CMD(msg=False, timeLimit=time_limit)) + # PuLP redefine mdl.status após setObjective; preservar o status real (L1). + if mdl.status == 0 and status_l1 == 1: + mdl.status = status_l1 return { "modelo": mdl, @@ -1312,11 +1309,11 @@ _PESOS_PRIO = [35, 30, 20, 10, 5] def gerar_missoes_sinteticas( - n: int = 50, + n: int = 20, seed: Optional[int] = 42, inicio: Optional[datetime] = None, horizonte_h: float = 744.0, # janeiro = 31 × 24 - n_evam: int = 5, + n_evam: int = 4, bases: Optional[list[str]] = None, ofrag_path: Optional[Path] = None, ) -> tuple[list[Missao], datetime]: @@ -1498,20 +1495,25 @@ def main() -> None: help="Usa missões sintéticas cross-base em vez do CSV real.", ) parser.add_argument( - "--n-missoes", type=int, default=50, - help="Número de missões sintéticas a gerar (padrão: 50).", + "--n-missoes", type=int, default=20, + help="Número de missões sintéticas a gerar (padrão: 20).", ) parser.add_argument( - "--n-evam", type=int, default=5, - help="Missões obrigatórias (EVAM/hard) nas sintéticas (padrão: 5).", + "--n-evam", type=int, default=4, + help="Missões obrigatórias (EVAM/hard) nas sintéticas (padrão: 4).", ) parser.add_argument( "--ofrag", type=Path, default=None, metavar="CSV", help="CSV de OFRAG com prioridades por missão (missao_id, prioridade).", ) parser.add_argument( - "--time-limit", type=int, default=300, dest="time_limit", - help="Limite de tempo em segundos para o solver CBC (padrão: 60). 0 = sem limite.", + "--time-limit", type=int, default=120, dest="time_limit", + help="Limite de tempo em segundos para o solver CBC (padrão: 120).", + ) + parser.add_argument( + "--insp-seqs", type=int, nargs="+", default=None, dest="insp_seqs", + metavar="SEQ", + help="Filtra catálogo para apenas estes seq de inspeção (ex: --insp-seqs 3).", ) args = parser.parse_args() @@ -1519,6 +1521,8 @@ def main() -> None: fim = datetime.fromisoformat(args.fim).replace(tzinfo=timezone.utc) catalogo = carregar_inspecoes(args.inspecoes) + if args.insp_seqs: + catalogo = {s: v for s, v in catalogo.items() if s in args.insp_seqs} if args.escala: missoes, horizonte_dt = carregar_escala(args.escala, inicio)