Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DNMY] Support in the loop PowerFlow evaluation #1040

Draft
wants to merge 50 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
1c30601
add PFS deps
jd-lara Jan 9, 2024
0bcb68e
make time_steps consistent
jd-lara Jan 9, 2024
cac31f2
add PowerFlows
jd-lara Jan 9, 2024
1b7d415
add new aux variables for power flow
jd-lara Jan 9, 2024
02c52ed
make time steps consistent
jd-lara Jan 9, 2024
1e71c06
fix failing test
jd-lara Jan 9, 2024
a94defb
add power flow evaluator to model and container
jd-lara Jan 9, 2024
d1336c0
add evaluation in initialization
jd-lara Jan 9, 2024
7d1b0d2
add code for evaluation
jd-lara Jan 9, 2024
c4071f4
add missing kwarg
jd-lara Jan 9, 2024
61ee4b5
fix incorrect var type
jd-lara Jan 9, 2024
3b31d02
fix old bug
jd-lara Jan 9, 2024
347b67c
update aux vars for power flow evaluation
jd-lara Jan 9, 2024
5606f65
WIP: function for the power flow evaluation
jd-lara Jan 9, 2024
e8caaca
whitespace
jd-lara Jan 9, 2024
198e7b4
Merge branch 'main' into jd/pf_integration
jd-lara Jan 23, 2024
bf872ca
bump deps
jd-lara Jan 23, 2024
4e0f8ee
change to varnames
jd-lara Jan 23, 2024
7a2555a
add auxiliary variables
jd-lara Jan 23, 2024
d95ef9e
add wrapper for power flow data
jd-lara Jan 24, 2024
d2b26ac
use wrapper
jd-lara Jan 24, 2024
ddb92e3
add powerflow wrapper
jd-lara Jan 24, 2024
33bd977
power flow rename
jd-lara Jan 24, 2024
2645ced
file rename
jd-lara Jan 24, 2024
c997e2f
add evaluation to the container
jd-lara Jan 25, 2024
1ba76cb
WIP: add functions to map results to pf
jd-lara Jan 25, 2024
c11f4cf
Merge branch 'psy4' into jd/pf_integration
jd-lara Mar 6, 2024
4fcfa16
Merge branch 'main' into jd/pf_integration
GabrielKS Aug 19, 2024
b26e2b0
Remove redundant code
GabrielKS Oct 15, 2024
54e4859
Rewrite power flow evaluation to support more types of power flow
GabrielKS Oct 15, 2024
1e714ac
Support evaluation of multiple power flows in the loop
GabrielKS Oct 15, 2024
508db25
Better accommodate `PSSEExporter` in the loop, reduce duplication
GabrielKS Oct 16, 2024
b6fc453
Trait-based special behavior for power flow aux vars
GabrielKS Oct 17, 2024
eb51cfb
Scaffold subtype-based updating of power flow aux vars
GabrielKS Oct 17, 2024
dfd7405
Update power flow aux vars from `PowerFlowData`
GabrielKS Oct 17, 2024
c0ac0be
Complete prototype power flow in the loop -> export implementation
GabrielKS Oct 17, 2024
9e1f30a
Power flow in the loop: handle load special cases
GabrielKS Oct 28, 2024
1507b14
Power flow in the loop: support multi period export
GabrielKS Oct 28, 2024
9ec1356
Misc code cleanup following self-review
GabrielKS Oct 28, 2024
ad25918
Add basic tests of power flow in the loop
GabrielKS Nov 4, 2024
958e68d
Update `DISABLED_TEST_FILES`
GabrielKS Nov 4, 2024
187f473
Merge branch 'main' into jd/pf_integration
GabrielKS Nov 4, 2024
e016521
PERF: don't use `get_bus(sys, number)`, it's O(n)
GabrielKS Nov 19, 2024
30454e6
Add aux vars, infrastructure to receive results from AC power flow
GabrielKS Jan 16, 2025
a242ddf
Fix `bus_activepower_withdrawals` sign error
GabrielKS Jan 17, 2025
f5eb983
Merge branch 'main' into jd/pf_integration
jd-lara Jan 20, 2025
670d4fc
Fix merge, update dependencies
GabrielKS Jan 21, 2025
3e091c4
Power flow in the loop: support `exporter` kwarg interface
GabrielKS Jan 21, 2025
d5e6fab
Rewrite PSI -> PF mapping for more flexibility
GabrielKS Jan 22, 2025
b0ba0d6
Add `TimerOutputs.@timeit` for PF in the loop
GabrielKS Jan 22, 2025
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
5 changes: 4 additions & 1 deletion src/core/network_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ mutable struct NetworkModel{T <: PM.AbstractPowerModel}
reduce_radial_branches = false,
subnetworks = Dict{Int, Set{Int}}(),
duals = Vector{DataType}(),
power_flow_evaluation::Union{PFS.PowerFlowEvaluationModel, Vector{PFS.PowerFlowEvaluationModel}} = PFS.PowerFlowEvaluationModel[],
power_flow_evaluation::Union{
PFS.PowerFlowEvaluationModel,
Vector{PFS.PowerFlowEvaluationModel},
} = PFS.PowerFlowEvaluationModel[],
) where {T <: PM.AbstractPowerModel}
_check_pm_formulation(T)
new{T}(
Expand Down
10 changes: 6 additions & 4 deletions src/core/power_flow_data_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ mutable struct PowerFlowEvaluationData{T <: PFS.PowerFlowContainer}
power_flow_data::T
"""
Records which PSI keys are read as input to the power flow and how the data are mapped.
For `PowerFlowData`, values are `Dict{String, Int64}` mapping component name to matrix
index of bus; for `SystemPowerFlowContainer`, values are Dict{Union{String, Int64},
The Symbol is a category of data: `:active_power`, `:reactive_power`, etc. The
`OptimizationContainerKey` is a source of that data in the `OptimizationContainer`. For
`PowerFlowData`, leaf values are `Dict{String, Int64}` mapping component name to matrix
index of bus; for `SystemPowerFlowContainer`, leaf values are Dict{Union{String, Int64},
Union{String, Int64}} mapping component name/bus number to component name/bus number.
"""
input_key_map::Dict{<:OptimizationContainerKey, <:Any}
input_key_map::Dict{Symbol, <:Dict{<:OptimizationContainerKey, <:Any}}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate dicts for active and reactive power, basically. There were other ways to do it but I think this is the most natural.

is_solved::Bool
end

function PowerFlowEvaluationData(power_flow_data::T) where {T <: PFS.PowerFlowContainer}
return PowerFlowEvaluationData{T}(
power_flow_data,
Dict{OptimizationContainerKey, Nothing}(),
Dict{Symbol, Dict{OptimizationContainerKey, <:Any}}(),
false,
)
end
Expand Down
163 changes: 108 additions & 55 deletions src/network_models/power_flow_evaluation.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Defines the order of precedence for each type of information that could be sent to PowerFlows.jl
const PF_INPUT_KEY_PRECEDENCES = Dict(
:active_power => [ActivePowerVariable, PowerOutput, ActivePowerTimeSeriesParameter],
:reactive_power => [ReactivePowerVariable, ReactivePowerTimeSeriesParameter],
:voltage_angle => [PowerFlowVoltageAngle],
:voltage_magnitude => [PowerFlowVoltageMagnitude],
)
# TODO define separate categories :active_power_from_power_flow, etc. for exporter

function _add_aux_variables!(
container::OptimizationContainer,
component_map::Dict{Type{<:AuxVariableType}, <:Set{<:Tuple{DataType, Any}}},
Expand All @@ -18,12 +27,17 @@ function _add_aux_variables!(
end
end

# Trait that determines what keys serve as input to each type of power flow, if they exist
pf_input_keys(::PFS.PowerFlowData) =
[ActivePowerVariable, PowerOutput, ActivePowerTimeSeriesParameter]
# Trait that determines which types of information are needed for each type of power flow
pf_input_keys(::PFS.ABAPowerFlowData) =
[:active_power]
pf_input_keys(::PFS.PTDFPowerFlowData) =
[:active_power]
pf_input_keys(::PFS.vPTDFPowerFlowData) =
[:active_power]
pf_input_keys(::PFS.ACPowerFlowData) =
[:active_power, :reactive_power]
pf_input_keys(::PFS.PSSEExporter) =
[ActivePowerVariable, PowerOutput, ActivePowerTimeSeriesParameter,
PowerFlowVoltageAngle, PowerFlowVoltageMagnitude]
[:active_power, :reactive_power, :voltage_angle, :voltage_magnitude]

# Maps the StaticInjection component type by name to the
# index in the PowerFlow data arrays going from Bus number to bus index
Expand Down Expand Up @@ -73,35 +87,48 @@ function _make_pf_input_map!(
pf_data = get_power_flow_data(pf_e_data)
temp_component_map = _make_temp_component_map(pf_data, sys)
map_type = valtype(temp_component_map) # Dict{String, Int} for PowerFlowData, Dict{Union{String, Int64}, String} for SystemPowerFlowContainer
input_keys = pf_input_keys(pf_data)

# Second map that persists to store the bus index that the variable
# has to be added/substracted to in the power flow data dictionary
pf_data_opt_container_map = Dict{OptimizationContainerKey, map_type}()
added_injection_types = DataType[]
for (key, val) in Iterators.flatten([
get_variables(container),
get_aux_variables(container),
get_parameters(container),
])
# Skip irrelevant keys
(get_entry_type(key) in input_keys) || continue

comp_type = get_component_type(key)
# Skip types that have already been handled (prefer variable over aux variable, aux variable over parameter)
(comp_type in added_injection_types) && continue
push!(added_injection_types, comp_type)

name_bus_ix_map = map_type()
comp_names =
(key isa ParameterKey) ? get_component_names(get_attributes(val)) : axes(val)[1]
for comp_name in comp_names
name_bus_ix_map[comp_name] = temp_component_map[comp_type][comp_name]
pf_e_data.input_key_map = Dict{Symbol, Dict{OptimizationContainerKey, map_type}}()

# available_keys is a vector of Pair{OptimizationContainerKey, data} containing all possibly relevant data sources to iterate over
available_keys = vcat(
[
collect(pairs(f(container))) for
f in [get_variables, get_aux_variables, get_parameters]
]...,
)
# Separate map for each category
for category in pf_input_keys(pf_data)
# Map that persists to store the bus index to which the variable maps in the PowerFlowData, etc.
pf_data_opt_container_map = Dict{OptimizationContainerKey, map_type}()
@info "Adding input map to send $category to $(nameof(typeof(pf_data)))"
precedence = PF_INPUT_KEY_PRECEDENCES[category]
added_injection_types = DataType[]
# For each data source that is relevant to this category in order of precedence,
# loop over the component types where data exists at that source and record the
# association
for entry_type in precedence
for (key, val) in available_keys
(get_entry_type(key) === entry_type) || continue
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this if statement for readability

comp_type = get_component_type(key)
# Skip types that have already been handled by something of higher precedence
(comp_type in added_injection_types) && continue
push!(added_injection_types, comp_type)

name_bus_ix_map = map_type()
comp_names =
if (key isa ParameterKey)
get_component_names(get_attributes(val))
else
axes(val)[1]
end
for comp_name in comp_names
name_bus_ix_map[comp_name] = temp_component_map[comp_type][comp_name]
end
pf_data_opt_container_map[key] = name_bus_ix_map
end
end
pf_data_opt_container_map[key] = name_bus_ix_map
pf_e_data.input_key_map[category] = pf_data_opt_container_map
end

pf_e_data.input_key_map = pf_data_opt_container_map
return
end

Expand Down Expand Up @@ -190,21 +217,40 @@ end
# How to update the PowerFlowData given a component type. A bit duplicative of code in PowerFlows.jl.
_update_pf_data_component!(
pf_data::PFS.PowerFlowData,
::Val{:active_power},
::Type{<:PSY.StaticInjection},
index,
t,
value,
) = (pf_data.bus_activepower_injection[index, t] += value)
_update_pf_data_component!(
pf_data::PFS.PowerFlowData,
::Val{:active_power},
::Type{<:PSY.ElectricLoad},
index,
t,
value,
) = (pf_data.bus_activepower_withdrawals[index, t] -= value)
_update_pf_data_component!(
pf_data::PFS.PowerFlowData,
::Val{:reactive_power},
::Type{<:PSY.StaticInjection},
index,
t,
value,
) = (pf_data.bus_reactivepower_injection[index, t] += value)
_update_pf_data_component!(
pf_data::PFS.PowerFlowData,
::Val{:reactive_power},
::Type{<:PSY.ElectricLoad},
index,
t,
value,
) = (pf_data.bus_reactivepower_withdrawals[index, t] -= value)

function _write_value_to_pf_data!(
pf_data::PFS.PowerFlowData,
category::Symbol,
container::OptimizationContainer,
key::OptimizationContainerKey,
component_map)
Expand All @@ -213,7 +259,14 @@ function _write_value_to_pf_data!(
injection_values = result[device_name, :]
for t in get_time_steps(container)
value = jump_value(injection_values[t])
_update_pf_data_component!(pf_data, get_component_type(key), index, t, value)
_update_pf_data_component!(
pf_data,
Val(category),
get_component_type(key),
index,
t,
value,
)
end
end
return
Expand All @@ -226,41 +279,41 @@ function update_pf_data!(
pf_data = get_power_flow_data(pf_e_data)
PFS.clear_injection_data!(pf_data)
input_map = get_input_key_map(pf_e_data)
for (key, component_map) in input_map
_write_value_to_pf_data!(pf_data, container, key, component_map)
for (category, inputs) in input_map
@info "Writing $category to $(nameof(typeof(pf_data)))"
for (key, component_map) in inputs
_write_value_to_pf_data!(pf_data, category, container, key, component_map)
end
end
return
end

_update_component(
::Type{<:Union{ActivePowerVariable, PowerOutput, ActivePowerTimeSeriesParameter}},
comp::PSY.Component,
value,
) = (comp.active_power = value)
_update_component!(comp::PSY.Component, ::Val{:active_power}, value) =
(comp.active_power = value)
# Sign is flipped for loads (TODO can we rely on some existing function that encodes this information?)
_update_component(
::Type{<:Union{ActivePowerVariable, PowerOutput, ActivePowerTimeSeriesParameter}},
comp::PSY.ElectricLoad,
value,
) = (comp.active_power = -value)
_update_component(::Type{PowerFlowVoltageAngle}, comp::PSY.Component, value) =
_update_component!(comp::PSY.ElectricLoad, ::Val{:active_power}, value) =
(comp.active_power = -value)
_update_component!(comp::PSY.Component, ::Val{:voltage_angle}, value) =
comp.angle = value
_update_component(::Type{PowerFlowVoltageMagnitude}, comp::PSY.Component, value) =
_update_component!(comp::PSY.Component, ::Val{:voltage_magnitude}, value) =
comp.magnitude = value

GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
function update_pf_system!(
sys::PSY.System,
container::OptimizationContainer,
input_map::Dict{<:OptimizationContainerKey, <:Any},
input_map::Dict{Symbol, <:Dict{OptimizationContainerKey, <:Any}},
time_step::Int,
)
for (key, component_map) in input_map
result = lookup_value(container, key)
for (device_id, device_name) in component_map
injection_values = result[device_id, :]
comp = PSY.get_component(get_component_type(key), sys, device_name)
val = jump_value(injection_values[time_step])
_update_component(get_entry_type(key), comp, val)
for (category, inputs) in input_map
@debug "Writing $category to (possibly internal) System"
for (key, component_map) in inputs
result = lookup_value(container, key)
for (device_id, device_name) in component_map
injection_values = result[device_id, :]
comp = PSY.get_component(get_component_type(key), sys, device_name)
val = jump_value(injection_values[time_step])
_update_component!(comp, Val(category), val)
end
end
end
end
Expand Down