Multi-Period OPF

The multi-period models extend single-period OPF with a time dimension, allowing optimisation over load and generation profiles (e.g. one day at 15-minute resolution).

Overview

The time horizon is controlled by fromT and toT (0-indexed time step indices into the SimBench profiles). Each time step is 15 minutes (deltaT = 0.25 h).

Flexible devices — batteries, heat pumps — are instantiated separately and automatically attach their Pyomo sets, parameters, variables, and constraints to the parent model.

Step 1 — Load a SimBench network with profiles

Multi-period models require net.profiles, which SimBench provides:

import simbench as sb
net = sb.get_simbench_net("1-LV-urban6--0-sw")

Step 2 — Configure network limits

net.bus["max_vm_pu"] = 1.05
net.bus["min_vm_pu"] = 0.95
net.line["max_loading_percent"] = 100.
net.sgen["controllable"] = True
net.sgen["max_p_mw"] = net.sgen["p_mw"]
net.sgen["min_p_mw"] = 0.

Step 3 — Build the multi-period model

from potpourri.models_multi_period.ACOPF_multi_period import ACOPF_multi_period

fromT = 0
toT = 96        # 1 day at 15-min resolution

opf = ACOPF_multi_period(net, toT=toT, fromT=fromT)

The constructor automatically creates device objects for loads (Demand_multi_period), static generators (Sgens_multi_period), and external grid generators (Generator_multi_period). Shunt capacitors and wind power objects are included when the network data supports them.

Step 4 — Add flexible devices (optional)

Instantiate device classes and pass the model to them. Each device attaches itself to opf.model. See Flexible Devices for all options.

Step 5 — Add OPF constraints and objective

opf.add_OPF()
opf.add_voltage_deviation_objective()     # minimise Σ_t Σ_b (v[b,t] - 1)²

Other available objectives:

opf.add_minimize_power_objective()        # minimise total load consumption
opf.add_generation_objective()            # minimise Σ pG²
opf.add_weighted_generation_objective()   # weighted: generators + sgens

Step 6 — Solve

opf.solve(solver="ipopt", print_solver_output=False, time_limit=3600)

Multi-period problems are large NLPs. IPOPT with a time_limit is recommended. For MINLP problems (discrete tap changers), use MindtPy:

opf.solve(solver="mindtpy", mip_solver="gurobi", time_limit=3600)

Step 7 — Access results

Results are indexed by time step. Access Pyomo variables directly:

import pyomo.environ as pyo

for t in opf.model.T:
    p_gen = sum(pyo.value(opf.model.psG[g, t]) * opf.model.baseMVA
                for g in opf.model.sG)
    print(f"t={t}: total sgen output = {p_gen:.3f} MW")

Model architecture

The multi-period model is composed from modular device objects. Each device implements a standard interface:

Device.__init__(net)
  └─ get_all(model)
       ├─ get_sets(model)
       ├─ get_parameters(model)
       ├─ get_variables(model)
       ├─ get_all_constraints(model)
       └─ get_all_acopf(model)   # AC-OPF specific additions

This means you can inspect or extend individual device constraints without touching the core model.