Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
25 changes: 25 additions & 0 deletions ENVIRONMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Environment Metadata

## Sistema
- SO: Linux (container)
- Shell: bash
- Python: 3.10.19

## Solver e modelagem
- Modelagem: Pyomo
- Solver principal: CBC
- Solvers alternativos: HiGHS / Gurobi (quando disponíveis)

## Reprodutibilidade
- Dependências listadas em `requirements.txt`
- Cenários parametrizados em `configs/*.yaml`
- Execução padrão por `src/run_scenario.py`

## Coleta automática recomendada
Para registrar snapshots do ambiente, execute e anexe o resultado em `docs/`:

```bash
python --version
pip freeze > docs/pip_freeze.txt
cbc -stop
```
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.PHONY: run-base run-mod run-all

run-base:
python src/run_scenario.py --config configs/caso_base.yaml --output results/caso_base

run-mod:
python src/run_scenario.py --config configs/caso_modificado.yaml --output results/caso_modificado

run-all: run-base run-mod
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,65 @@
# Modelos-Base
# Otimização de BESS para Microgrid Comercial com FV e Eletroposto

Repositório para desenvolvimento, análise e reprodução de modelos de otimização energética (MILP) para um pequeno comércio com geração fotovoltaica (FV), sistema de armazenamento em bateria (BESS) e carga de eletroposto.

## Objetivo

Minimizar o custo operacional diário de energia, respeitando:
- balanço de energia por hora,
- dinâmica de estado de carga (SOC) da bateria,
- limites operacionais da bateria,
- compra e exportação de energia para a rede.

## Escopo atual

- Modelos em notebooks Jupyter (caso base e caso modificado)
- Estrutura reprodutível com scripts Python em `src/`
- Configuração de cenários em YAML (`configs/`)
- Documentação de hipóteses, dados e ambiente

## Estrutura de pastas

```text
Modelos-Base/
├── notebooks/ # Notebooks originais
├── src/ # Código modular executável
├── configs/ # Cenários de entrada (.yaml)
├── data/
│ ├── raw/ # Dados brutos
│ ├── interim/ # Dados intermediários
│ └── processed/ # Dados tratados
├── results/ # Saídas (csv, figuras, logs)
├── docs/ # Documentação complementar
├── scripts/ # Scripts utilitários/automação
├── assumptions.md
├── changelog.md
├── data_dictionary.md
├── ENVIRONMENT.md
└── requirements.txt
```

## Execução rápida

1. Crie e ative ambiente virtual.
2. Instale dependências:

```bash
pip install -r requirements.txt
```

3. Rode um cenário:

```bash
python src/run_scenario.py --config configs/caso_base.yaml --output results/caso_base
```

## Versionamento científico

- Mudanças de modelagem: `changelog.md`
- Assunções e limitações: `assumptions.md`
- Dicionário de dados: `data_dictionary.md`
- Metadados de ambiente: `ENVIRONMENT.md`

## Versão

Versão inicial proposta: **v0.1** (caso base com eficiência da bateria).
28 changes: 28 additions & 0 deletions assumptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Assumptions (Assunções de Modelagem)

## Escopo temporal e granularidade
- **A1**: Horizonte de otimização de 24 horas com passo de 1 hora.
- **A2**: Todos os perfis de demanda, geração FV e tarifa são determinísticos no horizonte diário.

## Sistema elétrico
- **A3**: O sistema pode importar energia da rede em qualquer hora, limitado apenas por restrições do modelo.
- **A4**: Exportação de energia para rede é permitida e remunerada por tarifa definida no cenário.
- **A5**: Perdas na rede interna do comércio não são modeladas explicitamente.

## BESS
- **A6**: A bateria possui limites fixos de potência de carga/descarga e capacidade energética.
- **A7**: Eficiência de carga/descarga é tratada de forma agregada por parâmetro de eficiência.
- **A8**: SOC inicial e final são definidos por cenário e devem ser fisicamente factíveis.
- **A9**: Degradação da bateria não é modelada explicitamente no objetivo (sem custo de ciclo nesta versão).

## Eletroposto e cargas
- **A10**: A demanda do eletroposto é considerada exógena e deve ser plenamente atendida.
- **A11**: Não há flexibilidade temporal de carga (sem deslocamento de demanda) nesta versão.

## Formulação e solução
- **A12**: Modelo MILP resolvido com CBC (alternativamente HiGHS/Gurobi, quando disponível).
- **A13**: Solução ótima depende da consistência das unidades e dos limites de parâmetros.

## Visualização e análise
- **A14**: Gráficos e tabelas são pós-processamento e não alteram a solução do otimizador.
- **A15**: Resultados são válidos apenas para os cenários de entrada definidos em `configs/`.
19 changes: 19 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Changelog

Este arquivo registra mudanças relevantes de estrutura, modelagem, dados e reprodutibilidade.

## [v0.1.0] - 2026-02-13

### Added
- Estrutura profissional de pastas (`notebooks`, `src`, `configs`, `data/*`, `results`, `docs`, `scripts`).
- Documentação de base: `README.md`, `assumptions.md`, `data_dictionary.md`, `ENVIRONMENT.md`.
- Cenários YAML iniciais para caso base e caso modificado.
- Script executável standalone para reproduzir cenário (`src/run_scenario.py`).
- Script de execução em lote (`run_all.sh`).
- Dependências fixadas em `requirements.txt`.

### Changed
- Notebooks originais movidos para `notebooks/` para separar prototipação de código de produção.

### Notes
- Próxima etapa: extração incremental de funções dos notebooks para módulos menores (`src/model/`, `src/io/`, `src/plots/`).
17 changes: 17 additions & 0 deletions configs/caso_base.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: caso_base
horizonte_horas: 24

demanda_comercio: [6,6,6,6,7,8,10,12,14,15,16,17,18,18,17,16,15,14,12,10,9,8,7,6]
demanda_ev: [0,0,0,0,0,0,5,8,10,6,4,2,0,0,0,2,4,6,8,6,4,2,0,0]
geracao_pv: [0,0,0,0,0,1,2,4,6,8,10,12,11,9,7,5,3,1,0,0,0,0,0,0]

tarifa_compra: [0.15,0.20,0.25,0.30,0.35,0.40,0.45,0.50,0.45,0.40,0.35,0.30,0.25,0.20,0.15,0.10,0.10,0.15,0.20,0.25,0.30,0.35,0.40,0.45]
tarifa_venda: [0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.10,0.10,0.10,0.10,0.10,0.10,0.10,0.10,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08]

bess_capacidade: 20.0
bess_pot_carga_max: 5.0
bess_pot_descarga_max: 5.0
bess_soc_inicial: 10.0
bess_soc_min: 2.0
bess_soc_max: 20.0
bess_eficiencia: 0.95
17 changes: 17 additions & 0 deletions configs/caso_modificado.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: caso_modificado
horizonte_horas: 24

demanda_comercio: [6,6,6,6,7,8,10,12,14,15,16,17,18,18,17,16,15,14,12,10,9,8,7,6]
demanda_ev: [0,0,0,0,0,0,5,8,10,6,4,2,0,0,0,2,4,6,8,6,4,2,0,0]
geracao_pv: [0,0,0,0,0,1,2,4,6,8,11,13,12,10,8,6,4,2,0,0,0,0,0,0]

tarifa_compra: [0.15,0.20,0.25,0.30,0.35,0.40,0.45,0.50,0.45,0.40,0.35,0.30,0.25,0.20,0.15,0.10,0.10,0.15,0.20,0.25,0.30,0.35,0.40,0.45]
tarifa_venda: [0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.10,0.10,0.10,0.10,0.10,0.10,0.10,0.10,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08]

bess_capacidade: 24.0
bess_pot_carga_max: 6.0
bess_pot_descarga_max: 6.0
bess_soc_inicial: 12.0
bess_soc_min: 2.0
bess_soc_max: 24.0
bess_eficiencia: 0.93
Empty file added data/interim/.gitkeep
Empty file.
Empty file added data/processed/.gitkeep
Empty file.
Empty file added data/raw/.gitkeep
Empty file.
38 changes: 38 additions & 0 deletions data_dictionary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Data Dictionary

## Entidades principais

| Campo | Tipo | Unidade | Descrição | Origem |
|---|---|---:|---|---|
| `horizonte_horas` | int | h | Número de períodos de otimização | Configuração de cenário |
| `demanda_comercio` | list[float] | kWh/h | Demanda horária do comércio | Entrada do cenário |
| `demanda_ev` | list[float] | kWh/h | Demanda horária do eletroposto | Entrada do cenário |
| `geracao_pv` | list[float] | kWh/h | Geração FV horária | Entrada do cenário |
| `tarifa_compra` | list[float] | R$/kWh | Tarifa de compra da rede por hora | Entrada do cenário |
| `tarifa_venda` | list[float] | R$/kWh | Tarifa de exportação por hora | Entrada do cenário |
| `bess_capacidade` | float | kWh | Capacidade nominal da bateria | Entrada do cenário |
| `bess_pot_carga_max` | float | kW | Limite de potência de carga | Entrada do cenário |
| `bess_pot_descarga_max` | float | kW | Limite de potência de descarga | Entrada do cenário |
| `bess_soc_inicial` | float | kWh | SOC no período inicial | Entrada do cenário |
| `bess_soc_min` | float | kWh | Limite mínimo de SOC | Entrada do cenário |
| `bess_soc_max` | float | kWh | Limite máximo de SOC | Entrada do cenário |
| `bess_eficiencia` | float | adim. | Eficiência (0-1) aplicada ao armazenamento | Entrada do cenário |

## Variáveis de decisão (saída do modelo)

| Campo | Tipo | Unidade | Descrição |
|---|---|---:|---|
| `grid_import[t]` | float | kWh/h | Energia importada da rede em `t` |
| `grid_export[t]` | float | kWh/h | Energia exportada para rede em `t` |
| `bess_charge[t]` | float | kWh/h | Carga da bateria em `t` |
| `bess_discharge[t]` | float | kWh/h | Descarga da bateria em `t` |
| `soc[t]` | float | kWh | Estado de carga da bateria em `t` |
| `objective_value` | float | R$ | Custo total minimizado no horizonte |

## Regras de validação recomendadas

1. Comprimento das séries horárias igual a `horizonte_horas`.
2. Valores não negativos para demanda e geração.
3. `0 < bess_eficiencia <= 1`.
4. `bess_soc_min <= bess_soc_inicial <= bess_soc_max`.
5. `bess_soc_max <= bess_capacidade`.
Empty file added docs/.gitkeep
Empty file.
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pyomo==6.8.2
pandas==2.2.2
numpy==1.26.4
matplotlib==3.9.0
PyYAML==6.0.2
Empty file added results/.gitkeep
Empty file.
7 changes: 7 additions & 0 deletions run_all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail

python src/run_scenario.py --config configs/caso_base.yaml --output results/caso_base
python src/run_scenario.py --config configs/caso_modificado.yaml --output results/caso_modificado

echo "Execução completa. Veja a pasta results/."
Empty file added scripts/.gitkeep
Empty file.
31 changes: 31 additions & 0 deletions scripts/extract_notebook_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

import json
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
NOTEBOOKS = ROOT / "notebooks"
OUT = ROOT / "src" / "notebook_exports"
OUT.mkdir(parents=True, exist_ok=True)

for nb_path in NOTEBOOKS.glob("*.ipynb"):
data = json.loads(nb_path.read_text(encoding="utf-8"))
lines = [
'"""Arquivo extraído automaticamente do notebook para rastreabilidade.\n',
"NÃO editável manualmente sem sincronização com o notebook de origem.\n",
'"""\n\n',
]
for cell in data.get("cells", []):
if cell.get("cell_type") != "code":
continue
src = "".join(cell.get("source", []))
if src.lstrip().startswith("!") or "MEU_TOKEN" in src or "gurobipy" in src:
continue
lines.append("\n# %%\n")
lines.append(src)
if not src.endswith("\n"):
lines.append("\n")

out_path = OUT / f"{nb_path.stem}.py"
out_path.write_text("".join(lines), encoding="utf-8")
print(f"Gerado: {out_path.relative_to(ROOT)}")
1 change: 1 addition & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Pacote de otimização energética (MILP) para microgrid comercial com FV, BESS e eletroposto."""
49 changes: 49 additions & 0 deletions src/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from __future__ import annotations

import pyomo.environ as pyo


def build_model(cfg: dict) -> pyo.ConcreteModel:
n = int(cfg["horizonte_horas"])
T = range(n)

m = pyo.ConcreteModel()
m.T = pyo.Set(initialize=T)

demanda_total = [c + e for c, e in zip(cfg["demanda_comercio"], cfg["demanda_ev"])]
pv = cfg["geracao_pv"]
buy = cfg["tarifa_compra"]
sell = cfg["tarifa_venda"]

eta = float(cfg["bess_eficiencia"])

m.P_grid = pyo.Var(m.T, domain=pyo.NonNegativeReals)
m.P_export = pyo.Var(m.T, domain=pyo.NonNegativeReals)
m.P_charge = pyo.Var(m.T, domain=pyo.NonNegativeReals)
m.P_discharge = pyo.Var(m.T, domain=pyo.NonNegativeReals)
m.SOC = pyo.Var(m.T, bounds=(cfg["bess_soc_min"], cfg["bess_soc_max"]))

m.obj = pyo.Objective(
expr=sum(buy[t] * m.P_grid[t] - sell[t] * m.P_export[t] for t in m.T),
sense=pyo.minimize,
)

def balanco_regra(mm, t):
return (
mm.P_grid[t] + pv[t] + mm.P_discharge[t]
== demanda_total[t] + mm.P_charge[t] + mm.P_export[t]
)

m.balanco = pyo.Constraint(m.T, rule=balanco_regra)

def soc_regra(mm, t):
if t == 0:
return mm.SOC[t] == cfg["bess_soc_inicial"] + eta * mm.P_charge[t] - (1 / eta) * mm.P_discharge[t]
return mm.SOC[t] == mm.SOC[t - 1] + eta * mm.P_charge[t] - (1 / eta) * mm.P_discharge[t]

m.soc_dinamica = pyo.Constraint(m.T, rule=soc_regra)

m.charge_limit = pyo.Constraint(m.T, rule=lambda mm, t: mm.P_charge[t] <= cfg["bess_pot_carga_max"])
m.discharge_limit = pyo.Constraint(m.T, rule=lambda mm, t: mm.P_discharge[t] <= cfg["bess_pot_descarga_max"])

return m
Loading