Arquitetura do inewave¶
Visão Geral¶
O inewave é uma biblioteca Python de leitura e escrita de arquivos para o modelo de otimização do planejamento energético NEWAVE e seus programas auxiliares NWLISTOP e NWLISTCF. O modelo NEWAVE produz e consome dezenas de arquivos de entrada e saída com formatos proprietários — alguns em texto, outros em binário — cujas especificações são distribuídas pela CEPEL e pelo ONS.
O objetivo do inewave é fornecer uma interface Python consistente e orientada a objetos para todos
esses arquivos, expondo seus dados como DataFrame e permitindo que analistas e
desenvolvedores manipulem decks de PMO e resultados de simulação sem precisar conhecer os detalhes de
formatação de cada arquivo.
Nota
O inewave não executa o modelo NEWAVE. Ele apenas lê e escreve os arquivos que o modelo consome e produz. A execução do modelo em si é de responsabilidade do ambiente onde o NEWAVE está instalado.
Framework cfinterface¶
A implementação de cada classe de arquivo do inewave é construída sobre o framework cfinterface, que define um modelo de classificação para arquivos de dados estruturados. O framework abstrai o mecanismo de leitura e escrita, permitindo que cada classe de arquivo no inewave descreva apenas o que está no arquivo, e não como realizar o parsing de bytes.
O cfinterface define três modelos de classificação de arquivos:
Arquivos por blocos (BlockFile)¶
Um BlockFile é composto por blocos independentes e autocontidos.
Cada bloco possui um padrão de início — textual ou binário — que sinaliza o começo de um conjunto
coerente de informações. Opcionalmente, um padrão de terminação pode ser definido, ou o próprio bloco
pode determinar seus limites por outros critérios.
Cada bloco é modelado como uma subclasse de Block. Um mesmo tipo
de bloco pode aparecer múltiplas vezes dentro de um arquivo, o que torna esse modelo adequado para
arquivos que repetem estruturas para diferentes entidades (usinas, estágios, cenários).
Exemplos de arquivos do inewave implementados como BlockFile: pmo.dat e
parp.dat.
Arquivos por seções (SectionFile)¶
Um SectionFile é composto por seções obrigatórias que sempre
aparecem em uma ordem fixa e predeterminada. Diferentemente do modelo por blocos, não há flexibilidade
na sequência ou na presença das seções: todas devem estar presentes e na ordem declarada.
Cada seção é modelada como uma subclasse de Section, que pode
definir seu próprio critério de fim, permitindo alguma variação no comprimento interno de cada seção.
Esse modelo é adequado para arquivos cujo layout é rígido e bem especificado.
Exemplos de arquivos do inewave implementados como SectionFile: dger.dat e
sistema.dat.
Arquivos por registros (RegisterFile)¶
Um RegisterFile é composto por registros — unidades mínimas
de conteúdo que ocupam exatamente uma linha e possuem formato constante. Registros podem ser vistos
como blocos de uma única linha, mas sua simplicidade permite uma definição ainda mais direta: basta
declarar os campos do registro como uma subclasse de Register,
e o framework infere automaticamente a leitura e a escrita a partir dos tipos e posições dos campos.
No inewave, o arquivo modif.dat é o principal exemplo implementado com esse modelo.
Ver também
Documentação completa do framework cfinterface em https://rjmalves.github.io/cfinterface/.
Estrutura de Módulos¶
O inewave organiza seus arquivos em quatro módulos públicos, refletindo a origem e a finalidade de cada tipo de arquivo no contexto do NEWAVE:
inewave.newave¶
Contém as classes que representam os arquivos de entrada e saída do modelo NEWAVE propriamente dito. Inclui tanto arquivos de configuração (como dger.dat e arquivos.dat) quanto arquivos de dados de entrada (como vazoes.dat e hidr.dat) e arquivos de saída da simulação (como pmo.dat e arquivos binários de forward/backward).
inewave.nwlistop¶
Contém as classes que representam os arquivos de saída do programa NWLISTOP, que pós-processa os resultados da simulação do NEWAVE e gera relatórios de variáveis operativas (geração térmica, armazenamento, custo marginal, entre outros). Esses arquivos são apenas de leitura no inewave.
inewave.nwlistcf¶
Contém as classes que representam os arquivos relacionados ao programa NWLISTCF, que processa os cortes de Benders gerados pelo NEWAVE. Este módulo é menor e possui estrutura de importação direta (sem lazy imports), pois contém poucos arquivos.
inewave.libs¶
Contém utilitários e estruturas de dados compartilhados entre os demais módulos, como modelos de usinas hidrelétricas, restrições e fontes de geração eólica, que são referenciados em múltiplos contextos dentro da biblioteca.
Nota
Além dos módulos públicos, o pacote conta com inewave._utils (funções internas de suporte)
e inewave.config (constantes e configurações globais da biblioteca). Esses módulos não fazem
parte da API pública e podem mudar entre versões sem aviso.
Mecanismo de Lazy Import¶
Os módulos inewave.newave e inewave.nwlistop expõem dezenas de classes de arquivos. Para
evitar que a importação de inewave carregue todo o código de parsing na memória antes que qualquer
classe seja utilizada, esses módulos utilizam o padrão de lazy import introduzido pela
PEP 562.
O mecanismo funciona da seguinte forma:
O
__init__.pyde cada subpacote define um dicionário_LAZY_IMPORTSque mapeia o nome da classe (chave) para o nome do módulo interno onde ela está definida (valor):
# inewave/newave/__init__.py (trecho ilustrativo)
_LAZY_IMPORTS: dict[str, str] = {
"Vazoes": "vazoes",
"Dger": "dger",
"Pmo": "pmo",
# ... demais classes
}
Uma função
__getattr__no nível do módulo intercepta qualquer acesso a atributo não definido explicitamente no namespace do pacote. Quando o nome solicitado está presente em_LAZY_IMPORTS, o módulo interno correspondente é carregado dinamicamente viaimportlib.import_module, a classe é extraída e armazenada emglobals()para uso futuro (evitando reimportação):
def __getattr__(name: str) -> Any:
if name in _LAZY_IMPORTS:
module = importlib.import_module(f".{_LAZY_IMPORTS[name]}", __name__)
value = getattr(module, name)
globals()[name] = value # cache para acessos subsequentes
return value
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
A função
__dir__é sobrescrita para retornar todos os nomes do dicionário, garantindo que ferramentas de introspecção (comodir(inewave.newave)e o autocompletar de IDEs) enxerguem todas as classes disponíveis mesmo antes de elas serem carregadas.
O efeito prático é que from inewave.newave import Vazoes carrega apenas o módulo vazoes.py,
sem incorrer no custo de inicializar todas as outras classes do subpacote.
Convenções de Nomenclatura¶
O inewave adota um conjunto de convenções que garantem consistência entre os nomes dos arquivos do NEWAVE e as classes e propriedades Python correspondentes.
Nomes de classes¶
Cada arquivo do NEWAVE é representado por uma classe cujo nome segue PascalCase, derivado do nome do arquivo em disco. Abreviações presentes no nome do arquivo são mantidas no nome da classe sem alteração de caso. Por exemplo:
Nomes de propriedades e colunas¶
As propriedades das classes e as colunas dos DataFrame retornados seguem
snake_case. Evita-se ao máximo ambiguidades na escolha dos nomes:
Atributos que identificam usinas hidrelétricas ou termelétricas são nomeados
codigo_usina(para o identificador inteiro) enome_usina(para a string de nome).Atributos relacionados a submercados de energia são sempre nomeados
codigo_submercadoenome_submercado, independentemente de o arquivo original usar o termo “subsistema”.O mesmo padrão se aplica a REEs e demais entidades do modelo.
Dados tabulares¶
Sempre que possível, os dados são expostos em formato normalizado (formato longo, ou tidy data), com uma coluna por variável e uma linha por observação. Isso facilita a integração com ferramentas de análise como pandas, mesmo quando o arquivo original armazena os dados em formato de tabela cruzada.
Fluxo de Dados¶
O ciclo completo de leitura de um arquivo percorre as seguintes etapas:
1. Invocação do método ``read``
O usuário chama o método de classe read, passando o caminho do arquivo:
from inewave.newave import Dger
arq = Dger.read("./dger.dat")
2. Abertura do arquivo e despacho para o cfinterface
Internamente, read delega ao cfinterface a abertura do arquivo em disco (modo texto ou binário,
conforme declarado na classe). O cfinterface instancia o objeto de arquivo e inicia o parsing.
3. Parsing pelos componentes (Block / Section / Register)
O cfinterface percorre o conteúdo do arquivo e, conforme encontra os padrões de início de cada
componente, invoca o método de leitura do Block,
Section ou Register
correspondente. Cada componente é responsável por interpretar sua própria porção do arquivo e
armazenar os dados em atributos internos.
4. Acesso às propriedades
Após a leitura, o objeto Python resultante expõe os dados por meio de propriedades que retornam
DataFrame (para dados tabulares) ou tipos escalares (inteiros, floats, strings):
# Acesso a uma propriedade escalar
print(arq.num_anos_estudo)
# Acesso a dados tabulares como DataFrame
df = arq.sistema
print(df.head())
5. Modificação e escrita (opcional)
Para arquivos de entrada, é possível modificar as propriedades e persistir o resultado com write:
from inewave.newave import Vazoes
arq_vazoes = Vazoes.read("./vazoes.dat")
# Sensibilidade: elevar todas as vazões em 10%
arq_vazoes.vazoes *= 1.1
# Persistir o arquivo modificado
arq_vazoes.write("./vazoes_sensibilidade.dat")
O método write delega novamente ao cfinterface, que percorre os componentes na ordem de
declaração e serializa cada um de volta ao formato original do arquivo.
Nota
Arquivos de saída do NEWAVE e do NWLISTOP são somente leitura no inewave. A tentativa de
chamar write em uma instância dessas classes levantará uma exceção. Verifique a documentação
da classe específica para confirmar se a escrita é suportada.