PowerSymulations.jl

A Flexible, Modular and Scalable Framework for Power System Optimization Modeling

José Daniel Lara

Energy and Resources Group UC Berkeley / National Renewable Energy Laboratory

nrel UCB

SIIP Power Systems Julia Packages

The core design of the Scalable Integrated Infrastructure Planning (SIIP) simulation platform is that models are comprised of two major parts:

fig1
  • PowerSystems.jl Takes advantage of Julia's dynamic types and functional dispatch to define data schemas for Power System's data in a structured yet extensible way.

  • PowerSimulations.jl We leverage the types/structs defined in PowerSystems.jl to utilize multiple dispatch in the construction of device models within operational models for power systems analysis.

Framework

  • In this approach, mathematical programming serves as the unifying framework for operational models in power systems.
  • The underlying meta model for PowerSimulations.jl can be described as follows:
system

The contributions are:

  • Develop a framework that enables developers and researchers to create and integrate new formulations to system-wide models to study renewable energy integration.
  • Allow analysts to describe the functional assumptions used in their models in a language that is simple yet clearly states the assumptions.
In [1]:
] activate nb-environs/SMD_env
In [2]:
] instantiate
  Updating registry at `~/.julia/registries/General`
  Updating git-repo `https://github.com/JuliaRegistries/General.git`
In [3]:
] up
  Updating registry at `~/.julia/registries/General`
  Updating git-repo `https://github.com/JuliaRegistries/General.git`
  Updating git-repo `https://github.com/JuliaOpt/JuMP.jl.git`
  Updating git-repo `https://github.com/lanl-ansi/InfrastructureModels.jl.git`
  Updating git-repo `https://github.com/lanl-ansi/PowerModels.jl.git`
  Updating git-repo `https://github.com/NREL/PowerSystems.jl.git`
 Resolving package versions...
  Updating `~/Dropbox/Code/SIIP-management/PowerSystems_Notebooks/nb-environs/SMD_env/Project.toml`
 [no changes]
  Updating `~/Dropbox/Code/SIIP-management/PowerSystems_Notebooks/nb-environs/SMD_env/Manifest.toml`
 [no changes]
In [4]:
using Logging
# Testing Topological components of the schema
gl = global_logger()
global_logger(ConsoleLogger(gl.stream, Logging.Error));
In [5]:
tsteps = 2
include("SIIP-Design-Demo/demo-preload.jl");

5-Bus System Data

For this presentation, we will use the 5-bus test system for examples.

system
  • It is possible to store the data for the system in a formal type structure.
  • The structure organizes the generators, branches, and topological data.
  • Using structs/types to handle system data enables the use of multiple dispatch to perform specific operations depending on the device.
In [6]:
sys5b_th
Out[6]:
PowerSystem:
   buses: Bus[Bus(name="nodeA"), Bus(name="nodeB"), Bus(name="nodeC"), Bus(name="nodeD"), Bus(name="nodeE")]
   generators: 
     GenClasses(T:5,R:0,H:0):
   thermal: ThermalDispatch[ThermalDispatch(name="Alta"), ThermalDispatch(name="Park City"), ThermalDispatch(name="Solitude"), ThermalDispatch(name="Sundance"), ThermalDispatch(name="Brighton")]
   renewable: nothing
   hydro: nothing
     (end generators)
   loads: PowerLoad[PowerLoad(name="Bus2"), PowerLoad(name="Bus3"), PowerLoad(name="Bus4")]
   branches: Line[Line(name="1"), Line(name="2"), Line(name="3"), Line(name="4"), Line(name="5"), Line(name="6")]
   storage: nothing
   basepower: 100.0
   time_periods: 2
In [7]:
sys5b_th.generators
Out[7]:
GenClasses(T:5,R:0,H:0):
   thermal: ThermalDispatch[ThermalDispatch(name="Alta"), ThermalDispatch(name="Park City"), ThermalDispatch(name="Solitude"), ThermalDispatch(name="Sundance"), ThermalDispatch(name="Brighton")]
   renewable: nothing
   hydro: nothing
In [8]:
sys5b_th.buses[1]
Out[8]:
Bus:
   number: 1
   name: nodeA
   bustype: PV
   angle: 0.0
   voltage: 1.0
   voltagelimits: (min = 0.9, max = 1.05)
   basevoltage: 230.0
In [9]:
sys5b_th.branches[1]
Out[9]:
Line:
   name: 1
   available: true
   connectionpoints: (from = Bus(name="nodeA"), to = Bus(name="nodeB"))
   r: 0.00281
   x: 0.0281
   b: (from = 0.00356, to = 0.00356)
   rate: 2.0
   anglelimits: (min = -0.7, max = 0.7)

Design Principle 1: Flexibility

Specification of Device Models (Formulations)

  • Optimization problems are created by applying model formulations to the device data structure.

  • PowerSimulations.jl builds a device formulation by calling functions with common constraint structures, enabling code re-usability and consistency across device formulations.

  • The equations that comprise each device formulation are dispatched based on the specification of the device type, device formulation and transmission formulation.

device_spec
device_spec
In [33]:
display(TypeTree(PSI.AbstractRenewableFormulation,init_expand = 2,scopesep="\n"))
Julia D3Tree

Attempting to display the tree. If the tree is large, this may take some time.

Note: D3Trees.jl requires an internet connection. If no tree appears, please check your connection. To help fix this, please see this issue. You may also diagnose errors with the javascript console (Ctrl-Shift-J in chrome).

Example Thermal Generators

  • The most generic model for a thermal generator in a power system operations model includes the commitment decisions.
  • The descriptor of the devices can be directly mapped into the optimization mathematical program.
  • An Abstract Type higher up in the tree will generate a more general model of the device.
function activepower_constraints(ps_m::CanonicalModel, 
                                 devices::Array{T,1}, 
                                 device_formulation::Type{D}, 
                                 system_formulation::Type{S}, 
                                 time_range::UnitRange{Int64}) 
                                    where {T <: PSY.ThermalGen, 
                                           D <: AbstractThermalFormulation, 
                                           S <: PM.AbstractPowerFormulation}

    range_data = [(g.name, g.tech.activepowerlimits) for g in devices]

    device_semicontinuousrange(ps_m, range_data, time_range, 
                               :thermal_active_range, :Pth, :on_th)

    return
end
  • When a more specific formulation such as ThermalDispatch is used, the active power variables constraints are created as a range without the binary component.
function activepower_constraints(ps_m::CanonicalModel, 
                                 devices::Array{T,1}, 
                                 device_formulation::Type{D}, 
                                 system_formulation::Type{S}, 
                                 time_range::UnitRange{Int64}) 
                                    where {T <: PSY.ThermalGen, 
                                           D <: AbstractThermalDispatchForm, 
                                           S <: PM.AbstractPowerFormulation}

    range_data = [(g.name, g.tech.activepowerlimits) for g in devices]

    device_range(ps_m, range_data, time_range, 
                 :thermal_active_range, :Pth)

    return

end
function device_range(ps_m::CanonicalModel, 
                range_data::Array{Tuple{String,NamedTuple{(:min, :max),Tuple{Float64,Float64}}},1}, 
                time_range::UnitRange{Int64}, 
                cons_name::Symbol, 
                var_name::Symbol)

    ps_m.constraints[cons_name] = 
    JuMP.Containers.DenseAxisArray{JuMP.ConstraintRef}
    (undef, [r[1] for r in range_data], time_range)

    for t in time_range, r in range_data

            ps_m.constraints[cons_name][r[1], t] = 
            JuMP.@constraint(ps_m.JuMPmodel, r[2].min <= ps_m.variables[var_name][r[1], t] <= r[2].max)

    end

    return

end
In [11]:
ED = PSI.EconomicDispatch(sys5b_th, PSI.CopperPlatePowerModel; optimizer = GLPK_optimizer);
UC = PSI.UnitCommitment(sys5b_th, PSI.CopperPlatePowerModel; optimizer = GLPK_optimizer);
In [12]:
ED.devices[:ThermalGenerators]
Out[12]:
PowerSimulations.DeviceModel{ThermalGen,PowerSimulations.ThermalDispatch}(ThermalGen, PowerSimulations.ThermalDispatch)
In [13]:
ED.canonical_model.constraints[:thermal_active_range]["Alta",1]
Out[13]:
$ Pth_{Alta,1} \in \[0.1, 0.4\] $
In [14]:
keys(ED.canonical_model.constraints)
Out[14]:
Base.KeySet for a Dict{Symbol,JuMP.Containers.DenseAxisArray} with 2 entries. Keys:
  :CopperPlateBalance
  :thermal_active_range

In a Unit Commitment (UC) model the thermal generators output limits are described as a semi-continuous range.

In [15]:
UC.devices[:ThermalGenerators]
Out[15]:
PowerSimulations.DeviceModel{ThermalGen,PowerSimulations.ThermalUnitCommitment}(ThermalGen, PowerSimulations.ThermalUnitCommitment)
In [16]:
display(UC.canonical_model.constraints[:thermal_active_range_ub]["Alta",1])
display(UC.canonical_model.constraints[:thermal_active_range_lb]["Alta",1])
$ Pth_{Alta,1} - 0.4 on_th_{Alta,1} \leq 0.0 $
$ Pth_{Alta,1} - 0.1 on_th_{Alta,1} \geq 0.0 $
In [17]:
keys(UC.canonical_model.constraints)
Out[17]:
Base.KeySet for a Dict{Symbol,JuMP.Containers.DenseAxisArray} with 8 entries. Keys:
  :CopperPlateBalance
  :thermal_active_range_ub
  :ramp_thermal_up
  :ramp_thermal_down
  :commitment_th
  :time_up
  :thermal_active_range_lb
  :time_down

Mixing and Changing Device Formulations

A common requirement is the capability to define different device models for a particular problem in order to answer questions about the system's operation under different conditions.

For instance, how does the system dispatch change when the units have little flexibility?

In [31]:
ED.devices[:ThermalGenerators] = PSI.DeviceModel(PSY.ThermalGen, PSI.ThermalRampLimited)
PSI.build_op_model!(ED);
ED.canonical_model.constraints[:ramp_thermal_up][:,2]
Out[31]:
1-dimensional DenseAxisArray{ConstraintRef,1,...} with index sets:
    Dimension 1, ["Park City", "Solitude", "Sundance", "Brighton"]
And data, a 4-element Array{ConstraintRef,1}:
 Pth_{Park City,1} - Pth_{Park City,2} ≤ 30.0            
 Pth_{Solitude,1} - Pth_{Solitude,2} ≤ 31.200000000000003
 Pth_{Sundance,1} - Pth_{Sundance,2} ≤ 30.0              
 Pth_{Brighton,1} - Pth_{Brighton,2} ≤ 30.0              

Network Representation

  • For a combination of devices in the system, PowerSimulations.jl can specify the network formulation independently in the model.
  • By collaborating with researchers from LANL, we have integrated PowerSystems.jl (NREL) and PowerSimulations.jl (NREL) with PowerModels.jl (LANL) to enable nonlinear AC power flow representations and relaxations.
  • This feature expands the modeling capabilities to perform research with a broader scope, including the use convex optimization models for AC power flow.

device_spec

In [19]:
EDPF = PSI.EconomicDispatch(sys5b_th, PSI.StandardPTDFForm ; optimizer = GLPK_optimizer, PTDF = PTDF);
In [20]:
display(ED.transmission)
ED.canonical_model.constraints[:CopperPlateBalance][1]
CopperPlatePowerModel
Out[20]:
$ Pth_{Alta,1} + Pth_{Park City,1} + Pth_{Solitude,1} + Pth_{Sundance,1} + Pth_{Brighton,1} = 8.356660648 $
In [21]:
display(EDPF.transmission)
display(EDPF.canonical_model.constraints[:network_flow]["1",1])
display(EDPF.canonical_model.constraints[:nodal_balance]["nodeA",1])
StandardPTDFForm
$ Fbr_{1,1} - 0.1939166051164976 Pth_{Alta,1} - 0.1939166051164976 Pth_{Park City,1} + 0.34898945813469434 Pth_{Solitude,1} - 0.1595380380044316 Pth_{Brighton,1} = 2.001896928292487 $
$ Pth_{Alta,1} + Pth_{Park City,1} - Fbr_{1,1} - Fbr_{2,1} - Fbr_{3,1} = 0.0 $
In [22]:
ED_AC= PSI.EconomicDispatch(sys5b_th, PM.StandardACPForm ; optimizer = ipopt_optimizer);

This capability is supported by the use of JuMP.DenseAxisArrays to keep track of the affine expressions (AffnExpr) that make up for the total injections at the nodes at each time-step.

In [23]:
ED_AC.canonical_model.expressions[:var_active]
Out[23]:
5×2 Array{GenericAffExpr{Float64,VariableRef},2}:
 Pth_{Alta,1} + Pth_{Park City,1}  Pth_{Alta,2} + Pth_{Park City,2}
 -2.378189934                      -2.169604722                    
 Pth_{Solitude,1} - 2.493281346    Pth_{Solitude,2} - 2.069589684  
 Pth_{Sundance,1} - 3.485189368    Pth_{Sundance,2} - 2.681958996  
 Pth_{Brighton,1}                  Pth_{Brighton,2}                
In [24]:
ED_AC.canonical_model.expressions[:var_reactive]
Out[24]:
5×2 Array{GenericAffExpr{Float64,VariableRef},2}:
 Qth_{Alta,1} + Qth_{Park City,1}       Qth_{Alta,2} + Qth_{Park City,2}  
 -0.7817110313058                       -0.7131490721214                  
 Qth_{Solitude,1} - 0.8195415784301999  Qth_{Solitude,2} - 0.6802741291308
 Qth_{Sundance,1} - 1.1454946155274     Qth_{Sundance,2} - 0.8814928730103
 Qth_{Brighton,1}                       Qth_{Brighton,2}                  

Services Representation - WIP

  • Most available open source models have limited representation for services required by the system to integrate large amounts of renewable energy.
  • A major line of research is developing new services or market products that operators need to add in order to integrate more renewable energy.
  • PowerSimulations.jl includes a specification for services based on the formulation and the collection of devices that participate in the provision of the service.
device_spec

Design Principle 2: Modularity

The design of PowerSimulations.jl separates all the modeling by device, network, and service, and wraps it all into single struct.

opt_spec
In [25]:
display(ED.devices)
Dict{Symbol,PowerSimulations.DeviceModel} with 3 entries:
  :ThermalGenerators   => DeviceModel{ThermalGen,ThermalRampLimited}(ThermalGen…
  :RenewableGenerators => DeviceModel{RenewableGen,RenewableFullDispatch}(Renew…
  :Loads               => DeviceModel{PowerLoad,StaticPowerLoad}(PowerLoad, Sta…
In [26]:
display(ED.branches)
display(ED.transmission)
Dict{Symbol,PowerSimulations.DeviceModel} with 1 entry:
  :Lines => DeviceModel{Branch,SeriesLine}(Branch, SeriesLine)
CopperPlatePowerModel
In [27]:
display(ED.services)
Dict{Symbol,PowerSimulations.ServiceModel} with 1 entry:
  :Reserves => ServiceModel{Reserve,AbstractReservesForm}(Reserve, AbstractRese…
In [40]:
EDPF.canonical_model.expressions[:var_active]
Out[40]:
5×2 Array{GenericAffExpr{Float64,VariableRef},2}:
 Pth_{Alta,1} + Pth_{Park City,1} - Fbr_{1,1} - Fbr_{2,1} - Fbr_{3,1}  …  Pth_{Alta,2} + Pth_{Park City,2} - Fbr_{1,2} - Fbr_{2,2} - Fbr_{3,2}
 Fbr_{1,1} - Fbr_{4,1} - 2.378189934                                      Fbr_{1,2} - Fbr_{4,2} - 2.169604722                                 
 Pth_{Solitude,1} + Fbr_{4,1} - Fbr_{5,1} - 2.493281346                   Pth_{Solitude,2} + Fbr_{4,2} - Fbr_{5,2} - 2.069589684              
 Pth_{Sundance,1} + Fbr_{2,1} + Fbr_{5,1} - Fbr_{6,1} - 3.485189368       Pth_{Sundance,2} + Fbr_{2,2} + Fbr_{5,2} - Fbr_{6,2} - 2.681958996  
 Pth_{Brighton,1} + Fbr_{3,1} + Fbr_{6,1}                                 Pth_{Brighton,2} + Fbr_{3,2} + Fbr_{6,2}                            
In [29]:
ED.canonical_model.constraints
Out[29]:

2 rows × 5 columns

AltaPark CitySolitudeSundanceBrighton
Float64Float64Float64Float64Float64
10.41.70.160.16.0
20.40.320.10.16.0
In [42]:
res = solve_op_model!(UC, optimizer = GLPK_optimizer);
res.optimizer_log
Out[42]:
Dict{Symbol,Any} with 3 entries:
  :dual_status        => NO_SOLUTION
  :primal_status      => FEASIBLE_POINT
  :termination_status => OPTIMAL

Design Principle 3: Scalability

  • Power system operational models need to be run at large scales: 50,000+ buses, 5000+ generators at 5 minute resolution.
  • The third layer defines the mechanics of executing many operational models depending on the type of analysis required.
  • This feature is under development right now.
opt_spec

Use of StructJuMP.jl + PowerSimulations.jl for Expansion Problem Definition

  • Simplify the problem specification phases to develop algorithms and decomposition methods for large scale problems of this nature.
  • Incorporate operational models with the appropriate level of detail and resolution into expansion decision-making models.
opt_spec

Next Steps

  • Develop type structures for more devices and branches.
  • Explore mechanisms to update Affine Expression constants in nodal power balance equations when updating time-series data and avoid model rebuilding.
    • Use ParameterJuMP or use MOI constraint modification?
  • Develop compiling operational model that can be deployed in a self-contained form.
  • Use the experience gained and developed structure to build Systems and Simulations packages for water and gas.