The core design of the Scalable Integrated Infrastructure Planning (SIIP) simulation platform is that models are comprised of two major parts:
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.
PowerSimulations.jl
can be described as follows:The contributions are:
For this presentation, we will use the 5-bus test system for examples.
sys5b_th
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
sys5b_th.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
sys5b_th.buses[1]
Bus: number: 1 name: nodeA bustype: PV angle: 0.0 voltage: 1.0 voltagelimits: (min = 0.9, max = 1.05) basevoltage: 230.0
sys5b_th.branches[1]
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)
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.
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
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
ED.devices[:ThermalGenerators]
PowerSimulations.DeviceModel{ThermalGen,PowerSimulations.ThermalDispatch}(ThermalGen, PowerSimulations.ThermalDispatch)
ED.canonical_model.constraints[:thermal_active_range]["Alta",1]
keys(ED.canonical_model.constraints)
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.
UC.devices[:ThermalGenerators]
PowerSimulations.DeviceModel{ThermalGen,PowerSimulations.ThermalUnitCommitment}(ThermalGen, PowerSimulations.ThermalUnitCommitment)
display(UC.canonical_model.constraints[:thermal_active_range_ub]["Alta",1])
display(UC.canonical_model.constraints[:thermal_active_range_lb]["Alta",1])
keys(UC.canonical_model.constraints)
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
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?
ED.devices[:ThermalGenerators] = PSI.DeviceModel(PSY.ThermalGen, PSI.ThermalRampLimited)
PSI.build_op_model!(ED);
ED.canonical_model.constraints[:ramp_thermal_up][:,2]
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
PowerSimulations.jl
can specify the network formulation independently in the model.PowerSystems.jl
(NREL) and PowerSimulations.jl
(NREL) with PowerModels.jl
(LANL) to enable nonlinear AC power flow representations and relaxations.EDPF = PSI.EconomicDispatch(sys5b_th, PSI.StandardPTDFForm ; optimizer = GLPK_optimizer, PTDF = PTDF);
display(ED.transmission)
ED.canonical_model.constraints[:CopperPlateBalance][1]
CopperPlatePowerModel
display(EDPF.transmission)
display(EDPF.canonical_model.constraints[:network_flow]["1",1])
display(EDPF.canonical_model.constraints[:nodal_balance]["nodeA",1])
StandardPTDFForm
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.
ED_AC.canonical_model.expressions[:var_active]
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}
ED_AC.canonical_model.expressions[:var_reactive]
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}
PowerSimulations.jl
includes a specification for services based on the formulation and the collection of devices that participate in the provision of the service.The design of PowerSimulations.jl
separates all the modeling by device, network, and service, and wraps it all into single struct.
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…
display(ED.branches)
display(ED.transmission)
Dict{Symbol,PowerSimulations.DeviceModel} with 1 entry: :Lines => DeviceModel{Branch,SeriesLine}(Branch, SeriesLine)
CopperPlatePowerModel
display(ED.services)
Dict{Symbol,PowerSimulations.ServiceModel} with 1 entry: :Reserves => ServiceModel{Reserve,AbstractReservesForm}(Reserve, AbstractRese…
EDPF.canonical_model.expressions[:var_active]
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}
ED.canonical_model.constraints
Alta | Park City | Solitude | Sundance | Brighton | |
---|---|---|---|---|---|
Float64 | Float64 | Float64 | Float64 | Float64 | |
1 | 0.4 | 1.7 | 0.16 | 0.1 | 6.0 |
2 | 0.4 | 0.32 | 0.1 | 0.1 | 6.0 |
res = solve_op_model!(UC, optimizer = GLPK_optimizer);
res.optimizer_log
Dict{Symbol,Any} with 3 entries: :dual_status => NO_SOLUTION :primal_status => FEASIBLE_POINT :termination_status => OPTIMAL
StructJuMP.jl
+ PowerSimulations.jl
for Expansion Problem Definition¶ParameterJuMP
or use MOI
constraint modification?Systems
and Simulations
packages for water and gas.