Geração e transformação de instâncias para o problema de escalonamento de workflows híbridos (VM + FX/serverless). Uma instância descreve um DAG de tarefas, artefatos de dados, máquinas virtuais disponíveis, perfis de execução serverless e faixas de custo de bucket.
O repositório tem duas frentes sobre o mesmo formato de instância:
| contexto | o que produz | código | dados | docs |
|---|---|---|---|---|
| sintético | instâncias geradas por modelo, com topologia controlada | src/synthetic/ |
data/instances/synthetic/ |
tabela abaixo |
| denethor | instâncias derivadas de execuções reais do workflow DENETHOR na AWS — medidas e preditas | src/denethor/ |
data/instances/denethor/, data/denethor_study/ |
tabela abaixo |
| compartilhado | o formato .txt e o que vale para qualquer instância |
src/shared/ |
— | formato_instancia.md |
src/synthetic/ e src/denethor/ não se enxergam: o que os dois precisam mora em
src/shared/. tests/test_package_boundaries.py trava essa direção.
Há uma única forma de invocar qualquer script deste repositório: como módulo, a partir
de src/.
cd src
python3 -m synthetic.cli.generate_instances_user --tier easy # gerar sintéticas
python3 -m denethor.cli.run_study --all # pipeline denethor
python3 -m shared.cli.instances_validation ../data/... # validar qualquer umapython3 src/<pacote>/cli/<script>.py não funciona: src/ não está no sys.path e o
import do próprio pacote falha com ModuleNotFoundError. A única exceção é
denethor/cli/run_study.py, que faz um sys.path.insert — e mesmo esse deve ser chamado
como módulo, para não haver duas convenções.
Os caminhos de dados passados na linha de comando são relativos ao diretório corrente,
que nessa forma é src/ — daí o ../data/... dos exemplos. O gerador do modo user é a
exceção: ancora entrada e saída na raiz do repositório via shared/repo.py, então não
recebe caminho nenhum.
Pendência conhecida.
synthetic.cli.generate_instances_randomesynthetic.cli.generate_instances_mermaidainda resolvemdata/…a partir do diretório corrente com caminho fixo, em vez derepo.ROOT. Rodados desrc/eles procuramsrc/data/…e falham (FileNotFoundError). Migrá-los parashared.repoé o conserto; até lá, os exemplos deste README para esses dois scripts descrevem a chamada pretendida, não uma que funcione hoje.
O formato completo das instâncias está documentado em docs/formato_instancia.md.
Gera instâncias a partir dos workflows descritos em data/instances/defs/instance_def_<tier>.txt.
Existem dois arquivos de definição: easy e ultrahard. medium e hard não são gerados:
são derivados da easy (próxima seção).
cd src
# Todos os workflows do tier (padrão: easy)
python3 -m synthetic.cli.generate_instances_user --tier easy
python3 -m synthetic.cli.generate_instances_user --tier ultrahard
# Ou apenas alguns IDs
python3 -m synthetic.cli.generate_instances_user --tier ultrahard --fx Synthetic_124 Synthetic_168Cada tier grava em data/instances/synthetic/user/<tier>/. As demais opções são
--skip-edge-cases, --output-dir e --seed.
Não regerar a
easy. Aeasydo disco teve o painel de VM trocado pelo painel real do DENETHOR (commit6b8f05d); o gerador ainda escreve o catálogo sintético (DEFAULT_VMS), então regerá-la devolveria o painel antigo. É pendência conhecida.
medium e hard são a easy com outro painel de VM (e, no hard, todos os tempos × 5).
DAG, tempos de tarefa, artefatos e perfis FX são os da easy, byte a byte; a única
variável entre os tiers é o painel — escolha × capacidade — e a escala. O perfil de cada
um é declarado em data/instances/defs/perfil_<tier>.txt (tipos do catálogo AWS, com
réplica, e o fator de tempo), e a derivação é determinística:
cd src
python3 -m synthetic.cli.derive_tier --tier medium # 10 VMs, 7 tipos, tempos da easy
python3 -m synthetic.cli.derive_tier --tier hard # 50 VMs (medium × 5), tempos × 5
python3 -m shared.cli.instances_effective_catalog ../data/instances/synthetic/user/{easy,medium,hard}O desenho, as medições que o sustentam e as baterias do Stratus estão em
docs/sintetico_tiers.md. As versões anteriores dos dois tiers
(geradas por definição própria) ficaram em medium/v1/ e hard/v1/.
Gera uma instância com DAG criado aleatoriamente a cada execução.
cd src && python3 -m synthetic.cli.generate_instances_randomOs arquivos são salvos em data/instances/synthetic/random/ com nome sequencial automático.
Vale a pendência de caminho descrita em Como rodar.
Os campos abaixo valem no bloco DEFAULTS: de um arquivo de definição e, por workflow, como sobrescrita:
| Campo | Descrição |
|---|---|
NUM_VMS |
Número de VMs incluídas na instância (10 no catálogo; acima disso ele é estendido pela mesma reta — ver docs/sintetico_catalogo_vm.md, seção 2.3) |
NUM_CONFIGS |
Número de configurações FX por tarefa (o teto depende do piso do FX_SLOWDOWN: 3 com piso 3.0, 9 com piso 8.0, 24 com piso 20.0 — ver a seção de testes) |
NUM_BUCKET_RANGES |
Número de faixas de preço do bucket |
USE_INTEGER_TIME |
Usa tempos inteiros (facilita leitura e depuração) |
FX_SLOWDOWN |
Faixa min,max de lentidão do FX em relação à VM de referência |
CPU_TIME |
Faixa min,max do tempo de CPU sorteado por tarefa |
READ_TIME |
Faixa min,max do tempo de leitura por dado |
WRITE_TIME |
Faixa min,max do tempo de escrita por dado |
As VMs são escolhidas de forma determinística: um tier com mais VMs recebe as mesmas do tier menor mais as seguintes do catálogo, para que os tiers só difiram na quantidade de recursos. O catálogo é ancorado nos preços on-demand da família t3 em sa-east-1 e estendido linearmente (custo/s = 5.167e-6 / cpu_slowdown), o que deixa o custo por unidade de trabalho de CPU igual em todas as VMs — o trade-off tempo × custo vem do I/O e da ociosidade, cobrados na tarifa da VM. A tabela completa, a análise de dominância e a conferência contra o preço real (incluindo duas ressalvas de fidelidade) estão em docs/sintetico_catalogo_vm.md.
O gerador do modo user ainda expõe TASK_ID_OFFSET / DATA_ID_OFFSET para renomear IDs ao escrever o arquivo final.
Após gerar cada arquivo, ambos os geradores recalculam automaticamente os limites max_running_time (TM) e max_financial_cost (CM) no cabeçalho da instância.
Cada arquivo começa com um bloco DEFAULTS: que vale para todos os seus workflows, seguido dos blocos de workflow. O trecho abaixo é o do instance_def_easy.txt:
DEFAULTS:
NUM_VMS: 5
NUM_CONFIGS: 3
NUM_BUCKET_RANGES: 3
USE_INTEGER_TIME: true
FX_SLOWDOWN: 3.0,10.0
CPU_TIME: 1,8
READ_TIME: 1,6
WRITE_TIME: 1,3
EDGE_CASES:
VM_TASKS_ONLY: Synthetic_007, Synthetic_022, Synthetic_032, Synthetic_060
VM_HIGH_COST: Synthetic_011
TM_EXCLUDES_OPTIMAL: Synthetic_022=60.0
--------------------------------
WORKFLOW_ID: Synthetic_016
TASKS: 6
DATA: 10
PATTERN: Map, Split, Merge (Cascading Diamonds)
CPU_TIME: 1,5
READ_TIME: 1,3
WRITE_TIME: 1,3
COMMENT: ...
---
t0: d0 -> d1,d2
t1: d1 -> d3
As linhas após --- seguem o padrão task_id: [inputs] -> [outputs]. As dependências do DAG são implícitas: uma tarefa só pode executar quando todos os seus dados de entrada estiverem disponíveis.
Bloco opcional, no mesmo nível do DEFAULTS:. Cada campo lista WORKFLOW_IDs que ganham, além da instância normal, uma instância extra com uma transformação de caso de borda aplicada por cima — gerada automaticamente pelo gerador do modo user, sem edição manual do .txt gerado:
| Campo | Sufixo do arquivo | Transformação |
|---|---|---|
VM_TASKS_ONLY |
_vm_tasks_only |
task_type forçado para 0 (só-VM) em toda tarefa, mesmos recursos do workflow no tier |
VM_HIGH_COST |
_vm_high_cost |
cost_per_second de toda VM somado a 9999, CM recalculado |
TM_EXCLUDES_OPTIMAL |
_tm_excludes_optimal |
max_running_time fixado no valor declarado (ID=segundos), ou reduzido à metade do calculado se nenhum for dado |
A variante é cópia da base com a transformação por cima, e não uma nova geração: o que ela pode diferir da base é exatamente o mecanismo sob teste, e nada mais. tests/test_edge_case_variants.py guarda essa propriedade.
Só este campo aceita um valor por workflow, na forma ID=segundos:
TM_EXCLUDES_OPTIMAL: Synthetic_022=60.0 # TM medido na mão
TM_EXCLUDES_OPTIMAL: Synthetic_022 # sem valor: usa a razão automática
O valor explícito existe porque saber qual TM de fato exclui a solução ótima exige rodar a heurística na instância e decidir na mão — a razão automática não sabe onde o ótimo está. Declarar no arquivo de definição torna esse ajuste manual reproduzível: regerar o tier passa a reproduzir o valor em vez de sobrescrevê-lo. Um valor não numérico, vazio ou não-positivo é erro (InvalidEdgeCaseValueError), assim como usar =valor nos outros dois campos, que o ignorariam em silêncio. Se o TM declarado não for menor que o calculado, o gerador emite um aviso — o arquivo continua válido, mas a variante não excluiria nada.
Sem valor explícito, a razão automática é um limite conservador seguro: update_file_bounds grava TM_final = TM_listsched × (1 + margem) — o teto do escalonamento mais barato. Como TM_final / (1 + margem) = TM_listsched, qualquer razão abaixo de 1 / (1 + margem) (0.8 com a margem atual de 0.25) deixa o TM abaixo de TM_listsched para qualquer instância, sem precisar calcular o ótimo real — daí a razão fixa de 0.5 (TM_EXCLUDES_OPTIMAL_RATIO, em src/synthetic/edge_cases.py).
Um WORKFLOW_ID referenciado em EDGE_CASES que não existe no arquivo é erro (load_definition levanta UnknownEdgeCaseWorkflowError; validate_file reporta o código E3).
campo no bloco do workflow > bloco DEFAULTS do arquivo
Qualquer campo do DEFAULTS pode ser repetido dentro de um workflow para valer só para ele — é o que Synthetic_016 faz com CPU_TIME e o que as topologias pesadas do ultrahard (Synthetic_124, Synthetic_168, Synthetic_264) fazem para esticar o horizonte.
Não existe um terceiro nível: o gerador não tem constantes de configuração. Um campo que não apareça nem no DEFAULTS nem no bloco do workflow faz load_definition levantar MissingSettingError apontando o campo e o workflow, e validate_file reportar o código F14. A configuração é sempre o que está escrito no arquivo de definição.
cd src
# do arquivo de definição do tier
python3 -m synthetic.cli.generate_instances_mermaid --tier ultrahard Synthetic_042 Synthetic_060
# de uma família de instâncias já gerada
python3 -m synthetic.cli.generate_instances_mermaid --family ultrahard --allVale a pendência de caminho descrita em Como rodar.
Escreve um .mermaid por workflow em data/instances/diagrams/mermaid/, com os dados como círculos e as
tarefas como retângulos, nos mesmos ids do arquivo de instância (t_100+, d_900+).
| Opção | Efeito |
|---|---|
--tier <easy|medium|ultrahard> |
lê o DAG de data/instances/defs/instance_def_<tier>.txt |
--family <nome> |
lê o DAG dos arquivos de instância da família já gerada (as opções válidas estão em INSTANCE_FAMILIES, no próprio script) |
--all |
todos os workflows da fonte, em vez dos IDs passados |
--no-labels |
exporta só a forma do DAG: nós sem rótulo, fundo branco, sem título nem moldura. Sai em data/instances/diagrams/mermaid_plain/ com sufixo _plain |
--out-subdir <nome> |
subpasta dentro do diretório de saída |
Os dois modos de leitura produzem o mesmo DAG; --family usa só os arquivos base, já
que as variantes de caso de borda não mudam a topologia, e cruza PATTERN/COMMENT
com o instance_def do tier de origem. A origem fica registrada numa linha %% SOURCE
no topo de cada arquivo.
Regerar um diagrama que já existe preserva os style/linkStyle ajustados à mão,
desde que o conjunto de nós não tenha mudado; se mudou, o arquivo antigo fica como
.bak ao lado.
Instâncias construídas a partir de execuções reais do workflow DENETHOR na AWS (Lambda para FX, EC2 para a VM de referência). Cadeia completa, da linha de log bruta ao arquivo validado: docs/denethor_geracao_instancias.md.
As famílias denethor são organizadas pelo front de seleção dos n arquivos de entrada
— a única coisa que muda entre as campanhas. Contagens conferidas no disco em 2026-08-27:
data/instances/ o produto — tudo que é instância
denethor/ achatada como no Stratus: front no nome da pasta
runs/ 42 as 33 reais (11 cada run1/run2/run3) + as 9 da
extensão E11 (só run1: 5 run4 medidas + 4 preditas) —
sufixo _r1/_r2/_r3 no nome; + CARIMBO_CONGELADO.md,
_readme.md, file_mapping*.txt e os manifestos CSV
runs_x100/ 20 escala ×100 coerente (só o front run1, sem sufixo)
runs_x1000/ 20 escala ×1000 coerente (idem)
first-n_bug/ 5 as I060–I100 originais, com o bug de proveniência
(+ os 5 .sql que as geraram; só registro, não usar)
denethor_original_tm_cm_sequencial/
33 as 33 reais originais, com o carimbo TM/CM sequencial
synthetic/user/ as sintéticas (ver a parte A) — sete pastas
easy/ 21 (+ Synthetic_easy.zip e v1/, com os 17 arquivos da campanha anterior)
medium/ 21 derivada da easy: 10 VMs do catálogo AWS (+ v1/, a gerada, com o seu instance_def)
hard/ 21 derivada da easy: 50 VMs, tempos × 5 (+ v1/, a derivada à mão da ultrahard)
ultrahard/ 31
scale022/ 8
scale022_v2/ 8
_scalehard_v1/ 23 aposentada; ver o README da própria pasta
cplex/ NÃO são instâncias: são saídas do solver (ver cplex/README.md)
easy/ 56 55 saídas da campanha easy + o zip de resultados parciais
model_validation/ 6 as 6 instâncias como foram ao solver…
output/ 18 …e as saídas correspondentes (6 × α ∈ {0,05; 0,50; 0,95})
defs/ 4 instance_def_{easy,ultrahard}.txt (topologias) e perfil_{medium,hard}.txt (painéis)
diagrams/ mermaid/ (14), mermaid_plain/{easy,scale_hard} e um .jpeg avulso
data/denethor_study/ o estudo do trace real; cada pasta leva o nome
measured/ da camada que a produz. measured/ é a exceção:
ec2_benchmarks/ 13 é a medição que ENTRA, não sai de camada nenhuma
local_runs/ (benchmarks de download na EC2 e a rodada local)
MANIFEST.csv procedência e sha256 do que foi copiado para cá
observed/ 8 saída de E1–E5 (observation)
predicted/ 21 saída de E6–E9 (prediction), + models/ e selected_files/
data/aws_study/ preços e velocidade da AWS
catalog/ 2 specs e preços reais de sa-east-1 (12 VMs, 48 configs FX)
fx_vm_speed/ 3 razão task_time_cpu(FX)/vm_cpu_time
A natureza da instância está no basename: as preditas levam o sufixo _predicted,
porque é o basename que identifica a instância nos CSVs de resultado e nos nomes dos
schedules. As medidas não levam sufixo.
cd src
python3 -m denethor.cli.run_study --all # E1–E7, para no gate de validação
python3 -m denethor.cli.run_study --etapa 9 # re-ancora a IR na execução local
python3 -m denethor.cli.run_study --etapa 10 # extrai as fontes do repo denethor
python3 -m denethor.generation.db_ids # IDs do banco (vw_task) para as I060–I100
python3 -m denethor.generation.generate_denethor --all # E11 — constrói as instâncias
python3 -m denethor.tools.validate_instance_files ../data/instances/denethor/runs/I002_T7_C5_D18_VM5_r1.txtrun_study aceita --etapa de 1 a 12; generate_denethor aceita --targets (só
I125–I201, a partir da IR), --regen (só I060–I100, da run4) ou --all, mais --out.
denethor/generation/é a ponta da cadeia — lê o export do Postgres e escreve os.txt. O resto dedenethor/é infraestrutura compartilhada com E1–E9.
Fora do pipeline, sob demanda: denethor.tools.format_instance normaliza a largura
decimal das colunas de tempo (10 casas) e de custo (14, a convenção do SQL) das seções de
tarefas, dados e matriz — sem tocar em TM/CM, painel de VM ou bucket. Idempotente e sem
efeito no carimbo; tem --check para verificar sem escrever. Ver §5.4 de
docs/denethor_geracao_instancias.md.
A E9 é que produz a IR definitiva: ela re-ancora na execução local medida e chama a E8 com as âncoras. O
--extrapolarroda a E8 sozinha, no modo "modelo puro", sem as âncoras — variante diferente, TM bem menor.
O dump do Postgres é lido direto de denethor/database/backup_29-06-2025/ — o repo
denethor precisa estar montado (os caminhos externos ficam todos em src/denethor/paths.py).
data/denethor_study/measured/ guarda só o que não existe lá: hoje, os 13 relatórios de
benchmark de download da EC2 e a rodada local de Python, com procedência e sha256 no
MANIFEST.csv ao lado.
As reais ficam no regime sub-segundo, onde ceil(cpu × slowdown) = 1 em todas as VMs e
a escolha VM × FX passa a ser decidida pelo arredondamento. A extensão linear tira as
tarefas desse piso — é uma transformação genérica, aplicável a qualquer instância:
cd src && python3 -m shared.cli.extend_instance --fator 100
cd src && python3 -m shared.cli.instances_cm_tm_updates --strategy resource_aware \
--margin 0.25 --instances-dir ../data/instances/denethor/runs_x100
cd src && python3 -m denethor.tools.format_instance ../data/instances/denethor/runs_x100Regra da escala coerente: todos os campos de tempo escalam pelo mesmo fator, a duração
exclui o cold start (o Lambda fatura duração decorrida) e o custo é recalculado
pela tarifa, nunca multiplicado — o termo fixo por invocação não escala. Travada por
tests/test_extend_instance.py; detalhes na seção 5.1 de
denethor_geracao_instancias.md.
Valem para qualquer instância, sintética ou real.
cd src
python3 -m shared.cli.instances_cm_tm_updates --instances-dir ../data/instances/synthetic/user/easy
python3 -m shared.cli.instances_cm_tm_updates --instances-dir ../data/instances/synthetic/user/ultrahard \
--patterns "Synthetic_124*.txt"--strategy escolhe entre resource_aware (padrão) e pessimistic; --margin sobrescreve
a folga padrão da estratégia (0.25 e 0.0, respectivamente).
cd src && python3 -m shared.cli.instances_validation ../data/instances/denethor/runscd src && python3 -m shared.cli.verifica_carimbos ../data/instances/denethor/runs --esperado-congeladas 2O regime das reais é misto por design: das 42 instâncias dessa pasta (reunificação de
2026-08-31 de first-n/, rand-1/ e rand-2/, com o front no sufixo _r1/_r2/_r3 do
nome — ver data/instances/denethor/runs/_readme.md), 40 batem com resource_aware(0.25)
e 2 estão congeladas no carimbo de 2026-08-07 — as únicas com solução ótima do CPLEX. Sem
este script, "40 batem e 2 não" parece bug. Ver
data/instances/denethor/runs/CARIMBO_CONGELADO.md.
Compartilhado — o formato e suas réguas
| Documento | Conteúdo |
|---|---|
| docs/formato_instancia.md | Especificação completa das 6 seções do arquivo .txt |
| docs/formato_modelo.txt | Exemplo comentado do arquivo, seção a seção |
| docs/formato_tm_cm.md | Cálculo dos limites TM/CM |
| docs/formato_tm_cm_v2_testes.md | CM v2 por dominância: o que motivou, o critério e as baterias que o testaram — experimento, não adotado |
| docs/sintetico_catalogo_vm.md | Catálogo de VMs: tabela adotada, análise de dominância e conferência contra os preços reais da AWS |
| docs/aws_catalogo.md | Preços e specs reais da AWS em sa-east-1 (EC2 + Lambda), em dólar: as 5 VMs e as 5 configs do DENETHOR marcadas, mais 7 VMs de workflow científico e a escada de 48 memórias de Lambda |
Sintético
| Documento | Conteúdo |
|---|---|
| docs/sintetico_tiers.md | Os tiers vigentes: medium e hard derivados da easy por perfil declarado — o painel, a escala de tempo, o catálogo efetivo medido e as baterias do Stratus |
| docs/sintetico_analise_topologias.md | Inventário dos workflows, linhagens entre instâncias e desafios para a fase construtiva |
| docs/sintetico_fx_vm_calibracao.md | Relação cpu_time FX × VM: gerador vs instâncias reais do DENETHOR |
| docs/sintetico_escada_tiers_aposentada.md | Proposta de escada ×1/×4/×16 entre tiers — aposentada em 2026-08-27, nunca implementada; a direção é o catálogo AWS (docs/aws_catalogo.md) |
DENETHOR
| Documento | Conteúdo |
|---|---|
| docs/denethor_geracao_instancias.md | Cadeia completa dos logs às instâncias: fontes, o bug da run4, o construtor, a organização por front e a regra da escala |
| docs/aws_fx_vm_speed.md | Medição independente de task_time_cpu(FX)/vm_cpu_time sobre os 1.254 itens de trabalho reais, confrontada com a literatura acadêmica — inclui o piso de overhead de 103 ms, o excesso de ~4,2× sobre o modelo de cota de CPU e a replicação pela run4 (I060–I100) |
| docs/denethor_estudo_escalabilidade.md | Relatório numérico do estudo — auto-gerado por src/denethor/prediction/report.py, não editar |
| docs/denethor_estudo_escalabilidade_retomada.md | Estado e decisões do estudo ao longo do tempo |
| docs/denethor_bateria_stratus_2026-08-18.md | Registro das rodadas da heurística (Stratus) sobre as instâncias novas |
Os testes usam pytest e rodam a partir da raiz do repositório — pytest.ini já põe
src e tests no pythonpath:
pip install -r requirements-dev.txt
pytestA suíte valida os arquivos data/instances/defs/instance_def_*.txt antes de qualquer
geração. A lista de tiers não é fixa: tests/conftest.py a descobre por glob, então um
instance_def novo entra na suíte sem editar nada (hoje são dois: easy e ultrahard;
medium e hard são derivados e testados sobre os arquivos, em test_derive_tier.py).
| Arquivo | Cobertura |
|---|---|
tests/test_workflow_definitions.py |
Um caso por workflow de cada tier, mais as relações entre tiers |
tests/test_workflow_def_validation_rules.py |
Blocos propositalmente quebrados, garantindo que cada regra realmente falha |
tests/test_edge_case_variants.py |
Uma variante de caso de borda só difere da sua base naquilo que ela testa: DAG, vm_cpu_time, dados, perfis FX e faixas de bucket saem byte a byte idênticos |
tests/test_fx_speed_rules.py |
Coerência de velocidade FX × VM nos parâmetros do gerador |
tests/test_instance_validation.py |
task_time_cpu nunca zerado: validador (V4.11) + guarda no gerador |
tests/test_scale_instance.py |
scale_instance.py só acrescenta recursos: preservação do workflow, configs novas não dominadas, catálogo estendido monotônico |
tests/test_derive_tier.py |
medium e hard são a easy com outro painel: perfis declarados, Seções 2/3/5/6 idênticas (ou × 5), TM escala exatamente com o fator, derivação reproduzível byte a byte |
tests/test_package_boundaries.py |
Direção das dependências entre shared/, synthetic/ e denethor/; nenhuma biblioteca importa de um cli/; a raiz do repo tem um dono só (shared/repo.py) |
tests/test_extend_instance.py |
Regra da escala coerente: I/O e cold start escalam, o cold start fica fora da duração faturada, o custo é recalculado pela tarifa |
tests/test_denethor_pipeline.py |
Pipeline denethor E1–E7, contra os dados reais do estudo |
tests/test_denethor_generation.py |
O construtor de instâncias (E11) e a extração de fontes (E10), também contra os dados reais |
As regras de definição ficam em src/synthetic/workflow_def_validation.py e cobrem:
- Campos —
TASKS,DATA,PATTERNeCOMMENTobrigatórios; os campos de configuração (CPU_TIME,READ_TIME,WRITE_TIME,FX_SLOWDOWN,NUM_VMS,NUM_CONFIGS,NUM_BUCKET_RANGES,USE_INTEGER_TIME) valem tanto noDEFAULTSquanto por workflow, e precisam existir em pelo menos um dos dois níveis (F14); campos desconhecidos ou repetidos são erro;COMMENTprecisa ser o último campo do cabeçalho (o loader absorve como comentário qualquer linha após ele) e estar em inglês — a regra checa ASCII e também procura palavras em português, já que um texto sem acentuação passaria pela checagem de ASCII. - Nome —
WORKFLOW_IDno padrão<Prefixo>_<NNN>, comNNN = TASKS + DATAe único dentro de cada arquivo (o mesmo ID aparece em mais de um tier de propósito). - Tarefas — ids
t0..tN-1sequenciais e sem repetição, cada tarefa com ao menos uma entrada e uma saída, sem dados repetidos e sem ler e escrever o mesmo dado. - Dados — ids
d0..dM-1contíguos, cada dado produzido por no máximo uma tarefa. - Grafo — todas as dependências satisfeitas, ausência de ciclos e linhas de tarefa em ordem topológica.
- Consistência —
TASKSeDATAdeclarados iguais ao que o DAG realmente descreve. - Entre tiers —
ultrahardcontém todo oeasycom os DAGs intactos e acrescenta topologias maiores; não pode existirinstance_def_medium/_hard, que reabririam a geração independente que o desenho derivado aposentou.
As regras de velocidade FX × VM ficam em src/synthetic/fx_speed_rules.py. O gerador assume que a FX é sempre mais lenta que a VM mais lenta, mas o speedup por config é cumulativo: no pior sorteio ele chega a 1 + (NUM_CONFIGS - 1) × 0.8, e o piso do FX_SLOWDOWN precisa ficar acima disso. Daí o teto de configs por piso — 3 com piso 3.0, 9 com piso 8.0, 24 com piso 20.0. As duas definições atuais estão dentro do invariante, cada uma pagando pelas próprias configs (razão de pior caso entre a melhor config e a VM mais lenta); medium e hard herdam as 3 configs da easy:
| tier | configs | piso do FX_SLOWDOWN |
pior speedup acumulado | razão FX/VM no pior caso |
|---|---|---|---|---|
easy (e os derivados medium, hard) |
3 | 3.0 | 2.6 | 1.15 |
ultrahard |
10 | 20.0 | 8.2 | 2.44 |
Os defeitos conhecidos estão marcados como xfail(strict=True) — o teste vira falha assim que forem corrigidos, sinalizando a remoção do marcador.