diff --git a/docs/src/api/PowerSimulations.md b/docs/src/api/PowerSimulations.md index cc1843fc0..dfd88ddd3 100644 --- a/docs/src/api/PowerSimulations.md +++ b/docs/src/api/PowerSimulations.md @@ -233,6 +233,7 @@ PowerOutput ```@docs PowerFlowVoltageAngle PowerFlowVoltageMagnitude +PowerFlowLossFactors ``` ### Branch Auxiliary Variables diff --git a/src/PowerSimulations.jl b/src/PowerSimulations.jl index ee82d5c31..3fad6051f 100644 --- a/src/PowerSimulations.jl +++ b/src/PowerSimulations.jl @@ -244,6 +244,7 @@ export PowerFlowVoltageAngle export PowerFlowVoltageMagnitude export PowerFlowLineReactivePowerFromTo, PowerFlowLineReactivePowerToFrom export PowerFlowLineActivePowerFromTo, PowerFlowLineActivePowerToFrom +export PowerFlowLossFactors # Constraints export AbsoluteValueConstraint diff --git a/src/core/auxiliary_variables.jl b/src/core/auxiliary_variables.jl index 83d2dc90d..c6a82cc81 100644 --- a/src/core/auxiliary_variables.jl +++ b/src/core/auxiliary_variables.jl @@ -48,6 +48,11 @@ Auxiliary Variable for the line active flow in the to -> from direction from pow """ struct PowerFlowLineActivePowerToFrom <: PowerFlowAuxVariableType end +""" +Auxiliary Variable for the loss factors from AC power flow evaluation that are calculated using the Jacobian matrix +""" +struct PowerFlowLossFactors <: PowerFlowAuxVariableType end + convert_result_to_natural_units(::Type{PowerOutput}) = true convert_result_to_natural_units( ::Type{ diff --git a/src/network_models/power_flow_evaluation.jl b/src/network_models/power_flow_evaluation.jl index e4485458c..dd8c0478a 100644 --- a/src/network_models/power_flow_evaluation.jl +++ b/src/network_models/power_flow_evaluation.jl @@ -2,8 +2,10 @@ const PF_INPUT_KEY_PRECEDENCES = Dict( :active_power => [ActivePowerVariable, PowerOutput, ActivePowerTimeSeriesParameter], :reactive_power => [ReactivePowerVariable, ReactivePowerTimeSeriesParameter], - :voltage_angle => [PowerFlowVoltageAngle], - :voltage_magnitude => [PowerFlowVoltageMagnitude], + :voltage_angle_export => [PowerFlowVoltageAngle, VoltageAngle], + :voltage_magnitude_export => [PowerFlowVoltageMagnitude, VoltageMagnitude], + :voltage_angle_opf => [VoltageAngle], + :voltage_magnitude_opf => [VoltageMagnitude], ) const RELEVANT_COMPONENTS_SELECTOR = @@ -37,9 +39,9 @@ pf_input_keys(::PFS.PTDFPowerFlowData) = pf_input_keys(::PFS.vPTDFPowerFlowData) = [:active_power] pf_input_keys(::PFS.ACPowerFlowData) = - [:active_power, :reactive_power] + [:active_power, :reactive_power, :voltage_angle_opf, :voltage_magnitude_opf] pf_input_keys(::PFS.PSSEExporter) = - [:active_power, :reactive_power, :voltage_angle, :voltage_magnitude] + [:active_power, :reactive_power, :voltage_angle_export, :voltage_magnitude_export] # Maps the StaticInjection component type by name to the # index in the PowerFlow data arrays going from Bus number to bus index @@ -53,6 +55,12 @@ function _make_temp_component_map(pf_data::PFS.PowerFlowData, sys::PSY.System) bus_number = PSY.get_number(PSY.get_bus(comp)) bus_dict[get_name(comp)] = bus_lookup[bus_number] end + # we need this to be able to export the voltage magnitude and voltage angles to data + temp_component_map[PSY.ACBus] = + Dict( + PSY.get_name(c) => bus_lookup[PSY.get_number(c)] for + c in get_components(PSY.ACBus, sys) + ) return temp_component_map end @@ -145,7 +153,12 @@ branch_aux_vars(::PFS.vPTDFPowerFlowData) = branch_aux_vars(::PFS.PSSEExporter) = DataType[] # Same for bus aux vars -bus_aux_vars(::PFS.ACPowerFlowData) = [PowerFlowVoltageAngle, PowerFlowVoltageMagnitude] +bus_aux_vars(data::PFS.ACPowerFlowData) = + if data.calculate_loss_factors + [PowerFlowVoltageAngle, PowerFlowVoltageMagnitude, PowerFlowLossFactors] + else + [PowerFlowVoltageAngle, PowerFlowVoltageMagnitude] + end bus_aux_vars(::PFS.ABAPowerFlowData) = [PowerFlowVoltageAngle] bus_aux_vars(::PFS.PTDFPowerFlowData) = DataType[] bus_aux_vars(::PFS.vPTDFPowerFlowData) = DataType[] @@ -251,6 +264,22 @@ _update_pf_data_component!( t, value, ) = (pf_data.bus_reactivepower_withdrawals[index, t] -= value) +_update_pf_data_component!( + pf_data::PFS.PowerFlowData, + ::Union{Val{:voltage_angle_export}, Val{:voltage_angle_opf}}, + ::Type{<:PSY.ACBus}, + index, + t, + value, +) = (pf_data.bus_angles[index, t] = value) +_update_pf_data_component!( + pf_data::PFS.PowerFlowData, + ::Union{Val{:voltage_magnitude_export}, Val{:voltage_magnitude_opf}}, + ::Type{<:PSY.ACBus}, + index, + t, + value, +) = (pf_data.bus_magnitude[index, t] = value) function _write_value_to_pf_data!( pf_data::PFS.PowerFlowData, @@ -297,9 +326,21 @@ _update_component!(comp::PSY.Component, ::Val{:active_power}, value) = # Sign is flipped for loads (TODO can we rely on some existing function that encodes this information?) _update_component!(comp::PSY.ElectricLoad, ::Val{:active_power}, value) = (comp.active_power = -value) -_update_component!(comp::PSY.Component, ::Val{:voltage_angle}, value) = +_update_component!(comp::PSY.Component, ::Val{:reactive_power}, value) = + (comp.reactive_power = value) +_update_component!(comp::PSY.ElectricLoad, ::Val{:reactive_power}, value) = + (comp.reactive_power = -value) +_update_component!( + comp::PSY.ACBus, + ::Union{Val{:voltage_angle_export}, Val{:voltage_angle_opf}}, + value, +) = comp.angle = value -_update_component!(comp::PSY.Component, ::Val{:voltage_magnitude}, value) = +_update_component!( + comp::PSY.ACBus, + ::Union{Val{:voltage_magnitude_export}, Val{:voltage_magnitude_opf}}, + value, +) = comp.magnitude = value function update_pf_system!( @@ -387,6 +428,8 @@ _get_pf_result(::Type{PowerFlowLineActivePowerFromTo}, pf_data::PFS.PowerFlowDat PFS.get_branch_activepower_flow_from_to(pf_data) _get_pf_result(::Type{PowerFlowLineActivePowerToFrom}, pf_data::PFS.PowerFlowData) = PFS.get_branch_activepower_flow_to_from(pf_data) +_get_pf_result(::Type{PowerFlowLossFactors}, pf_data::PFS.PowerFlowData) = + pf_data.loss_factors _get_pf_lookup(::Type{<:PSY.Bus}, pf_data::PFS.PowerFlowData) = PFS.get_bus_lookup(pf_data) _get_pf_lookup(::Type{<:PSY.Branch}, pf_data::PFS.PowerFlowData) = diff --git a/test/test_simulation_results.jl b/test/test_simulation_results.jl index eae92cd40..d7ada30c3 100644 --- a/test/test_simulation_results.jl +++ b/test/test_simulation_results.jl @@ -1015,7 +1015,8 @@ end read_result_names(results, key::PSI.OptimizationContainerKey) = Set(names(only(values(PSI.read_results_with_keys(results, [key])))[!, Not(:DateTime)])) -@testset "Test AC power flow in the loop: small system UCED, PSS/E export" begin +@testset "Test AC power flow in the loop: small system UCED, PSS/E export" for calculate_loss_factors in + (true, false) file_path = mktempdir(; cleanup = true) export_path = mktempdir(; cleanup = true) pf_path = mktempdir(; cleanup = true) @@ -1033,6 +1034,7 @@ read_result_names(results, key::PSI.OptimizationContainerKey) = power_flow_evaluation = ACPowerFlow(; exporter = PSSEExportPowerFlow(:v33, pf_path; write_comments = true), + calculate_loss_factors = calculate_loss_factors, ), ), ) @@ -1047,6 +1049,24 @@ read_result_names(results, key::PSI.OptimizationContainerKey) = first_result = first(thermal_results) last_result = last(thermal_results) + available_aux_variables = list_aux_variable_keys(results_ed) + loss_factors_aux_var_key = PSI.AuxVarKey(PowerFlowLossFactors, ACBus) + + # here we check if the loss factors are stored in the results, the values are tested in PowerFlows.jl + if calculate_loss_factors + @test loss_factors_aux_var_key ∈ available_aux_variables + loss_factors = first( + values( + PSI.read_results_with_keys(results_ed, + [loss_factors_aux_var_key]), + ), + ) + @test !isnothing(loss_factors) + @test nrow(loss_factors) == 48 * 12 + else + @test loss_factors_aux_var_key ∉ available_aux_variables + end + @test length(filter(x -> isdir(joinpath(pf_path, x)), readdir(pf_path))) == 48 * 12 first_export = load_pf_export(pf_path, "export_1_1") last_export = load_pf_export(pf_path, "export_48_12")