From 54100f3a25779d88d22508e3d0248e09794f2ba9 Mon Sep 17 00:00:00 2001 From: Carleton Coffrin Date: Mon, 22 Jan 2024 09:30:34 -0700 Subject: [PATCH] Updates for PowerModels v0.20 (#22) * updates for powermodels v0.20 * REF: objective functions needed for PowerModels v0.20 --------- Co-authored-by: Juan Ospina --- CHANGELOG.md | 2 +- Project.toml | 2 +- src/PowerModelsITD.jl | 5 + src/core/objective.jl | 10 +- src/core/objective_dmld.jl | 10 +- src/core/objective_dmld_simple.jl | 10 +- src/core/objective_helpers.jl | 234 ++++++++++++++++++++++++++++++ src/core/objective_storage.jl | 6 +- 8 files changed, 259 insertions(+), 20 deletions(-) create mode 100644 src/core/objective_helpers.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cbfe41..7525d38 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## staged -- none. +- Added multiple functions to `objetive_helpers.jl` adapted from the implementation in PowerModels <= v0.19 due to their removal from >= v0.20. ## v0.8.0 diff --git a/Project.toml b/Project.toml index f619959..3e02695 100755 --- a/Project.toml +++ b/Project.toml @@ -18,7 +18,7 @@ Ipopt = "0.9, 1.0.2" JSON = "~0.18, ~0.19, ~0.20, ~0.21" JuMP = "~0.22, ~0.23, 1" LinearAlgebra = "1.6" -PowerModels = "0.19.9" +PowerModels = "0.20" PowerModelsDistribution = "0.15.1" SCS = "~1.0, ~1.1" julia = "1.6" diff --git a/src/PowerModelsITD.jl b/src/PowerModelsITD.jl index ea83351..54ca1ad 100755 --- a/src/PowerModelsITD.jl +++ b/src/PowerModelsITD.jl @@ -32,6 +32,10 @@ module PowerModelsITD "BOUNDARY_NUMBER constant that determines the starting counter for the boundaries defined." const BOUNDARY_NUMBER = 100001 + ### compat for PM v0.20 + # enables support for v[1] + Base.getindex(v::JuMP.VariableRef, i::Int) = v + # Files to include in module include("io/common.jl") include("core/base.jl") @@ -40,6 +44,7 @@ module PowerModelsITD include("core/ref.jl") include("core/helpers.jl") include("core/variable.jl") + include("core/objective_helpers.jl") include("core/objective.jl") include("core/objective_dmld.jl") include("core/objective_dmld_simple.jl") diff --git a/src/core/objective.jl b/src/core/objective.jl index de54223..79661fb 100644 --- a/src/core/objective.jl +++ b/src/core/objective.jl @@ -14,7 +14,7 @@ function objective_itd_min_fuel_cost(pmitd::AbstractPowerModelITD) pmd_model = _get_powermodeldistribution_from_powermodelitd(pmitd) # PM cost models - pm_cost_model = _PM.check_cost_models(pm_model) + pm_cost_model = _check_gen_cost_models(pm_model) # PMD cost models pmd_cost_model = _PMD.check_gen_cost_models(pmd_model) @@ -43,8 +43,8 @@ Fuel cost minimization objective with piecewise linear terms. function objective_itd_min_fuel_cost_pwl(pmitd::AbstractPowerModelITD, pm::_PM.AbstractPowerModel, pmd::_PMD.AbstractUnbalancedPowerModel) # PM-section part - _PM.objective_variable_pg_cost(pm) - _PM.objective_variable_dc_cost(pm) + _objective_variable_pg_cost(pm) + _objective_variable_dc_cost(pm) # PMD-section part objective_mc_variable_pg_cost(pmd) @@ -185,7 +185,7 @@ function _objective_itd_min_fuel_cost_polynomial_linquad(pmitd::AbstractPowerMod for (n, nw_ref) in _PM.nws(pm) for (i,gen) in nw_ref[:gen] - pg = sum( _PM.var(pm, n, :pg, i)[c] for c in _PM.conductor_ids(pm, n) ) + pg = _PM.var(pm, n, :pg, i) if length(gen["cost"]) == 1 pm_gen_cost[(n,i)] = gen["cost"][1] @@ -387,7 +387,7 @@ function _objective_itd_min_fuel_cost_polynomial_nl(pmitd::AbstractPowerModelITD pm_gen_cost = Dict() for (n, nw_ref) in _PM.nws(pm) for (i,gen) in nw_ref[:gen] - pg = sum( _PM.var(pm, n, :pg, i)[c] for c in _PM.conductor_ids(pm, n)) + pg = _PM.var(pm, n, :pg, i) cost_rev = reverse(gen["cost"]) if length(cost_rev) == 1 diff --git a/src/core/objective_dmld.jl b/src/core/objective_dmld.jl index 508e13e..a491c25 100644 --- a/src/core/objective_dmld.jl +++ b/src/core/objective_dmld.jl @@ -14,7 +14,7 @@ function objective_itd_min_fuel_distribution_load_setpoint_delta(pmitd::Abstract pmd_model = _get_powermodeldistribution_from_powermodelitd(pmitd) # PM cost models - pm_cost_model = _PM.check_cost_models(pm_model) + pm_cost_model = _check_cost_models(pm_model) # PMD cost models pmd_cost_model = _PMD.check_gen_cost_models(pmd_model) @@ -42,8 +42,8 @@ Fuel cost minimization objective with piecewise linear terms in transmission and function objective_itd_min_fuel_pwl_distribution_load_setpoint_delta(pmitd::AbstractPowerModelITD, pm::_PM.AbstractPowerModel, pmd::_PMD.AbstractUnbalancedPowerModel) # PM-section part - _PM.objective_variable_pg_cost(pm) - _PM.objective_variable_dc_cost(pm) + _objective_variable_pg_cost(pm) + _objective_variable_dc_cost(pm) # PMD-section part for (n, nw_ref) in _PMD.nws(pmd) @@ -132,7 +132,7 @@ function _objective_itd_min_fuel_polynomial_linquad_distribution_load_setpoint_d for (n, nw_ref) in _PM.nws(pm) for (i,gen) in nw_ref[:gen] - pg = sum( _PM.var(pm, n, :pg, i)[c] for c in _PM.conductor_ids(pm, n) ) + pg = _PM.var(pm, n, :pg, i) if length(gen["cost"]) == 1 pm_gen_cost[(n,i)] = gen["cost"][1] @@ -207,7 +207,7 @@ function _objective_itd_min_fuel_polynomial_nl_distribution_load_setpoint_delta( pm_gen_cost = Dict() for (n, nw_ref) in _PM.nws(pm) for (i,gen) in nw_ref[:gen] - pg = sum( _PM.var(pm, n, :pg, i)[c] for c in _PM.conductor_ids(pm, n)) + pg = _PM.var(pm, n, :pg, i) cost_rev = reverse(gen["cost"]) if length(cost_rev) == 1 diff --git a/src/core/objective_dmld_simple.jl b/src/core/objective_dmld_simple.jl index 457e7a0..00ffe83 100644 --- a/src/core/objective_dmld_simple.jl +++ b/src/core/objective_dmld_simple.jl @@ -14,7 +14,7 @@ function objective_itd_min_fuel_distribution_load_setpoint_delta_simple(pmitd::A pmd_model = _get_powermodeldistribution_from_powermodelitd(pmitd) # PM cost models - pm_cost_model = _PM.check_cost_models(pm_model) + pm_cost_model = _check_cost_models(pm_model) # PMD cost models pmd_cost_model = _PMD.check_gen_cost_models(pmd_model) @@ -42,8 +42,8 @@ Fuel cost minimization objective with piecewise linear terms in transmission and function objective_itd_min_fuel_pwl_distribution_load_setpoint_delta_simple(pmitd::AbstractPowerModelITD, pm::_PM.AbstractPowerModel, pmd::_PMD.AbstractUnbalancedPowerModel) # PM-section part - _PM.objective_variable_pg_cost(pm) - _PM.objective_variable_dc_cost(pm) + _objective_variable_pg_cost(pm) + _objective_variable_dc_cost(pm) # ITD (Combined objective) return JuMP.@objective(pmitd.model, Min, @@ -102,7 +102,7 @@ function _objective_itd_min_fuel_polynomial_linquad_distribution_load_setpoint_d for (n, nw_ref) in _PM.nws(pm) for (i,gen) in nw_ref[:gen] - pg = sum( _PM.var(pm, n, :pg, i)[c] for c in _PM.conductor_ids(pm, n) ) + pg = _PM.var(pm, n, :pg, i) if length(gen["cost"]) == 1 pm_gen_cost[(n,i)] = gen["cost"][1] @@ -145,7 +145,7 @@ function _objective_itd_min_fuel_polynomial_nl_distribution_load_setpoint_delta_ pm_gen_cost = Dict() for (n, nw_ref) in _PM.nws(pm) for (i,gen) in nw_ref[:gen] - pg = sum( _PM.var(pm, n, :pg, i)[c] for c in _PM.conductor_ids(pm, n)) + pg = _PM.var(pm, n, :pg, i) cost_rev = reverse(gen["cost"]) if length(cost_rev) == 1 diff --git a/src/core/objective_helpers.jl b/src/core/objective_helpers.jl new file mode 100644 index 0000000..2f863c2 --- /dev/null +++ b/src/core/objective_helpers.jl @@ -0,0 +1,234 @@ +""" + function _check_cost_models( + pm::_PM.AbstractPowerModel + ) + +Checks that all cost models are of the same type. +Adapted from the implementation in PowerModels <= v0.19. +""" +function _check_cost_models(pm::_PM.AbstractPowerModel) + gen_model = _check_gen_cost_models(pm) + dcline_model = _check_dcline_cost_models(pm) + + if dcline_model == nothing + return gen_model + end + + if gen_model == nothing + return dcline_model + end + + if gen_model != dcline_model + @error "generator and dcline cost models are inconsistent, the generator model is $(gen_model) however dcline model $(dcline_model)" + end + + return gen_model +end + + +""" + function _check_gen_cost_models( + pm::_PM.AbstractPowerModel + ) + +Checks that all generator cost models are of the same type. +Adapted from the implementation in PowerModels <= v0.19. +""" +function _check_gen_cost_models(pm::_PM.AbstractPowerModel) + model = nothing + + for (n, nw_ref) in _PM.nws(pm) + for (i,gen) in nw_ref[:gen] + if haskey(gen, "cost") + if model == nothing + model = gen["model"] + else + if gen["model"] != model + @error "cost models are inconsistent, the typical model is $(model) however model $(gen["model"]) is given on generator $(i)" + end + end + else + @error "no cost given for generator $(i)" + end + end + end + + return model +end + + +""" + function _check_dcline_cost_models( + pm::_PM.AbstractPowerModel + ) + +Checks that all dcline cost models are of the same type. +Adapted from the implementation in PowerModels <= v0.19. +""" +function _check_dcline_cost_models(pm::_PM.AbstractPowerModel) + model = nothing + + for (n, nw_ref) in _PM.nws(pm) + for (i,dcline) in nw_ref[:dcline] + if haskey(dcline, "model") + if model == nothing + model = dcline["model"] + else + if dcline["model"] != model + @error "cost models are inconsistent, the typical model is $(model) however model $(dcline["model"]) is given on dcline $(i)" + end + end + else + @error "no cost given for dcline $(i)" + end + end + end + + return model +end + + +""" + function _objective_variable_pg_cost( + pm::_PM.AbstractPowerModel, + report::Bool=true + ) + +Adds pg_cost variables and constraints. +Adapted from the implementation in PowerModels <= v0.19. +""" +function _objective_variable_pg_cost(pm::_PM.AbstractPowerModel, report::Bool=true) + for (n, nw_ref) in _PM.nws(pm) + pg_cost = _PM.var(pm, n)[:pg_cost] = Dict{Int,Any}() + + for (i,gen) in _PM.ref(pm, n, :gen) + pg_var = _PM.var(pm, n, :pg, i) + pmin = JuMP.lower_bound(pg_var) + pmax = JuMP.upper_bound(pg_var) + + points = _PM.calc_pwl_points(gen["ncost"], gen["cost"], pmin, pmax) + + pg_cost_lambda = JuMP.@variable(pm.model, + [i in 1:length(points)], base_name="$(n)_pg_cost_lambda", + lower_bound = 0.0, + upper_bound = 1.0 + ) + JuMP.@constraint(pm.model, sum(pg_cost_lambda) == 1.0) + + pg_expr = 0.0 + pg_cost_expr = 0.0 + for (i,point) in enumerate(points) + pg_expr += point.mw*pg_cost_lambda[i] + pg_cost_expr += point.cost*pg_cost_lambda[i] + end + JuMP.@constraint(pm.model, pg_expr == pg_var) + pg_cost[i] = pg_cost_expr + end + + report && _PM.sol_component_value(pm, n, :gen, :pg_cost, _PM.ids(pm, n, :gen), pg_cost) + end +end + + +""" + function _objective_variable_dc_cost( + pm::_PM.AbstractPowerModel, + report::Bool=true + ) + +Adds p_dc_cost variables and constraints. +Adapted from the implementation in PowerModels <= v0.19. +""" +function _objective_variable_dc_cost(pm::_PM.AbstractPowerModel, report::Bool=true) + for (n, nw_ref) in _PM.nws(pm) + p_dc_cost = _PM.var(pm, n)[:p_dc_cost] = Dict{Int,Any}() + + for (i,dcline) in _PM.ref(pm, n, :dcline) + arc = (i, dcline["f_bus"], dcline["t_bus"]) + p_dc_var = _PM.var(pm, n, :p_dc)[arc] + pmin = JuMP.lower_bound(p_dc_var) + pmax = JuMP.upper_bound(p_dc_var) + + # note pmin/pmax may be different from dcline["pminf"]/dcline["pmaxf"] in the on/off case + points = _PM.calc_pwl_points(dcline["ncost"], dcline["cost"], pmin, pmax) + + dc_p_cost_lambda = JuMP.@variable(pm.model, + [i in 1:length(points)], base_name="$(n)_dc_p_cost_lambda", + lower_bound = 0.0, + upper_bound = 1.0 + ) + JuMP.@constraint(pm.model, sum(dc_p_cost_lambda) == 1.0) + + dc_p_expr = 0.0 + dc_p_cost_expr = 0.0 + for (i,point) in enumerate(points) + dc_p_expr += point.mw*dc_p_cost_lambda[i] + dc_p_cost_expr += point.cost*dc_p_cost_lambda[i] + end + + JuMP.@constraint(pm.model, dc_p_expr == p_dc_var) + p_dc_cost[i] = dc_p_cost_expr + end + + report && _PM.sol_component_value(pm, n, :dcline, :p_dc_cost, _PM.ids(pm, n, :dcline), p_dc_cost) + end +end + + +""" + function _objective_variable_pg_cost( + pm::_PM.AbstractIVRModel, + report::Bool=true + ) + +Adds pg_cost variables and constraints for IVR Model. +Adapted from the implementation in PowerModels <= v0.19. +""" +function _objective_variable_pg_cost(pm::_PM.AbstractIVRModel; report::Bool=true) + for (n, nw_ref) in _PM.nws(pm) + gen_lines = _PM.calc_cost_pwl_lines(nw_ref[:gen]) + + #to avoid function calls inside of @NLconstraint + pg_cost = _PM.var(pm, n)[:pg_cost] = JuMP.@variable(pm.model, + [i in _PM.ids(pm, n, :gen)], base_name="$(n)_pg_cost", + ) + report && _PM.sol_component_value(pm, n, :gen, :pg_cost, _PM.ids(pm, n, :gen), pg_cost) + + for (i, gen) in nw_ref[:gen] + pg = _PM.var(pm, n, :pg, i) + for line in gen_lines[i] + JuMP.@NLconstraint(pm.model, pg_cost[i] >= line.slope*pg + line.intercept) + end + end + end +end + + +""" + function _objective_variable_dc_cost( + pm::_PM.AbstractIVRModel, + report::Bool=true + ) + +Adds p_dc_cost variables and constraints for IVR Model. +Added for compat with PowerModels <= v0.19 implementation. +""" +function _objective_variable_dc_cost(pm::_PM.AbstractIVRModel, report::Bool=true) + for (n, nw_ref) in _PM.nws(pm) + dcline_lines = _PM.calc_cost_pwl_lines(nw_ref[:dcline]) + + #to avoid function calls inside of @NLconstraint + p_dc_cost = _PM.var(pm, n)[:p_dc_cost] = JuMP.@variable(pm.model, + [i in _PM.ids(pm, n, :dcline)], base_name="$(n)_p_dc_cost", + ) + report && _PM.sol_component_value(pm, n, :dcline, :p_dc_cost, _PM.ids(pm, n, :dcline), p_dc_cost) + + for (i, dcline) in nw_ref[:dcline] + arc = (i, dcline["f_bus"], dcline["t_bus"]) + p_dc_var = _PM.var(pm, n, :p_dc)[arc] + for line in dcline_lines[i] + JuMP.@NLconstraint(pm.model, p_dc_cost[i] >= line.slope*p_dc_var + line.intercept) + end + end + end +end diff --git a/src/core/objective_storage.jl b/src/core/objective_storage.jl index af81c49..90ed08b 100644 --- a/src/core/objective_storage.jl +++ b/src/core/objective_storage.jl @@ -14,7 +14,7 @@ function objective_itd_min_fuel_cost_storage(pmitd::AbstractPowerModelITD) pmd_model = _get_powermodeldistribution_from_powermodelitd(pmitd) # PM cost models - pm_cost_model = _PM.check_cost_models(pm_model) + pm_cost_model = _check_cost_models(pm_model) # PMD cost models pmd_cost_model = _PMD.check_gen_cost_models(pmd_model) @@ -75,7 +75,7 @@ function _objective_itd_min_fuel_cost_polynomial_linquad_storage(pmitd::Abstract for (n, nw_ref) in _PM.nws(pm) for (i,gen) in nw_ref[:gen] - pg = sum( _PM.var(pm, n, :pg, i)[c] for c in _PM.conductor_ids(pm, n) ) + pg = _PM.var(pm, n, :pg, i) if length(gen["cost"]) == 1 pm_gen_cost[(n,i)] = gen["cost"][1] @@ -366,7 +366,7 @@ function _objective_itd_min_fuel_cost_polynomial_nl_storage(pmitd::AbstractPower pm_gen_cost = Dict() for (n, nw_ref) in _PM.nws(pm) for (i,gen) in nw_ref[:gen] - pg = sum( _PM.var(pm, n, :pg, i)[c] for c in _PM.conductor_ids(pm, n)) + pg = _PM.var(pm, n, :pg, i) cost_rev = reverse(gen["cost"]) if length(cost_rev) == 1