Análise e modificação do cadastro de usinas térmicas

O term.dat é o arquivo de entrada do modelo NEWAVE que contém as informações das usinas térmicas do sistema. Este arquivo define as características técnicas, operacionais e os limites de geração das usinas térmicas, incluindo suas inflexibilidades por mês.

from io import StringIO

import pandas as pd

from inewave.newave import Term

# Leitura do arquivo term.dat
arq_term = Term.read("./newave/term.dat")

As informações das usinas térmicas são acessadas através da propriedade usinas:

usinas = arq_term.usinas
print(f"Total de registros de usinas térmicas: {len(usinas)}")
print("\nPrimeiros registros:")
print(usinas.head())
Total de registros de usinas térmicas: 1417

Primeiros registros:
   codigo_usina nome_usina  ...  mes  geracao_minima
0             1    ANGRA 1  ...    1             0.0
1             1    ANGRA 1  ...    2             0.0
2             1    ANGRA 1  ...    3             0.0
3             1    ANGRA 1  ...    4             0.0
4             1    ANGRA 1  ...    5             0.0

[5 rows x 8 columns]

Análise das características técnicas das usinas

É importante compreender a capacidade instalada e os fatores de disponibilidade:

print("Estatísticas das usinas térmicas:")
stats_tecnicas = usinas[
    [
        "potencia_instalada",
        "fator_capacidade_maximo",
        "teif",
        "indisponibilidade_programada",
    ]
].describe()
print(stats_tecnicas)
Estatísticas das usinas térmicas:
       potencia_instalada  ...  indisponibilidade_programada
count         1417.000000  ...                   1417.000000
mean           233.321101  ...                      6.506789
std            293.112725  ...                     10.476122
min              0.000000  ...                      0.000000
25%             37.000000  ...                      1.530000
50%            144.000000  ...                      3.070000
75%            345.000000  ...                      7.630000
max           1593.000000  ...                     72.860000

[8 rows x 4 columns]

Cálculo da capacidade efetiva considerando fatores de indisponibilidade:

usinas_com_calc = usinas.copy()
usinas_com_calc["disponibilidade_total"] = (
    (100 - usinas_com_calc["teif"])
    * (100 - usinas_com_calc["indisponibilidade_programada"])
    / 10000
)
usinas_com_calc["capacidade_efetiva"] = (
    usinas_com_calc["potencia_instalada"]
    * usinas_com_calc["fator_capacidade_maximo"]
    / 100
    * usinas_com_calc["disponibilidade_total"]
)

print("Top 10 usinas térmicas por capacidade efetiva:")
top_usinas = usinas_com_calc.nlargest(10, "capacidade_efetiva")
print(top_usinas[["nome_usina", "potencia_instalada", "capacidade_efetiva"]])
Top 10 usinas térmicas por capacidade efetiva:
       nome_usina  potencia_instalada  capacidade_efetiva
806  P. SERGIPE I              1593.0         1351.528201
807  P. SERGIPE I              1593.0         1351.528201
808  P. SERGIPE I              1593.0         1351.528201
809  P. SERGIPE I              1593.0         1351.528201
810  P. SERGIPE I              1593.0         1351.528201
811  P. SERGIPE I              1593.0         1351.528201
812  P. SERGIPE I              1593.0         1351.528201
813  P. SERGIPE I              1593.0         1351.528201
814  P. SERGIPE I              1593.0         1351.528201
815  P. SERGIPE I              1593.0         1351.528201

Análise de inflexibilidades por mês

As usinas térmicas podem ter gerações mínimas obrigatórias variáveis ao longo do ano:

# Agrupando por usina para analisar perfil de inflexibilidade
print("Exemplo de análise de inflexibilidade:")
print("Usinas com geração mínima no mês 6:")
inflexibilidade_mes6 = usinas[usinas["mes"] == 6]
print(f"Quantidade de registros: {len(inflexibilidade_mes6)}")

inflexiveis = inflexibilidade_mes6[inflexibilidade_mes6["geracao_minima"] > 0]
print(f"Usinas com inflexibilidade > 0: {len(inflexiveis)}")

if len(inflexiveis) > 0:
    print("\nTop 5 usinas com maior inflexibilidade:")
    top_inflexiveis = inflexiveis.nlargest(5, "geracao_minima")
    print(top_inflexiveis[["codigo_usina", "nome_usina", "geracao_minima"]])
Exemplo de análise de inflexibilidade:
Usinas com geração mínima no mês 6:
Quantidade de registros: 109
Usinas com inflexibilidade > 0: 29

Top 5 usinas com maior inflexibilidade:
      codigo_usina    nome_usina  geracao_minima
18              13       ANGRA 2         1350.00
655            107     PAMPA SUL          291.42
1266           140        MAUA 3          264.00
109            183  DO_ATLANTICO          218.68
1279            46   N.VENECIA 2          148.15

Análise temporal das inflexibilidades

Verificando como as inflexibilidades variam ao longo dos meses:

def analisar_inflexibilidade_temporal(df):
    """Analisa a variação temporal das inflexibilidades"""
    # Agrupando por mês
    inflexibilidade_mensal = (
        df.groupby("mes")
        .agg({
            "geracao_minima": ["count", "sum", "mean"],
            "codigo_usina": "nunique",
        })
        .round(2)
    )

    # Renomeando colunas para clareza
    inflexibilidade_mensal.columns = [
        "registros_total",
        "geracao_minima_total",
        "geracao_minima_media",
        "usinas_unicas",
    ]

    return inflexibilidade_mensal


print("Análise temporal das inflexibilidades:")
analise_temporal = analisar_inflexibilidade_temporal(usinas)
print(analise_temporal)
Análise temporal das inflexibilidades:
     registros_total  geracao_minima_total  geracao_minima_media  usinas_unicas
mes
1                109                  0.00                  0.00            109
2                109                  0.00                  0.00            109
3                109                  0.00                  0.00            109
4                109                  0.00                  0.00            109
5                109                  0.00                  0.00            109
6                109               3027.94                 27.78            109
7                109               3343.52                 30.67            109
8                109               5956.28                 54.64            109
9                109               5994.33                 54.99            109
10               109               5994.33                 54.99            109
11               109               6095.57                 55.92            109
12               109               5003.98                 45.91            109
13               109               4871.93                 44.70            109

Identificação de usinas com perfil sazonal

Algumas usinas podem ter inflexibilidades que variam sazonalmente:

def identificar_perfil_sazonal(df):
    """Identifica usinas com variação sazonal de inflexibilidade"""
    # Pivot para ter inflexibilidade por usina e mês
    pivot_inflexibilidade = df.pivot_table(
        index="codigo_usina",
        columns="mes",
        values="geracao_minima",
        fill_value=0,
    )

    # Calculando variabilidade (desvio padrão) por usina
    pivot_inflexibilidade["variabilidade"] = pivot_inflexibilidade.std(axis=1)

    # Usinas com alta variabilidade sazonal
    alta_variabilidade = pivot_inflexibilidade[
        pivot_inflexibilidade["variabilidade"] > 0
    ].sort_values("variabilidade", ascending=False)

    return alta_variabilidade


if len(usinas[usinas["geracao_minima"] > 0]) > 0:
    print("Usinas com perfil sazonal de inflexibilidade:")
    perfil_sazonal = identificar_perfil_sazonal(usinas)
    print(f"Usinas com variação sazonal: {len(perfil_sazonal)}")
    if len(perfil_sazonal) > 0:
        print("\nTop 5 usinas com maior variação sazonal:")
        print(perfil_sazonal.head())
Usinas com perfil sazonal de inflexibilidade:
Usinas com variação sazonal: 42

Top 5 usinas com maior variação sazonal:
mes             1    2    3    4  ...       11       12       13  variabilidade
codigo_usina                      ...
238           0.0  0.0  0.0  0.0  ...  1278.60     0.00   639.27     721.125927
13            0.0  0.0  0.0  0.0  ...  1080.00  1080.00  1048.40     587.239472
1             0.0  0.0  0.0  0.0  ...   509.82   509.82   509.82     270.808047
212           0.0  0.0  0.0  0.0  ...   479.32   479.32   239.82     236.569566
140           0.0  0.0  0.0  0.0  ...   466.35   439.75   464.73     217.469146

[5 rows x 14 columns]

Modificação de dados térmicos

Criando cenários alternativos através de modificações programáticas:

print("Exemplo de modificação: Reduzindo TEIF em 20% para todas as usinas")

# Criando uma cópia para modificação
usinas_modificadas = usinas.copy()

# Reduzindo TEIF em 20% (melhoria na manutenção)
usinas_modificadas["teif"] = usinas_modificadas["teif"] * 0.8

print("Comparação do TEIF (original vs modificado):")
comparacao_teif = pd.DataFrame({
    "original": usinas["teif"],
    "modificado": usinas_modificadas["teif"],
    "melhoria_abs": usinas["teif"] - usinas_modificadas["teif"],
})
print(comparacao_teif.describe())
Exemplo de modificação: Reduzindo TEIF em 20% para todas as usinas
Comparação do TEIF (original vs modificado):
          original   modificado  melhoria_abs
count  1417.000000  1417.000000   1417.000000
mean      6.468991     5.175193      1.293798
std       9.490880     7.592704      1.898176
min       0.000000     0.000000      0.000000
25%       2.000000     1.600000      0.400000
50%       3.000000     2.400000      0.600000
75%       6.750000     5.400000      1.350000
max      71.260000    57.008000     14.252000

Criação de cenários de inflexibilidade

Modificando inflexibilidades para estudos de flexibilidade do sistema:

print("Criando cenário com redução de inflexibilidades:")

# Reduzindo inflexibilidades em 30%
usinas_flex = usinas.copy()
usinas_flex["geracao_minima"] = usinas_flex["geracao_minima"] * 0.7

inflexibilidade_original = usinas["geracao_minima"].sum()
inflexibilidade_nova = usinas_flex["geracao_minima"].sum()

print(f"Inflexibilidade total original: {inflexibilidade_original:.1f} MWmed")
print(f"Inflexibilidade total nova: {inflexibilidade_nova:.1f} MWmed")
print(
    f"Redução: {inflexibilidade_original - inflexibilidade_nova:.1f} MWmed ({(1 - inflexibilidade_nova / inflexibilidade_original) * 100:.1f}%)"
)
Criando cenário com redução de inflexibilidades:
Inflexibilidade total original: 40287.9 MWmed
Inflexibilidade total nova: 28201.5 MWmed
Redução: 12086.4 MWmed (30.0%)

Validação de dados térmicos

Implementando verificações de consistência:

def validar_dados_termicos(df):
    """Valida consistência dos dados térmicos"""
    problemas = []

    # Verificar potência instalada positiva
    pot_negativa = df[df["potencia_instalada"] <= 0]
    if len(pot_negativa) > 0:
        problemas.append(
            f"Potência instalada inválida: {len(pot_negativa)} registros"
        )

    # Verificar fator de capacidade entre 0 e 100
    fc_invalido = df[
        (df["fator_capacidade_maximo"] < 0)
        | (df["fator_capacidade_maximo"] > 100)
    ]
    if len(fc_invalido) > 0:
        problemas.append(
            f"Fator de capacidade inválido: {len(fc_invalido)} registros"
        )

    # Verificar TEIF entre 0 e 100
    teif_invalido = df[(df["teif"] < 0) | (df["teif"] > 100)]
    if len(teif_invalido) > 0:
        problemas.append(f"TEIF inválido: {len(teif_invalido)} registros")

    # Verificar se geração mínima não excede capacidade
    geracao_excessiva = df[df["geracao_minima"] > df["potencia_instalada"]]
    if len(geracao_excessiva) > 0:
        problemas.append(
            f"Geração mínima > Potência instalada: {len(geracao_excessiva)} registros"
        )

    # Verificar mês válido (1-13)
    mes_invalido = df[(df["mes"] < 1) | (df["mes"] > 13)]
    if len(mes_invalido) > 0:
        problemas.append(f"Mês inválido: {len(mes_invalido)} registros")

    return problemas


print("Validação dos dados térmicos:")
problemas = validar_dados_termicos(usinas)
if problemas:
    for problema in problemas:
        print(f"- {problema}")
else:
    print("✓ Dados térmicos validados com sucesso!")
Validação dos dados térmicos:
- Potência instalada inválida: 247 registros
- Geração mínima > Potência instalada: 15 registros

Aplicando modificações ao arquivo

Para salvar as modificações realizadas:

# Aplicando as modificações de TEIF reduzido
arq_term.usinas = usinas_modificadas

Exportação do arquivo modificado

# Exportação para buffer em memória
buffer = StringIO()
arq_term.write(buffer)
print(f"Arquivo térmico modificado gerado: {len(buffer.getvalue())} caracteres")

# Para salvar em disco:
# arq_term.write("./term_modificado.dat")
Arquivo térmico modificado gerado: 15095 caracteres

Análise de capacidade firme

Calculando a capacidade firme das térmicas considerando todos os fatores:

def calcular_capacidade_firme(df):
    """Calcula capacidade firme das usinas térmicas"""
    resultado = df.groupby("codigo_usina").first()  # Uma linha por usina

    # Capacidade firme = Pot_instalada * FC_max * (1-TEIF/100) * (1-IP/100)
    resultado["capacidade_firme"] = (
        resultado["potencia_instalada"]
        * resultado["fator_capacidade_maximo"]
        / 100
        * (1 - resultado["teif"] / 100)
        * (1 - resultado["indisponibilidade_programada"] / 100)
    )

    return resultado[["nome_usina", "potencia_instalada", "capacidade_firme"]]


print("Capacidade firme das usinas térmicas:")
capacidade_firme = calcular_capacidade_firme(usinas)
print(
    f"Capacidade instalada total: {capacidade_firme['potencia_instalada'].sum():.1f} MW"
)
print(
    f"Capacidade firme total: {capacidade_firme['capacidade_firme'].sum():.1f} MW"
)
print(
    f"Fator de capacidade firme médio: {capacidade_firme['capacidade_firme'].sum() / capacidade_firme['potencia_instalada'].sum() * 100:.1f}%"
)

print("\nTop 10 usinas por capacidade firme:")
print(capacidade_firme.nlargest(10, "capacidade_firme"))
Capacidade firme das usinas térmicas:
Capacidade instalada total: 25432.0 MW
Capacidade firme total: 21168.5 MW
Fator de capacidade firme médio: 83.2%

Top 10 usinas por capacidade firme:
                nome_usina  potencia_instalada  capacidade_firme
codigo_usina
224           P. SERGIPE I              1593.0       1351.528201
137                  GNA I              1338.0       1195.686199
13                 ANGRA 2              1350.0       1110.011391
47                TERMORIO               989.0        840.350966
90              TERMOMACAE               923.0        825.229397
60                NORTEFLU               827.0        787.293150
167             P. PECEM I               720.0        682.054330
1                  ANGRA 1               640.0        561.006861
96                 TERMOPE               550.0        532.105503
236            MARLIM AZUL               566.0        513.590211

Total running time of the script: (0 minutes 0.099 seconds)

Gallery generated by Sphinx-Gallery