From b59491746c2d1c961052b5a7274e05002d52040c Mon Sep 17 00:00:00 2001 From: GabrielKS <23368820+GabrielKS@users.noreply.github.com> Date: Fri, 10 Jan 2025 16:56:03 -0700 Subject: [PATCH] Fix bug 74: enforcing reactive power limits for AC power flow --- src/nlsolve_ac_powerflow.jl | 6 ++++-- src/post_processing.jl | 15 +++++++++++++++ test/test_nlsolve_powerflow.jl | 24 ++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/nlsolve_ac_powerflow.jl b/src/nlsolve_ac_powerflow.jl index 813848b0..c463d56f 100644 --- a/src/nlsolve_ac_powerflow.jl +++ b/src/nlsolve_ac_powerflow.jl @@ -1,3 +1,4 @@ +const _SOLVE_AC_POWERFLOW_KWARGS = Set([:check_reactive_power_limits, :check_connectivity]) """ Solves a the power flow into the system and writes the solution into the relevant structs. Updates generators active and reactive power setpoints and branches active and reactive @@ -42,9 +43,10 @@ function solve_ac_powerflow!(system::PSY.System; kwargs...) check_connectivity = get(kwargs, :check_connectivity, true), ) max_iterations = DEFAULT_MAX_REDISTRIBUTION_ITERATIONS - res = _solve_powerflow!(data, check_reactive_power_limits; kwargs...) + solver_kwargs = filter(p -> !(p.first in _SOLVE_AC_POWERFLOW_KWARGS), kwargs) + res = _solve_powerflow!(data, check_reactive_power_limits; solver_kwargs...) if res.f_converged - write_powerflow_solution!(system, res.zero, max_iterations) + write_powerflow_solution!(system, res.zero, data, max_iterations) @info("PowerFlow solve converged, the results have been stored in the system") #Restore original per unit base PSY.set_units_base_system!(system, settings_unit_cache) diff --git a/src/post_processing.jl b/src/post_processing.jl index 20c23810..e6a82022 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -448,12 +448,27 @@ Updates system voltages and powers with power flow results function write_powerflow_solution!( sys::PSY.System, result::Vector{Float64}, + data::PowerFlowData, max_iterations::Int, ) buses = enumerate( sort!(collect(PSY.get_components(PSY.Bus, sys)); by = x -> PSY.get_number(x)), ) + # Handle any changes made manually to the PowerFlowData, not necessarily reflected in the solver result + # Right now the only such change we handle is the one in _check_q_limit_bounds! + for (ix, bus) in buses + system_bustype = PSY.get_bustype(bus) + data_bustype = data.bus_type[ix] + (system_bustype == data_bustype) && continue + @assert system_bustype == PSY.ACBusTypes.PV + @assert data_bustype == PSY.ACBusTypes.PQ + @debug "Updating bus $(PSY.get_name(bus)) reactive power and type to PQ due to check_reactive_power_limits" + Q_gen = data.bus_reactivepower_injection[ix] + _reactive_power_redistribution_pv(sys, Q_gen, bus, max_iterations) + PSY.set_bustype!(bus, data_bustype) + end + for (ix, bus) in buses if bus.bustype == PSY.ACBusTypes.REF P_gen = result[2 * ix - 1] diff --git a/test/test_nlsolve_powerflow.jl b/test/test_nlsolve_powerflow.jl index eb2a2813..b07ae711 100644 --- a/test/test_nlsolve_powerflow.jl +++ b/test/test_nlsolve_powerflow.jl @@ -1,5 +1,6 @@ @testset "NLsolve Power Flow 14-Bus testing" begin sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) + set_units_base_system!(sys, UnitSystem.SYSTEM_BASE) result_14 = [ 2.3255081760423684 -0.15529254415401786 @@ -34,9 +35,28 @@ #Compare results between finite diff methods and Jacobian method res1 = PowerFlows._solve_powerflow!(data, false) @test LinearAlgebra.norm(result_14 - res1.zero) <= 1e-6 - @test solve_ac_powerflow!(sys; method = :newton) - # Test enforcing the reactive power Limits + # Test that solve_ac_powerflow! succeeds + solved1 = deepcopy(sys) + @test solve_ac_powerflow!(solved1) + + # Test that passing check_reactive_power_limits=false is the default and violates limits + solved2 = deepcopy(sys) + @test solve_ac_powerflow!(solved2; check_reactive_power_limits = false) + @test IS.compare_values(solved1, solved2) + @test get_reactive_power(get_component(ThermalStandard, solved2, "Bus8")) > + get_reactive_power_limits(get_component(ThermalStandard, solved2, "Bus8")).max + + # Test that passing check_reactive_power_limits=true fixes that + solved3 = deepcopy(sys) + @test solve_ac_powerflow!(solved3; check_reactive_power_limits = true) + @test get_reactive_power(get_component(ThermalStandard, solved3, "Bus8")) <= + get_reactive_power_limits(get_component(ThermalStandard, solved3, "Bus8")).max + + # Test Newton method + @test solve_ac_powerflow!(deepcopy(sys); method = :newton) + + # Test enforcing the reactive power limits in closer detail set_reactive_power!(get_component(PowerLoad, sys, "Bus4"), 0.0) data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) res2 = PowerFlows._solve_powerflow!(data, true)