From 198a4fe100683611838b2ece7cfab76f1cc0e8c9 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 5 Dec 2024 08:55:27 -0700 Subject: [PATCH 01/23] WIP: implement NR power flow function, rename ACPowerFlow to NLSolveACPowerFlow, introduce KLUACPowerFlow for the use in the new NR power flow function --- src/PowerFlowData.jl | 4 +- src/PowerFlows.jl | 2 +- src/nlsolve_ac_powerflow.jl | 99 ++++++++++++++++++++++++++-------- src/post_processing.jl | 2 +- src/powerflow_types.jl | 6 ++- test/test_nlsolve_powerflow.jl | 46 ++++++++-------- test/test_powerflow_data.jl | 12 ++--- test/test_psse_export.jl | 6 +-- 8 files changed, 118 insertions(+), 59 deletions(-) diff --git a/src/PowerFlowData.jl b/src/PowerFlowData.jl index f064a02c..00c8c4a2 100644 --- a/src/PowerFlowData.jl +++ b/src/PowerFlowData.jl @@ -157,7 +157,7 @@ NOTE: use it for AC power flow computations. WARNING: functions for the evaluation of the multi-period AC PF still to be implemented. """ function PowerFlowData( - ::ACPowerFlow, + ::Union{NLSolveACPowerFlow, KLUACPowerFlow}, sys::PSY.System; time_steps::Int = 1, timestep_names::Vector{String} = String[], @@ -440,7 +440,7 @@ Create an appropriate `PowerFlowContainer` for the given `PowerFlowEvaluationMod """ function make_power_flow_container end -make_power_flow_container(pfem::ACPowerFlow, sys::PSY.System; kwargs...) = +make_power_flow_container(pfem::NLSolveACPowerFlow, sys::PSY.System; kwargs...) = PowerFlowData(pfem, sys; kwargs...) make_power_flow_container(pfem::DCPowerFlow, sys::PSY.System; kwargs...) = diff --git a/src/PowerFlows.jl b/src/PowerFlows.jl index 7d88f80e..feeb0f1d 100644 --- a/src/PowerFlows.jl +++ b/src/PowerFlows.jl @@ -4,7 +4,7 @@ export solve_powerflow export solve_ac_powerflow! export PowerFlowData export DCPowerFlow -export ACPowerFlow +export NLSolveACPowerFlow export PTDFDCPowerFlow export vPTDFDCPowerFlow export PSSEExportPowerFlow diff --git a/src/nlsolve_ac_powerflow.jl b/src/nlsolve_ac_powerflow.jl index 813848b0..576b0cec 100644 --- a/src/nlsolve_ac_powerflow.jl +++ b/src/nlsolve_ac_powerflow.jl @@ -30,29 +30,29 @@ solve_ac_powerflow!(sys) solve_ac_powerflow!(sys, method=:newton) ``` """ -function solve_ac_powerflow!(system::PSY.System; kwargs...) +function solve_ac_powerflow!(pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, system::PSY.System; kwargs...) #Save per-unit flag settings_unit_cache = deepcopy(system.units_settings.unit_system) #Work in System per unit PSY.set_units_base_system!(system, "SYSTEM_BASE") check_reactive_power_limits = get(kwargs, :check_reactive_power_limits, false) data = PowerFlowData( - ACPowerFlow(; check_reactive_power_limits = check_reactive_power_limits), + NLSolveACPowerFlow(; check_reactive_power_limits = check_reactive_power_limits), system; check_connectivity = get(kwargs, :check_connectivity, true), ) max_iterations = DEFAULT_MAX_REDISTRIBUTION_ITERATIONS - res = _solve_powerflow!(data, check_reactive_power_limits; kwargs...) - if res.f_converged - write_powerflow_solution!(system, res.zero, max_iterations) + converged, x = _solve_powerflow!(pf, data, check_reactive_power_limits; kwargs...) + if converged + write_powerflow_solution!(system, x, 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) - return res.f_converged + return converged end - @error("The powerflow solver returned convergence = $(res.f_converged)") + @error("The powerflow solver returned convergence = $(converged)") PSY.set_units_base_system!(system, settings_unit_cache) - return res.f_converged + return converged end """ @@ -68,7 +68,7 @@ res = solve_powerflow(sys, method=:newton) ``` """ function solve_powerflow( - pf::ACPowerFlow, + pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, system::PSY.System; kwargs..., ) @@ -82,18 +82,18 @@ function solve_powerflow( check_connectivity = get(kwargs, :check_connectivity, true), ) - res = _solve_powerflow!(data, pf.check_reactive_power_limits; kwargs...) + converged, x = _solve_powerflow!(pf, data, pf.check_reactive_power_limits; kwargs...) - if res.f_converged + if converged @info("PowerFlow solve converged, the results are exported in DataFrames") - df_results = write_results(pf, system, data, res.zero) + df_results = write_results(pf, system, data, x) #Restore original per unit base PSY.set_units_base_system!(system, settings_unit_cache) return df_results end - @error("The powerflow solver returned convergence = $(res.f_converged)") + @error("The powerflow solver returned convergence = $(converged)") PSY.set_units_base_system!(system, settings_unit_cache) - return res.f_converged + return converged end function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) @@ -124,27 +124,28 @@ function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) end function _solve_powerflow!( + pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, data::ACPowerFlowData, check_reactive_power_limits; nlsolve_kwargs..., ) if check_reactive_power_limits for _ in 1:MAX_REACTIVE_POWER_ITERATIONS - res = _nlsolve_powerflow(data; nlsolve_kwargs...) - if res.f_converged - if _check_q_limit_bounds!(data, res.zero) - return res + converged, x = _nlsolve_powerflow(pf, data; nlsolve_kwargs...) + if converged + if _check_q_limit_bounds!(data, x) + return converged, x end else - return res + return converged, x end end else - return _nlsolve_powerflow(data; nlsolve_kwargs...) + return _nlsolve_powerflow(pf, data; nlsolve_kwargs...) end end -function _nlsolve_powerflow(data::ACPowerFlowData; nlsolve_kwargs...) +function _nlsolve_powerflow(pf::NLSolveACPowerFlow, data::ACPowerFlowData; nlsolve_kwargs...) pf = PolarPowerFlow(data) J = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) @@ -153,5 +154,59 @@ function _nlsolve_powerflow(data::ACPowerFlowData; nlsolve_kwargs...) if !res.f_converged @error("The powerflow solver returned convergence = $(res.f_converged)") end - return res + return res.f_converged, res.zero +end + +function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_kwargs...) + pf = PolarPowerFlow(data) + J_function = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) + + maxIter = 30 + tol = 1e-6 + + V = pf.x0 + Vm = abs.(V) + Va = angle.(V) + + mis = V .* conj(Ybus * V) - Sbus + F = [real(mis[[pv;pq]]); imag(mis[pq])] + + npv = length(pv) + npq = length(pq) + + converged = false + + while i < maxIter && converged + i += 1 + #diagV = Diagonal(V) + #diagIbus = Diagonal(Ybus * V) + #diagVnorm = Diagonal(V./abs.(V)) + #dSbus_dVm = Diagonal(V) * conj(Ybus * diagVnorm) + conj(diagIbus) * diagVnorm + #dSbus_dVa = im * diagV * conj(diagIbus - Ybus * diagV) + #j11 = real(dSbus_dVa[[pv; pq], [pv; pq]]) + #j12 = real(dSbus_dVm[[pv; pq], pq]) + #j21 = imag(dSbus_dVa[pq, [pv; pq]]) + #j22 = imag(dSbus_dVm[pq, pq]) + #J = [j11 j12; j21 j22] + J_function(J_function.Jv, x) + dx = - (J_function.Jv \ F) + + Va[pv] += dx[1:npv] + Va[pq] += dx[npv+1:(npv+npq)] + Vm[pq] += dx[(npv+npq+1):end] + + V = Vm .* exp.(im*Va) + + Vm = abs.(V) + Va = angle.(V) + + mis = V .* conj(Ybus * V) - Sbus + F = [real(mis[[pv;pq]]); imag(mis[pq])] + converged = norm(F, Inf) < tol + end + + if !converged + @error("The powerflow solver with KLU did not converge after $maxIter iterations") + end + return converged, V end diff --git a/src/post_processing.jl b/src/post_processing.jl index 6c500a25..efa5fe6c 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -608,7 +608,7 @@ dictionary will therefore feature just one key linked to one DataFrame. vector containing the reults for one single time-period. """ function write_results( - ::ACPowerFlow, + ::NLSolveACPowerFlow, sys::PSY.System, data::ACPowerFlowData, result::Vector{Float64}, diff --git a/src/powerflow_types.jl b/src/powerflow_types.jl index e1b9828a..65b6b241 100644 --- a/src/powerflow_types.jl +++ b/src/powerflow_types.jl @@ -1,6 +1,10 @@ abstract type PowerFlowEvaluationModel end -Base.@kwdef struct ACPowerFlow <: PowerFlowEvaluationModel +Base.@kwdef struct NLSolveACPowerFlow <: PowerFlowEvaluationModel + check_reactive_power_limits::Bool = false +end + +Base.@kwdef struct KLUACPowerFlow <: PowerFlowEvaluationModel check_reactive_power_limits::Bool = false end diff --git a/test/test_nlsolve_powerflow.jl b/test/test_nlsolve_powerflow.jl index eb2a2813..189cc5b3 100644 --- a/test/test_nlsolve_powerflow.jl +++ b/test/test_nlsolve_powerflow.jl @@ -30,35 +30,35 @@ 1.0213119628726421 -0.2803812119374241 ] - data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) + data = PowerFlows.PowerFlowData(NLSolveACPowerFlow(), sys; check_connectivity = true) #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) + converged1, x1 = PowerFlows._solve_powerflow!(NLSolveACPowerFlow(), data, false) + @test LinearAlgebra.norm(result_14 - x1) <= 1e-6 + @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys; method = :newton) # Test enforcing the reactive power Limits set_reactive_power!(get_component(PowerLoad, sys, "Bus4"), 0.0) - data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) - res2 = PowerFlows._solve_powerflow!(data, true) - @test LinearAlgebra.norm(result_14 - res2.zero) >= 1e-6 - @test 1.08 <= res2.zero[15] <= 1.09 + data = PowerFlows.PowerFlowData(NLSolveACPowerFlow(), sys; check_connectivity = true) + converged2, x2 = PowerFlows._solve_powerflow!(NLSolveACPowerFlow(), data, true) + @test LinearAlgebra.norm(result_14 - x2) >= 1e-6 + @test 1.08 <= x2[15] <= 1.09 end @testset "NLsolve Power Flow 14-Bus Line Configurations" begin sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) - base_res = solve_powerflow(ACPowerFlow(), sys) + base_res = solve_powerflow(NLSolveACPowerFlow(), sys) branch = first(PSY.get_components(Line, sys)) dyn_branch = DynamicBranch(branch) add_component!(sys, dyn_branch) - @test dyn_pf = solve_ac_powerflow!(sys) - dyn_pf = solve_powerflow(ACPowerFlow(), sys) + @test dyn_pf = solve_ac_powerflow!(NLSolveACPowerFlow(), sys) + dyn_pf = solve_powerflow(NLSolveACPowerFlow(), sys) @test LinearAlgebra.norm(dyn_pf["bus_results"].Vm - base_res["bus_results"].Vm) <= 1e-6 sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) - solve_ac_powerflow!(sys) + solve_ac_powerflow!(NLSolveACPowerFlow(), sys) @test PSY.get_active_power_flow(line) == 0.0 test_bus = get_component(PSY.Bus, sys, "Bus 4") @test isapprox(PSY.get_magnitude(test_bus), 1.002; atol = 1e-3) @@ -66,7 +66,7 @@ end sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) - res = solve_powerflow(ACPowerFlow(), sys) + res = solve_powerflow(NLSolveACPowerFlow(), sys) @test res["flow_results"].P_from_to[4] == 0.0 @test res["flow_results"].P_to_from[4] == 0.0 end @@ -78,7 +78,7 @@ end bus_103 = get_component(PSY.Bus, sys_3bus, "BUS 3") fix_shunt = PSY.FixedAdmittance("FixAdmBus3", true, bus_103, 0.0 + 0.2im) add_component!(sys_3bus, fix_shunt) - df = solve_powerflow(ACPowerFlow(), sys_3bus) + df = solve_powerflow(NLSolveACPowerFlow(), sys_3bus) @test isapprox(df["bus_results"].P_gen, p_gen_matpower_3bus, atol = 1e-4) @test isapprox(df["bus_results"].Q_gen, q_gen_matpower_3bus, atol = 1e-4) end @@ -95,7 +95,7 @@ end @test_logs( (:error, "The powerflow solver returned convergence = false"), match_mode = :any, - @test !solve_ac_powerflow!(pf_sys5_re) + @test !solve_ac_powerflow!(NLSolveACPowerFlow(), pf_sys5_re) ) end @@ -114,9 +114,9 @@ end pf_bus_result_file = joinpath(TEST_FILES_DIR, "test_data", "pf_bus_results.csv") pf_gen_result_file = joinpath(TEST_FILES_DIR, "test_data", "pf_gen_results.csv") - pf = solve_ac_powerflow!(system) + pf = solve_ac_powerflow!(NLSolveACPowerFlow(), system) @test pf - pf_result_df = solve_powerflow(ACPowerFlow(), system) + pf_result_df = solve_powerflow(NLSolveACPowerFlow(), system) v_diff, angle_diff, number = psse_bus_results_compare(pf_bus_result_file, pf_result_df) p_diff, q_diff, names = psse_gen_results_compare(pf_gen_result_file, system) @@ -166,13 +166,13 @@ end X_th = 1e-5, ) add_component!(sys, s2) - @test solve_ac_powerflow!(sys) + @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys) #Create power mismatch, test for error set_active_power!(get_component(Source, sys, "source_1"), -0.4) @test_throws ErrorException( "Sources do not match P and/or Q requirements for reference bus.", - ) solve_ac_powerflow!(sys) + ) solve_ac_powerflow!(NLSolveACPowerFlow(), sys) end @testset "AC PowerFlow with Multiple sources at PV" begin @@ -245,11 +245,11 @@ end ) add_component!(sys, s3) - @test solve_ac_powerflow!(sys) + @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys) #Create power mismatch, test for error set_reactive_power!(get_component(Source, sys, "source_3"), -0.5) - @test_throws ErrorException("Sources do not match Q requirements for PV bus.") solve_ac_powerflow!( + @test_throws ErrorException("Sources do not match Q requirements for PV bus.") solve_ac_powerflow!(NLSolveACPowerFlow(), sys, ) end @@ -300,7 +300,7 @@ end ) add_component!(sys, g1) - @test solve_ac_powerflow!(sys) + @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys) @test isapprox( get_active_power(get_component(Source, sys, "source_1")), 0.5; @@ -394,7 +394,7 @@ end ) add_component!(sys, g1) - @test solve_ac_powerflow!(sys) + @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys) @test isapprox( get_active_power(get_component(Source, sys, "source_2")), 0.5; diff --git a/test/test_powerflow_data.jl b/test/test_powerflow_data.jl index 75964cb7..5f91cddc 100644 --- a/test/test_powerflow_data.jl +++ b/test/test_powerflow_data.jl @@ -1,6 +1,6 @@ @testset "PowerFlowData" begin sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) - @test PowerFlowData(ACPowerFlow(), sys) isa PF.ACPowerFlowData + @test PowerFlowData(NLSolveACPowerFlow(), sys) isa PF.ACPowerFlowData @test PowerFlowData(DCPowerFlow(), sys) isa PF.ABAPowerFlowData @test PowerFlowData(PTDFDCPowerFlow(), sys) isa PF.PTDFPowerFlowData @test PowerFlowData(vPTDFDCPowerFlow(), sys) isa PF.vPTDFPowerFlowData @@ -22,18 +22,18 @@ end # TODO test that update_system! errors if the PowerFlowData doesn't correspond to the system sys_original = build_system(PSISystems, "RTS_GMLC_DA_sys") - data_original = PowerFlowData(ACPowerFlow(), sys_original) + data_original = PowerFlowData(NLSolveACPowerFlow(), sys_original) sys_modified = deepcopy(sys_original) modify_rts_system!(sys_modified) - data_modified = PowerFlowData(ACPowerFlow(), sys_original) + data_modified = PowerFlowData(NLSolveACPowerFlow(), sys_original) modify_rts_powerflow!(data_modified) # update_system! with unmodified PowerFlowData should result in system that yields unmodified PowerFlowData # (NOTE does NOT necessarily yield original system due to power redistribution) sys_null_updated = deepcopy(sys_original) PF.update_system!(sys_null_updated, data_original) - data_null_updated = PowerFlowData(ACPowerFlow(), sys_null_updated) + data_null_updated = PowerFlowData(NLSolveACPowerFlow(), sys_null_updated) @test IS.compare_values(powerflow_match_fn, data_null_updated, data_original) # Modified versions should not be the same as unmodified versions @@ -47,7 +47,7 @@ end # Constructing PowerFlowData from modified system should result in data_modified @test IS.compare_values( powerflow_match_fn, - PowerFlowData(ACPowerFlow(), sys_modified), + PowerFlowData(NLSolveACPowerFlow(), sys_modified), data_modified, ) @@ -56,6 +56,6 @@ end sys_modify_updated = deepcopy(sys_original) PF.update_system!(sys_modify_updated, data_modified) sys_mod_redist = deepcopy(sys_modified) - PF.update_system!(sys_mod_redist, PowerFlowData(ACPowerFlow(), sys_mod_redist)) + PF.update_system!(sys_mod_redist, PowerFlowData(NLSolveACPowerFlow(), sys_mod_redist)) @test IS.compare_values(powerflow_match_fn, sys_modify_updated, sys_mod_redist) end diff --git a/test/test_psse_export.jl b/test/test_psse_export.jl index d6abd32d..df7d925d 100644 --- a/test/test_psse_export.jl +++ b/test/test_psse_export.jl @@ -223,8 +223,8 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; end function test_power_flow(sys1::System, sys2::System; exclude_reactive_flow = false) - result1 = solve_powerflow(ACPowerFlow(), sys1) - result2 = solve_powerflow(ACPowerFlow(), sys2) + result1 = solve_powerflow(NLSolveACPowerFlow(), sys1) + result2 = solve_powerflow(NLSolveACPowerFlow(), sys2) reactive_power_tol = exclude_reactive_flow ? nothing : POWERFLOW_COMPARISON_TOLERANCE @test compare_df_within_tolerance("bus_results", result1["bus_results"], @@ -408,7 +408,7 @@ end # Updating with changed value should result in a different reimport (PowerFlowData version) exporter = PSSEExporter(sys, :v33, export_location) - pf2 = PowerFlowData(ACPowerFlow(), sys) + pf2 = PowerFlowData(NLSolveACPowerFlow(), sys) # This modifies the PowerFlowData in the same way that modify_rts_system! modifies the # system, so the reimport should be comparable to sys2 from above modify_rts_powerflow!(pf2) From d4a443acf0e96434c71290336f278a9221307a33 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 5 Dec 2024 12:25:16 -0700 Subject: [PATCH 02/23] WIP: some bugfixes in the PF function --- src/nlsolve_ac_powerflow.jl | 72 +++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/src/nlsolve_ac_powerflow.jl b/src/nlsolve_ac_powerflow.jl index 576b0cec..8f2c4fb6 100644 --- a/src/nlsolve_ac_powerflow.jl +++ b/src/nlsolve_ac_powerflow.jl @@ -159,54 +159,64 @@ end function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_kwargs...) pf = PolarPowerFlow(data) - J_function = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) + #J_function = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) maxIter = 30 tol = 1e-6 + i = 0 - V = pf.x0 + V = copy(pf.x0) Vm = abs.(V) Va = angle.(V) - mis = V .* conj(Ybus * V) - Sbus - F = [real(mis[[pv;pq]]); imag(mis[pq])] + Ybus = pf.data.power_network_matrix + + mis = V .* conj(Ybus * V) - Sbus # TODO Sbus + F = [real(mis[[pv; pq]]); imag(mis[pq])] + + npv = length(pv) # TODO + npq = length(pq) # TODO - npv = length(pv) - npq = length(pq) - converged = false - while i < maxIter && converged + while i < maxIter && !converged i += 1 - #diagV = Diagonal(V) - #diagIbus = Diagonal(Ybus * V) - #diagVnorm = Diagonal(V./abs.(V)) - #dSbus_dVm = Diagonal(V) * conj(Ybus * diagVnorm) + conj(diagIbus) * diagVnorm - #dSbus_dVa = im * diagV * conj(diagIbus - Ybus * diagV) - #j11 = real(dSbus_dVa[[pv; pq], [pv; pq]]) - #j12 = real(dSbus_dVm[[pv; pq], pq]) - #j21 = imag(dSbus_dVa[pq, [pv; pq]]) - #j22 = imag(dSbus_dVm[pq, pq]) - #J = [j11 j12; j21 j22] - J_function(J_function.Jv, x) - dx = - (J_function.Jv \ F) - - Va[pv] += dx[1:npv] - Va[pq] += dx[npv+1:(npv+npq)] - Vm[pq] += dx[(npv+npq+1):end] - - V = Vm .* exp.(im*Va) - + diagV = Diagonal(V) + diagIbus = Diagonal(Ybus * V) + diagVnorm = Diagonal(V ./ abs.(V)) + dSbus_dVm = diagV * conj(Ybus * diagVnorm) + conj(diagIbus) * diagVnorm + dSbus_dVa = 1im * diagV * conj(diagIbus - Ybus * diagV) + + j11 = real(dSbus_dVa[[pv; pq], [pv; pq]]) + j12 = real(dSbus_dVm[[pv; pq], pq]) + j21 = imag(dSbus_dVa[pq, [pv; pq]]) + j22 = imag(dSbus_dVm[pq, pq]) + J = [j11 j12; j21 j22] + + factor_J = klu(J) + dx = -(factor_J \ F) + + #J_function(J_function.Jv, x) + #dx = - (J_function.Jv \ F) + + Va[pv] .+= dx[1:npv] + Va[pq] .+= dx[(npv+1):(npv+npq)] + Vm[pq] .+= dx[(npv+npq+1):(npv+2*npq)] + + V = Vm .* exp.(1im * Va) + Vm = abs.(V) Va = angle.(V) mis = V .* conj(Ybus * V) - Sbus - F = [real(mis[[pv;pq]]); imag(mis[pq])] + F = [real(mis[[pv; pq]]); imag(mis[pq])] converged = norm(F, Inf) < tol end - + if !converged - @error("The powerflow solver with KLU did not converge after $maxIter iterations") + @error("The powerflow solver with KLU did not converge after $i iterations") + else + @info("The powerflow solver with KLU converged after $i iterations") end - return converged, V + return converged, V # TODO make sure the structure of V matches the structure of res.zero end From f2ad409b4553192d459aa4db9babc87682c88289 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 5 Dec 2024 12:27:25 -0700 Subject: [PATCH 03/23] small changes --- src/nlsolve_ac_powerflow.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nlsolve_ac_powerflow.jl b/src/nlsolve_ac_powerflow.jl index 8f2c4fb6..4c32fa4e 100644 --- a/src/nlsolve_ac_powerflow.jl +++ b/src/nlsolve_ac_powerflow.jl @@ -161,8 +161,8 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k pf = PolarPowerFlow(data) #J_function = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) - maxIter = 30 - tol = 1e-6 + maxIter = 30 # TODO + tol = 1e-6 # TODO i = 0 V = copy(pf.x0) From 251f1b306d89e5deae5883fcf49d883705866728 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Fri, 6 Dec 2024 14:47:49 -0700 Subject: [PATCH 04/23] KLU PF: use correct V0; return x in expected format; write results function supports KLU PF; implement tests for KLUACPowerFlow --- Project.toml | 2 + src/PowerFlows.jl | 2 + src/nlsolve_ac_powerflow.jl | 60 ++++++++++++++++++++------- src/post_processing.jl | 2 +- test/test_nlsolve_powerflow.jl | 74 ++++++++++++++++------------------ 5 files changed, 85 insertions(+), 55 deletions(-) diff --git a/Project.toml b/Project.toml index b61464a0..b339e3f3 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +KLU = "ef3ab10e-7fda-4108-b977-705223b18434" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56" @@ -22,6 +23,7 @@ DataStructures = "0.18" Dates = "1" InfrastructureSystems = "2" JSON3 = "1" +KLU = "0.6.0" LinearAlgebra = "1" Logging = "1" NLsolve = "4" diff --git a/src/PowerFlows.jl b/src/PowerFlows.jl index feeb0f1d..499ac42c 100644 --- a/src/PowerFlows.jl +++ b/src/PowerFlows.jl @@ -5,6 +5,7 @@ export solve_ac_powerflow! export PowerFlowData export DCPowerFlow export NLSolveACPowerFlow +export KLUACPowerFlow export PTDFDCPowerFlow export vPTDFDCPowerFlow export PSSEExportPowerFlow @@ -20,6 +21,7 @@ import PowerSystems import PowerSystems: System import LinearAlgebra import NLsolve +import KLU import SparseArrays import InfrastructureSystems import PowerNetworkMatrices diff --git a/src/nlsolve_ac_powerflow.jl b/src/nlsolve_ac_powerflow.jl index 4c32fa4e..c05c7ecd 100644 --- a/src/nlsolve_ac_powerflow.jl +++ b/src/nlsolve_ac_powerflow.jl @@ -165,25 +165,33 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k tol = 1e-6 # TODO i = 0 - V = copy(pf.x0) - Vm = abs.(V) - Va = angle.(V) + Vm = data.bus_magnitude[:] + Va = data.bus_angles[:] + V = Vm .* exp.(1im * Va) - Ybus = pf.data.power_network_matrix + # Find indices for each bus type + ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) + pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) + pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) - mis = V .* conj(Ybus * V) - Sbus # TODO Sbus + Ybus = pf.data.power_network_matrix.data + + Sbus = data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + 1im * (data.bus_reactivepower_injection[:] - data.bus_reactivepower_withdrawals[:]) + + mis = V .* conj(Ybus * V) - Sbus F = [real(mis[[pv; pq]]); imag(mis[pq])] - npv = length(pv) # TODO - npq = length(pq) # TODO + # nref = length(ref) + npv = length(pv) + npq = length(pq) - converged = false + converged = (npv + npq) == 0 # if only ref buses present, we do not need to enter the loop while i < maxIter && !converged i += 1 - diagV = Diagonal(V) - diagIbus = Diagonal(Ybus * V) - diagVnorm = Diagonal(V ./ abs.(V)) + diagV = LinearAlgebra.Diagonal(V) + diagIbus = LinearAlgebra.Diagonal(Ybus * V) + diagVnorm = LinearAlgebra.Diagonal(V ./ abs.(V)) dSbus_dVm = diagV * conj(Ybus * diagVnorm) + conj(diagIbus) * diagVnorm dSbus_dVa = 1im * diagV * conj(diagIbus - Ybus * diagV) @@ -193,7 +201,7 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k j22 = imag(dSbus_dVm[pq, pq]) J = [j11 j12; j21 j22] - factor_J = klu(J) + factor_J = KLU.klu(J) dx = -(factor_J \ F) #J_function(J_function.Jv, x) @@ -210,13 +218,35 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k mis = V .* conj(Ybus * V) - Sbus F = [real(mis[[pv; pq]]); imag(mis[pq])] - converged = norm(F, Inf) < tol + converged = LinearAlgebra.norm(F, Inf) < tol end + if !converged @error("The powerflow solver with KLU did not converge after $i iterations") else - @info("The powerflow solver with KLU converged after $i iterations") + @debug("The powerflow solver with KLU converged after $i iterations") + end + + # mock the expected x format, where the values depend on the type of the bus: + n_buses = length(data.bus_type) + x = Float64[0.0 for _ in 1:(2*n_buses)] + Sbus_result = V .* conj(Ybus * V) + for (ix, b) in enumerate(data.bus_type) + if b == PSY.ACBusTypes.REF + # When bustype == REFERENCE PSY.Bus, state variables are Active and Reactive Power Generated + x[2 * ix - 1] = real(Sbus_result[ix]) + data.bus_activepower_withdrawals[ix] + x[2 * ix] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] + elseif b == PSY.ACBusTypes.PV + # When bustype == PV PSY.Bus, state variables are Reactive Power Generated and Voltage Angle + x[2 * ix - 1] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] + x[2 * ix] = Va[ix] + elseif b == PSY.ACBusTypes.PQ + # When bustype == PQ PSY.Bus, state variables are Voltage Magnitude and Voltage Angle + x[2 * ix - 1] = Vm[ix] + x[2 * ix] = Va[ix] + end end - return converged, V # TODO make sure the structure of V matches the structure of res.zero + + return converged, x end diff --git a/src/post_processing.jl b/src/post_processing.jl index efa5fe6c..9b38e858 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -608,7 +608,7 @@ dictionary will therefore feature just one key linked to one DataFrame. vector containing the reults for one single time-period. """ function write_results( - ::NLSolveACPowerFlow, + ::Union{NLSolveACPowerFlow,KLUACPowerFlow}, sys::PSY.System, data::ACPowerFlowData, result::Vector{Float64}, diff --git a/test/test_nlsolve_powerflow.jl b/test/test_nlsolve_powerflow.jl index 189cc5b3..a11e7124 100644 --- a/test/test_nlsolve_powerflow.jl +++ b/test/test_nlsolve_powerflow.jl @@ -1,5 +1,4 @@ -@testset "NLsolve Power Flow 14-Bus testing" begin - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) +@testset "AC Power Flow 14-Bus testing" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) result_14 = [ 2.3255081760423684 -0.15529254415401786 @@ -30,60 +29,61 @@ 1.0213119628726421 -0.2803812119374241 ] - data = PowerFlows.PowerFlowData(NLSolveACPowerFlow(), sys; check_connectivity = true) + + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) + data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) #Compare results between finite diff methods and Jacobian method - converged1, x1 = PowerFlows._solve_powerflow!(NLSolveACPowerFlow(), data, false) - @test LinearAlgebra.norm(result_14 - x1) <= 1e-6 - @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys; method = :newton) + converged1, x1 = PowerFlows._solve_powerflow!(ACPowerFlow(), data, false) + @test LinearAlgebra.norm(result_14 - x1, Inf) <= 1e-6 + @test solve_ac_powerflow!(ACPowerFlow(), sys; method = :newton) # Test enforcing the reactive power Limits set_reactive_power!(get_component(PowerLoad, sys, "Bus4"), 0.0) - data = PowerFlows.PowerFlowData(NLSolveACPowerFlow(), sys; check_connectivity = true) - converged2, x2 = PowerFlows._solve_powerflow!(NLSolveACPowerFlow(), data, true) - @test LinearAlgebra.norm(result_14 - x2) >= 1e-6 + data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) + converged2, x2 = PowerFlows._solve_powerflow!(ACPowerFlow(), data, true) + @test LinearAlgebra.norm(result_14 - x2, Inf) >= 1e-6 @test 1.08 <= x2[15] <= 1.09 end -@testset "NLsolve Power Flow 14-Bus Line Configurations" begin +@testset "AC Power Flow 14-Bus Line Configurations" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) - base_res = solve_powerflow(NLSolveACPowerFlow(), sys) + base_res = solve_powerflow(ACPowerFlow(), sys) branch = first(PSY.get_components(Line, sys)) dyn_branch = DynamicBranch(branch) add_component!(sys, dyn_branch) - @test dyn_pf = solve_ac_powerflow!(NLSolveACPowerFlow(), sys) - dyn_pf = solve_powerflow(NLSolveACPowerFlow(), sys) - @test LinearAlgebra.norm(dyn_pf["bus_results"].Vm - base_res["bus_results"].Vm) <= - 1e-6 + @test dyn_pf = solve_ac_powerflow!(ACPowerFlow(), sys) + dyn_pf = solve_powerflow(ACPowerFlow(), sys) + @test LinearAlgebra.norm(dyn_pf["bus_results"].Vm - base_res["bus_results"].Vm, Inf) <= 1e-6 sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) - solve_ac_powerflow!(NLSolveACPowerFlow(), sys) + solve_ac_powerflow!(ACPowerFlow(), sys) @test PSY.get_active_power_flow(line) == 0.0 test_bus = get_component(PSY.Bus, sys, "Bus 4") - @test isapprox(PSY.get_magnitude(test_bus), 1.002; atol = 1e-3) + @test isapprox(PSY.get_magnitude(test_bus), 1.002; atol = 1e-3, rtol=0) sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) - res = solve_powerflow(NLSolveACPowerFlow(), sys) + res = solve_powerflow(ACPowerFlow(), sys) @test res["flow_results"].P_from_to[4] == 0.0 @test res["flow_results"].P_to_from[4] == 0.0 end -@testset "NLsolve Power Flow 3-Bus Fixed FixedAdmittance testing" begin +@testset "AC Power Flow 3-Bus Fixed FixedAdmittance testing" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) p_gen_matpower_3bus = [20.3512373930753, 100.0, 100.0] q_gen_matpower_3bus = [45.516916781567232, 10.453799727283879, -31.992561631394636] sys_3bus = PSB.build_system(PSB.PSYTestSystems, "psse_3bus_gen_cls_sys") bus_103 = get_component(PSY.Bus, sys_3bus, "BUS 3") fix_shunt = PSY.FixedAdmittance("FixAdmBus3", true, bus_103, 0.0 + 0.2im) add_component!(sys_3bus, fix_shunt) - df = solve_powerflow(NLSolveACPowerFlow(), sys_3bus) + df = solve_powerflow(ACPowerFlow(), sys_3bus) @test isapprox(df["bus_results"].P_gen, p_gen_matpower_3bus, atol = 1e-4) @test isapprox(df["bus_results"].Q_gen, q_gen_matpower_3bus, atol = 1e-4) end -@testset "NLsolve Power Flow convergence fail testing" begin +@testset "AC Power Flow convergence fail testing" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) pf_sys5_re = PSB.build_system(PSB.PSITestSystems, "c_sys5_re"; add_forecasts = false) remove_component!(Line, pf_sys5_re, "1") remove_component!(Line, pf_sys5_re, "2") @@ -95,11 +95,11 @@ end @test_logs( (:error, "The powerflow solver returned convergence = false"), match_mode = :any, - @test !solve_ac_powerflow!(NLSolveACPowerFlow(), pf_sys5_re) + @test !solve_ac_powerflow!(ACPowerFlow(), pf_sys5_re) ) end -@testset "Test 240 Case PSS/e results" begin +@testset "AC Test 240 Case PSS/e results" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) file = joinpath( TEST_FILES_DIR, "test_data", @@ -114,9 +114,9 @@ end pf_bus_result_file = joinpath(TEST_FILES_DIR, "test_data", "pf_bus_results.csv") pf_gen_result_file = joinpath(TEST_FILES_DIR, "test_data", "pf_gen_results.csv") - pf = solve_ac_powerflow!(NLSolveACPowerFlow(), system) + pf = solve_ac_powerflow!(ACPowerFlow(), system) @test pf - pf_result_df = solve_powerflow(NLSolveACPowerFlow(), system) + pf_result_df = solve_powerflow(ACPowerFlow(), system) v_diff, angle_diff, number = psse_bus_results_compare(pf_bus_result_file, pf_result_df) p_diff, q_diff, names = psse_gen_results_compare(pf_gen_result_file, system) @@ -132,7 +132,7 @@ end @test norm(q_diff, 2) / length(q_diff) < DIFF_L2_TOLERANCE end -@testset "Multiple sources at ref" begin +@testset "AC Multiple sources at ref" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b = ACBus(; number = 1, @@ -166,16 +166,14 @@ end X_th = 1e-5, ) add_component!(sys, s2) - @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys) + @test solve_ac_powerflow!(ACPowerFlow(), sys) #Create power mismatch, test for error set_active_power!(get_component(Source, sys, "source_1"), -0.4) - @test_throws ErrorException( - "Sources do not match P and/or Q requirements for reference bus.", - ) solve_ac_powerflow!(NLSolveACPowerFlow(), sys) + @test_throws ErrorException("Sources do not match P and/or Q requirements for reference bus.") solve_ac_powerflow!(ACPowerFlow(), sys) end -@testset "AC PowerFlow with Multiple sources at PV" begin +@testset "AC PowerFlow with Multiple sources at PV" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b1 = ACBus(; number = 1, @@ -245,16 +243,14 @@ end ) add_component!(sys, s3) - @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys) + @test solve_ac_powerflow!(ACPowerFlow(), sys) #Create power mismatch, test for error set_reactive_power!(get_component(Source, sys, "source_3"), -0.5) - @test_throws ErrorException("Sources do not match Q requirements for PV bus.") solve_ac_powerflow!(NLSolveACPowerFlow(), - sys, - ) + @test_throws ErrorException("Sources do not match Q requirements for PV bus.") solve_ac_powerflow!(ACPowerFlow(), sys) end -@testset "AC PowerFlow Source + non-source at Ref" begin +@testset "AC PowerFlow Source + non-source at Ref" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b = ACBus(; number = 1, @@ -300,7 +296,7 @@ end ) add_component!(sys, g1) - @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys) + @test solve_ac_powerflow!(ACPowerFlow(), sys) @test isapprox( get_active_power(get_component(Source, sys, "source_1")), 0.5; @@ -313,7 +309,7 @@ end ) end -@testset "AC PowerFlow Source + non-source at PV" begin +@testset "AC PowerFlow Source + non-source at PV" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b1 = ACBus(; number = 1, @@ -394,7 +390,7 @@ end ) add_component!(sys, g1) - @test solve_ac_powerflow!(NLSolveACPowerFlow(), sys) + @test solve_ac_powerflow!(ACPowerFlow(), sys) @test isapprox( get_active_power(get_component(Source, sys, "source_2")), 0.5; From 210937c1fdc2f58460211ce8188645b04f787e5e Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Fri, 6 Dec 2024 15:01:14 -0700 Subject: [PATCH 05/23] reformat code --- src/PowerFlowData.jl | 58 +++--- src/nlsolve_ac_powerflow.jl | 26 +-- src/post_processing.jl | 148 +++++++------- test/test_nlsolve_powerflow.jl | 346 ++++++++++++++++----------------- test/test_powerflow_data.jl | 10 +- test/test_psse_export.jl | 68 +++---- 6 files changed, 328 insertions(+), 328 deletions(-) diff --git a/src/PowerFlowData.jl b/src/PowerFlowData.jl index 00c8c4a2..ac0f198f 100644 --- a/src/PowerFlowData.jl +++ b/src/PowerFlowData.jl @@ -66,11 +66,11 @@ flows and angles, as well as these ones. - `neighbors::Vector{Set{Int}}`: Vector with the sets of adjacent buses. """ struct PowerFlowData{ - M <: PNM.PowerNetworkMatrix, - N <: Union{PNM.PowerNetworkMatrix, Nothing}, + M<:PNM.PowerNetworkMatrix, + N<:Union{PNM.PowerNetworkMatrix,Nothing}, } <: PowerFlowContainer - bus_lookup::Dict{Int, Int} - branch_lookup::Dict{String, Int} + bus_lookup::Dict{Int,Int} + branch_lookup::Dict{String,Int} bus_activepower_injection::Matrix{Float64} bus_reactivepower_injection::Matrix{Float64} bus_activepower_withdrawals::Matrix{Float64} @@ -81,7 +81,7 @@ struct PowerFlowData{ bus_magnitude::Matrix{Float64} bus_angles::Matrix{Float64} branch_flow_values::Matrix{Float64} - timestep_map::Dict{Int, String} + timestep_map::Dict{Int,String} valid_ix::Vector{Int} power_network_matrix::M aux_network_matrix::N @@ -119,8 +119,8 @@ end # TODO -> MULTI PERIOD: AC Power Flow Data function _calculate_neighbors( Yb::PNM.Ybus{ - Tuple{Vector{Int64}, Vector{Int64}}, - Tuple{Dict{Int64, Int64}, Dict{Int64, Int64}}, + Tuple{Vector{Int64},Vector{Int64}}, + Tuple{Dict{Int64,Int64},Dict{Int64,Int64}}, }, ) I, J, V = SparseArrays.findnz(Yb.data) @@ -157,11 +157,11 @@ NOTE: use it for AC power flow computations. WARNING: functions for the evaluation of the multi-period AC PF still to be implemented. """ function PowerFlowData( - ::Union{NLSolveACPowerFlow, KLUACPowerFlow}, + ::Union{NLSolveACPowerFlow,KLUACPowerFlow}, sys::PSY.System; - time_steps::Int = 1, - timestep_names::Vector{String} = String[], - check_connectivity::Bool = true) + time_steps::Int=1, + timestep_names::Vector{String}=String[], + check_connectivity::Bool=true) # assign timestep_names # timestep names are then allocated in a dictionary to map matrix columns @@ -174,7 +174,7 @@ function PowerFlowData( end # get data for calculations - power_network_matrix = PNM.Ybus(sys; check_connectivity = check_connectivity) + power_network_matrix = PNM.Ybus(sys; check_connectivity=check_connectivity) # get number of buses and branches n_buses = length(axes(power_network_matrix, 1)) @@ -185,8 +185,8 @@ function PowerFlowData( n_branches = length(branches) bus_lookup = power_network_matrix.lookup[2] - branch_lookup = Dict{String, Int}() - temp_bus_map = Dict{Int, String}( + branch_lookup = Dict{String,Int}() + temp_bus_map = Dict{Int,String}( PSY.get_number(b) => PSY.get_name(b) for b in PSY.get_components(PSY.Bus, sys) ) branch_types = Vector{DataType}(undef, n_branches) @@ -250,9 +250,9 @@ NOTE: use it for DC power flow computations. function PowerFlowData( ::DCPowerFlow, sys::PSY.System; - time_steps::Int = 1, - timestep_names::Vector{String} = String[], - check_connectivity::Bool = true) + time_steps::Int=1, + timestep_names::Vector{String}=String[], + check_connectivity::Bool=true) # assign timestep_names # timestep names are then allocated in a dictionary to map matrix columns @@ -263,7 +263,7 @@ function PowerFlowData( end # get the network matrices - power_network_matrix = PNM.ABA_Matrix(sys; factorize = true) + power_network_matrix = PNM.ABA_Matrix(sys; factorize=true) aux_network_matrix = PNM.BA_Matrix(sys) # get number of buses and branches @@ -272,7 +272,7 @@ function PowerFlowData( bus_lookup = aux_network_matrix.lookup[1] branch_lookup = aux_network_matrix.lookup[2] - temp_bus_map = Dict{Int, String}( + temp_bus_map = Dict{Int,String}( PSY.get_number(b) => PSY.get_name(b) for b in PSY.get_components(PSY.ACBus, sys) ) valid_ix = setdiff(1:n_buses, aux_network_matrix.ref_bus_positions) @@ -317,9 +317,9 @@ NOTE: use it for DC power flow computations. function PowerFlowData( ::PTDFDCPowerFlow, sys::PSY.System; - time_steps::Int = 1, - timestep_names::Vector{String} = String[], - check_connectivity::Bool = true) + time_steps::Int=1, + timestep_names::Vector{String}=String[], + check_connectivity::Bool=true) # assign timestep_names # timestep names are then allocated in a dictionary to map matrix columns @@ -333,7 +333,7 @@ function PowerFlowData( # get the network matrices power_network_matrix = PNM.PTDF(sys) - aux_network_matrix = PNM.ABA_Matrix(sys; factorize = true) + aux_network_matrix = PNM.ABA_Matrix(sys; factorize=true) # get number of buses and branches n_buses = length(axes(power_network_matrix, 1)) @@ -341,7 +341,7 @@ function PowerFlowData( bus_lookup = power_network_matrix.lookup[1] branch_lookup = power_network_matrix.lookup[2] - temp_bus_map = Dict{Int, String}( + temp_bus_map = Dict{Int,String}( PSY.get_number(b) => PSY.get_name(b) for b in PSY.get_components(PSY.Bus, sys) ) valid_ix = setdiff(1:n_buses, aux_network_matrix.ref_bus_positions) @@ -385,9 +385,9 @@ NOTE: use it for DC power flow computations. function PowerFlowData( ::vPTDFDCPowerFlow, sys::PSY.System; - time_steps::Int = 1, - timestep_names::Vector{String} = String[], - check_connectivity::Bool = true) + time_steps::Int=1, + timestep_names::Vector{String}=String[], + check_connectivity::Bool=true) # assign timestep_names # timestep names are then allocated in a dictionary to map matrix columns @@ -401,7 +401,7 @@ function PowerFlowData( # get the network matrices power_network_matrix = PNM.VirtualPTDF(sys) # evaluates an empty virtual PTDF - aux_network_matrix = PNM.ABA_Matrix(sys; factorize = true) + aux_network_matrix = PNM.ABA_Matrix(sys; factorize=true) # get number of buses and branches n_buses = length(axes(power_network_matrix, 2)) @@ -409,7 +409,7 @@ function PowerFlowData( bus_lookup = power_network_matrix.lookup[2] branch_lookup = power_network_matrix.lookup[1] - temp_bus_map = Dict{Int, String}( + temp_bus_map = Dict{Int,String}( PSY.get_number(b) => PSY.get_name(b) for b in PSY.get_components(PSY.Bus, sys) ) valid_ix = setdiff(1:n_buses, aux_network_matrix.ref_bus_positions) diff --git a/src/nlsolve_ac_powerflow.jl b/src/nlsolve_ac_powerflow.jl index c05c7ecd..57468aca 100644 --- a/src/nlsolve_ac_powerflow.jl +++ b/src/nlsolve_ac_powerflow.jl @@ -30,16 +30,16 @@ solve_ac_powerflow!(sys) solve_ac_powerflow!(sys, method=:newton) ``` """ -function solve_ac_powerflow!(pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, system::PSY.System; kwargs...) +function solve_ac_powerflow!(pf::Union{NLSolveACPowerFlow,KLUACPowerFlow}, system::PSY.System; kwargs...) #Save per-unit flag settings_unit_cache = deepcopy(system.units_settings.unit_system) #Work in System per unit PSY.set_units_base_system!(system, "SYSTEM_BASE") check_reactive_power_limits = get(kwargs, :check_reactive_power_limits, false) data = PowerFlowData( - NLSolveACPowerFlow(; check_reactive_power_limits = check_reactive_power_limits), + NLSolveACPowerFlow(; check_reactive_power_limits=check_reactive_power_limits), system; - check_connectivity = get(kwargs, :check_connectivity, true), + check_connectivity=get(kwargs, :check_connectivity, true), ) max_iterations = DEFAULT_MAX_REDISTRIBUTION_ITERATIONS converged, x = _solve_powerflow!(pf, data, check_reactive_power_limits; kwargs...) @@ -68,7 +68,7 @@ res = solve_powerflow(sys, method=:newton) ``` """ function solve_powerflow( - pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, + pf::Union{NLSolveACPowerFlow,KLUACPowerFlow}, system::PSY.System; kwargs..., ) @@ -79,7 +79,7 @@ function solve_powerflow( data = PowerFlowData( pf, system; - check_connectivity = get(kwargs, :check_connectivity, true), + check_connectivity=get(kwargs, :check_connectivity, true), ) converged, x = _solve_powerflow!(pf, data, pf.check_reactive_power_limits; kwargs...) @@ -101,7 +101,7 @@ function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) within_limits = true for (ix, b) in enumerate(data.bus_type) if b == PSY.ACBusTypes.PV - Q_gen = zero[2 * ix - 1] + Q_gen = zero[2*ix-1] else continue end @@ -124,7 +124,7 @@ function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) end function _solve_powerflow!( - pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, + pf::Union{NLSolveACPowerFlow,KLUACPowerFlow}, data::ACPowerFlowData, check_reactive_power_limits; nlsolve_kwargs..., @@ -235,16 +235,16 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k for (ix, b) in enumerate(data.bus_type) if b == PSY.ACBusTypes.REF # When bustype == REFERENCE PSY.Bus, state variables are Active and Reactive Power Generated - x[2 * ix - 1] = real(Sbus_result[ix]) + data.bus_activepower_withdrawals[ix] - x[2 * ix] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] + x[2*ix-1] = real(Sbus_result[ix]) + data.bus_activepower_withdrawals[ix] + x[2*ix] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] elseif b == PSY.ACBusTypes.PV # When bustype == PV PSY.Bus, state variables are Reactive Power Generated and Voltage Angle - x[2 * ix - 1] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] - x[2 * ix] = Va[ix] + x[2*ix-1] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] + x[2*ix] = Va[ix] elseif b == PSY.ACBusTypes.PQ # When bustype == PQ PSY.Bus, state variables are Voltage Magnitude and Voltage Angle - x[2 * ix - 1] = Vm[ix] - x[2 * ix] = Va[ix] + x[2*ix-1] = Vm[ix] + x[2*ix] = Va[ix] end end diff --git a/src/post_processing.jl b/src/post_processing.jl index 9b38e858..f8473b2f 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -147,7 +147,7 @@ function _get_fixed_admittance_power( if (l.bus == b) Vm_squared = if b.bustype == PSY.ACBusTypes.PQ - result[2 * ix - 1]^2 + result[2*ix-1]^2 else PSY.get_magnitude(b)^2 end @@ -163,11 +163,11 @@ function _get_limits_for_power_distribution(gen::PSY.StaticInjection) end function _get_limits_for_power_distribution(gen::PSY.RenewableDispatch) - return (min = 0.0, max = PSY.get_max_active_power(gen)) + return (min=0.0, max=PSY.get_max_active_power(gen)) end function _get_limits_for_power_distribution(gen::PSY.Storage) - return (min = 0.0, max = PSY.get_output_active_power_limits(gen).max) + return (min=0.0, max=PSY.get_output_active_power_limits(gen).max) end function _power_redistribution_ref( @@ -188,8 +188,8 @@ function _power_redistribution_ref( elseif length(sources) > 1 && length(non_source_devices) == 0 Psources = sum(PSY.get_active_power.(sources)) Qsources = sum(PSY.get_reactive_power.(sources)) - if isapprox(Psources, P_gen; atol = 0.001) && - isapprox(Qsources, Q_gen; atol = 0.001) + if isapprox(Psources, P_gen; atol=0.001) && + isapprox(Qsources, Q_gen; atol=0.001) @warn "Only sources found at reference bus --- no redistribution of active or reactive power will take place" return else @@ -204,7 +204,7 @@ function _power_redistribution_ref( return elseif length(devices_) > 1 devices = - sort(collect(devices_); by = x -> _get_limits_for_power_distribution(x).max) + sort(collect(devices_); by=x -> _get_limits_for_power_distribution(x).max) else error("No devices in bus $(PSY.get_name(bus))") end @@ -226,14 +226,14 @@ function _power_redistribution_ref( p_residual -= p_set_point end - if !isapprox(p_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) + if !isapprox(p_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) @debug "Ref Bus voltage residual $p_residual" removed_power = sum([ g.max for g in _get_limits_for_power_distribution.(devices[units_at_limit]) ]) reallocated_p = 0.0 it = 0 - while !isapprox(p_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) + while !isapprox(p_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) if length(devices) == length(units_at_limit) + 1 @warn "all devices at the active Power Limit" break @@ -255,7 +255,7 @@ function _power_redistribution_ref( reallocated_p += p_frac end p_residual -= reallocated_p - if isapprox(p_residual, 0; atol = ISAPPROX_ZERO_TOLERANCE) + if isapprox(p_residual, 0; atol=ISAPPROX_ZERO_TOLERANCE) break end it += 1 @@ -263,7 +263,7 @@ function _power_redistribution_ref( break end end - if !isapprox(p_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) + if !isapprox(p_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) remaining_unit_index = setdiff(1:length(devices), units_at_limit) @assert length(remaining_unit_index) == 1 remaining_unit_index device = devices[remaining_unit_index[1]] @@ -298,7 +298,7 @@ function _reactive_power_redistribution_pv( @warn "Found sources and non-source devices at the same bus. Reactive power re-distribution is not well defined for this case. Source reactive power will remain unchanged and remaining reactive power will be re-distributed among non-source devices." elseif length(sources) > 1 && length(non_source_devices) == 0 Qsources = sum(PSY.get_reactive_power.(sources)) - if isapprox(Qsources, Q_gen; atol = 0.001) + if isapprox(Qsources, Q_gen; atol=0.001) @warn "Only sources found at PV bus --- no redistribution of reactive power will take place" return else @@ -311,14 +311,14 @@ function _reactive_power_redistribution_pv( PSY.set_reactive_power!(first(devices_), Q_gen) return elseif length(devices_) > 1 - devices = sort(collect(devices_); by = x -> PSY.get_max_reactive_power(x)) + devices = sort(collect(devices_); by=x -> PSY.get_max_reactive_power(x)) else error("No devices in bus $(PSY.get_name(bus))") end total_active_power = sum(PSY.get_active_power.(devices)) - if isapprox(total_active_power, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) + if isapprox(total_active_power, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) @debug "Total Active Power Output at the bus is $(total_active_power). Using Unit's Base Power" sum_basepower = sum(PSY.get_base_power.(devices)) for d in devices @@ -333,8 +333,8 @@ function _reactive_power_redistribution_pv( for (ix, d) in enumerate(devices) q_limits = PSY.get_reactive_power_limits(d) - if isapprox(q_limits.max, 0.0; atol = BOUNDS_TOLERANCE) && - isapprox(q_limits.min, 0.0; atol = BOUNDS_TOLERANCE) + if isapprox(q_limits.max, 0.0; atol=BOUNDS_TOLERANCE) && + isapprox(q_limits.min, 0.0; atol=BOUNDS_TOLERANCE) push!(units_at_limit, ix) @info "Unit $(PSY.get_name(d)) has no Q control capability. Q_max = $(q_limits.max) Q_min = $(q_limits.min)" continue @@ -361,14 +361,14 @@ function _reactive_power_redistribution_pv( PSY.set_reactive_power!(d, q_set_point) q_residual -= q_set_point - if isapprox(q_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) + if isapprox(q_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) break end end - if !isapprox(q_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) + if !isapprox(q_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) it = 0 - while !isapprox(q_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) + while !isapprox(q_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) if length(devices) == length(units_at_limit) + 1 @debug "Only one device not at the limit in Bus" break @@ -407,7 +407,7 @@ function _reactive_power_redistribution_pv( PSY.set_reactive_power!(d, q_set_point) end q_residual -= reallocated_q - if isapprox(q_residual, 0; atol = ISAPPROX_ZERO_TOLERANCE) + if isapprox(q_residual, 0; atol=ISAPPROX_ZERO_TOLERANCE) break end it += 1 @@ -419,7 +419,7 @@ function _reactive_power_redistribution_pv( end # Last attempt to allocate reactive power - if !isapprox(q_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) + if !isapprox(q_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) remaining_unit_index = setdiff(1:length(devices), units_at_limit) @assert length(remaining_unit_index) == 1 remaining_unit_index device = devices[remaining_unit_index[1]] @@ -436,7 +436,7 @@ function _reactive_power_redistribution_pv( @assert isapprox( sum(PSY.get_reactive_power.(devices)), Q_gen; - atol = ISAPPROX_ZERO_TOLERANCE, + atol=ISAPPROX_ZERO_TOLERANCE, ) return @@ -451,21 +451,21 @@ function write_powerflow_solution!( max_iterations::Int, ) buses = enumerate( - sort!(collect(PSY.get_components(PSY.Bus, sys)); by = x -> PSY.get_number(x)), + sort!(collect(PSY.get_components(PSY.Bus, sys)); by=x -> PSY.get_number(x)), ) for (ix, bus) in buses if bus.bustype == PSY.ACBusTypes.REF - P_gen = result[2 * ix - 1] - Q_gen = result[2 * ix] + P_gen = result[2*ix-1] + Q_gen = result[2*ix] _power_redistribution_ref(sys, P_gen, Q_gen, bus, max_iterations) elseif bus.bustype == PSY.ACBusTypes.PV - Q_gen = result[2 * ix - 1] - bus.angle = result[2 * ix] + Q_gen = result[2*ix-1] + bus.angle = result[2*ix] _reactive_power_redistribution_pv(sys, Q_gen, bus, max_iterations) elseif bus.bustype == PSY.ACBusTypes.PQ - Vm = result[2 * ix - 1] - θ = result[2 * ix] + Vm = result[2*ix-1] + θ = result[2*ix] PSY.set_magnitude!(bus, Vm) PSY.set_angle!(bus, θ) end @@ -481,7 +481,7 @@ function _get_branches_buses(data::ABAPowerFlowData) end # returns list of branches names and buses numbers: PTDF and virtual PTDF case -function _get_branches_buses(data::Union{PTDFPowerFlowData, vPTDFPowerFlowData}) +function _get_branches_buses(data::Union{PTDFPowerFlowData,vPTDFPowerFlowData}) PNM.get_branch_ax(data.power_network_matrix) PNM.get_bus_ax(data.power_network_matrix) return PNM.get_branch_ax(data.power_network_matrix), @@ -513,28 +513,28 @@ function _allocate_results_data( end bus_df = DataFrames.DataFrame(; - bus_number = buses, - Vm = bus_magnitude, - θ = bus_angles, - P_gen = P_gen_vect, - P_load = P_load_vect, - P_net = P_gen_vect - P_load_vect, - Q_gen = Q_gen_vect, - Q_load = Q_load_vect, - Q_net = Q_gen_vect - Q_load_vect, + bus_number=buses, + Vm=bus_magnitude, + θ=bus_angles, + P_gen=P_gen_vect, + P_load=P_load_vect, + P_net=P_gen_vect - P_load_vect, + Q_gen=Q_gen_vect, + Q_load=Q_load_vect, + Q_net=Q_gen_vect - Q_load_vect, ) DataFrames.sort!(bus_df, :bus_number) branch_df = DataFrames.DataFrame(; - line_name = branches, - bus_from = from_bus, - bus_to = to_bus, - P_from_to = P_from_to_vect, - Q_from_to = Q_from_to_vect, - P_to_from = P_to_from_vect, - Q_to_from = Q_to_from_vect, - P_losses = zeros(length(branches)), - Q_losses = zeros(length(branches)), + line_name=branches, + bus_from=from_bus, + bus_to=to_bus, + P_from_to=P_from_to_vect, + Q_from_to=Q_from_to_vect, + P_to_from=P_to_from_vect, + Q_to_from=Q_to_from_vect, + P_losses=zeros(length(branches)), + Q_losses=zeros(length(branches)), ) DataFrames.sort!(branch_df, [:bus_from, :bus_to]) @@ -553,7 +553,7 @@ results. container storing the systam information. """ function write_results( - data::Union{PTDFPowerFlowData, vPTDFPowerFlowData, ABAPowerFlowData}, + data::Union{PTDFPowerFlowData,vPTDFPowerFlowData,ABAPowerFlowData}, sys::PSY.System, ) @info("Voltages are exported in pu. Powers are exported in MW/MVAr.") @@ -572,7 +572,7 @@ function write_results( to_bus[i] = PSY.get_number(PSY.get_arc(br).to) end - result_dict = Dict{Union{String, Char}, Dict{String, DataFrames.DataFrame}}() + result_dict = Dict{Union{String,Char},Dict{String,DataFrames.DataFrame}}() for i in 1:length(data.timestep_map) temp_dict = _allocate_results_data( branches, @@ -614,7 +614,7 @@ function write_results( result::Vector{Float64}, ) @info("Voltages are exported in pu. Powers are exported in MW/MVAr.") - buses = sort!(collect(PSY.get_components(PSY.Bus, sys)); by = x -> PSY.get_number(x)) + buses = sort!(collect(PSY.get_components(PSY.Bus, sys)); by=x -> PSY.get_number(x)) N_BUS = length(buses) bus_map = Dict(buses .=> 1:N_BUS) sys_basepower = PSY.get_base_power(sys) @@ -634,16 +634,16 @@ function write_results( if bustype == PSY.ACBusTypes.REF Vm_vect[ix] = data.bus_magnitude[ix] θ_vect[ix] = data.bus_angles[ix] - P_gen_vect[ix] = result[2 * ix - 1] * sys_basepower - Q_gen_vect[ix] = result[2 * ix] * sys_basepower + P_gen_vect[ix] = result[2*ix-1] * sys_basepower + Q_gen_vect[ix] = result[2*ix] * sys_basepower elseif bustype == PSY.ACBusTypes.PV Vm_vect[ix] = data.bus_magnitude[ix] - θ_vect[ix] = result[2 * ix] + θ_vect[ix] = result[2*ix] P_gen_vect[ix] = data.bus_activepower_injection[ix] * sys_basepower - Q_gen_vect[ix] = result[2 * ix - 1] * sys_basepower + Q_gen_vect[ix] = result[2*ix-1] * sys_basepower elseif bustype == PSY.ACBusTypes.PQ - Vm_vect[ix] = result[2 * ix - 1] - θ_vect[ix] = result[2 * ix] + Vm_vect[ix] = result[2*ix-1] + θ_vect[ix] = result[2*ix] P_gen_vect[ix] = data.bus_activepower_injection[ix] * sys_basepower Q_gen_vect[ix] = data.bus_reactivepower_injection[ix] * sys_basepower end @@ -666,27 +666,27 @@ function write_results( end bus_df = DataFrames.DataFrame(; - bus_number = PSY.get_number.(buses), - Vm = Vm_vect, - θ = θ_vect, - P_gen = P_gen_vect, - P_load = P_load_vect, - P_net = P_gen_vect - P_load_vect, - Q_gen = Q_gen_vect, - Q_load = Q_load_vect, - Q_net = Q_gen_vect - Q_load_vect, + bus_number=PSY.get_number.(buses), + Vm=Vm_vect, + θ=θ_vect, + P_gen=P_gen_vect, + P_load=P_load_vect, + P_net=P_gen_vect - P_load_vect, + Q_gen=Q_gen_vect, + Q_load=Q_load_vect, + Q_net=Q_gen_vect - Q_load_vect, ) branch_df = DataFrames.DataFrame(; - line_name = PSY.get_name.(branches), - bus_from = PSY.get_number.(PSY.get_from.(PSY.get_arc.(branches))), - bus_to = PSY.get_number.(PSY.get_to.(PSY.get_arc.(branches))), - P_from_to = P_from_to_vect, - Q_from_to = Q_from_to_vect, - P_to_from = P_to_from_vect, - Q_to_from = Q_to_from_vect, - P_losses = P_from_to_vect + P_to_from_vect, - Q_losses = Q_from_to_vect + Q_to_from_vect, + line_name=PSY.get_name.(branches), + bus_from=PSY.get_number.(PSY.get_from.(PSY.get_arc.(branches))), + bus_to=PSY.get_number.(PSY.get_to.(PSY.get_arc.(branches))), + P_from_to=P_from_to_vect, + Q_from_to=Q_from_to_vect, + P_to_from=P_to_from_vect, + Q_to_from=Q_to_from_vect, + P_losses=P_from_to_vect + P_to_from_vect, + Q_losses=Q_from_to_vect + Q_to_from_vect, ) DataFrames.sort!(branch_df, [:bus_from, :bus_to]) diff --git a/test/test_nlsolve_powerflow.jl b/test/test_nlsolve_powerflow.jl index a11e7124..138b3058 100644 --- a/test/test_nlsolve_powerflow.jl +++ b/test/test_nlsolve_powerflow.jl @@ -30,23 +30,23 @@ -0.2803812119374241 ] - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) - data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) + data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity=true) #Compare results between finite diff methods and Jacobian method converged1, x1 = PowerFlows._solve_powerflow!(ACPowerFlow(), data, false) @test LinearAlgebra.norm(result_14 - x1, Inf) <= 1e-6 - @test solve_ac_powerflow!(ACPowerFlow(), sys; method = :newton) + @test solve_ac_powerflow!(ACPowerFlow(), sys; method=:newton) # Test enforcing the reactive power Limits set_reactive_power!(get_component(PowerLoad, sys, "Bus4"), 0.0) - data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) + data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity=true) converged2, x2 = PowerFlows._solve_powerflow!(ACPowerFlow(), data, true) @test LinearAlgebra.norm(result_14 - x2, Inf) >= 1e-6 @test 1.08 <= x2[15] <= 1.09 end @testset "AC Power Flow 14-Bus Line Configurations" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) base_res = solve_powerflow(ACPowerFlow(), sys) branch = first(PSY.get_components(Line, sys)) dyn_branch = DynamicBranch(branch) @@ -55,15 +55,15 @@ end dyn_pf = solve_powerflow(ACPowerFlow(), sys) @test LinearAlgebra.norm(dyn_pf["bus_results"].Vm - base_res["bus_results"].Vm, Inf) <= 1e-6 - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) solve_ac_powerflow!(ACPowerFlow(), sys) @test PSY.get_active_power_flow(line) == 0.0 test_bus = get_component(PSY.Bus, sys, "Bus 4") - @test isapprox(PSY.get_magnitude(test_bus), 1.002; atol = 1e-3, rtol=0) + @test isapprox(PSY.get_magnitude(test_bus), 1.002; atol=1e-3, rtol=0) - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) res = solve_powerflow(ACPowerFlow(), sys) @@ -79,12 +79,12 @@ end fix_shunt = PSY.FixedAdmittance("FixAdmBus3", true, bus_103, 0.0 + 0.2im) add_component!(sys_3bus, fix_shunt) df = solve_powerflow(ACPowerFlow(), sys_3bus) - @test isapprox(df["bus_results"].P_gen, p_gen_matpower_3bus, atol = 1e-4) - @test isapprox(df["bus_results"].Q_gen, q_gen_matpower_3bus, atol = 1e-4) + @test isapprox(df["bus_results"].P_gen, p_gen_matpower_3bus, atol=1e-4) + @test isapprox(df["bus_results"].Q_gen, q_gen_matpower_3bus, atol=1e-4) end @testset "AC Power Flow convergence fail testing" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) - pf_sys5_re = PSB.build_system(PSB.PSITestSystems, "c_sys5_re"; add_forecasts = false) + pf_sys5_re = PSB.build_system(PSB.PSITestSystems, "c_sys5_re"; add_forecasts=false) remove_component!(Line, pf_sys5_re, "1") remove_component!(Line, pf_sys5_re, "2") br = get_component(Line, pf_sys5_re, "6") @@ -107,8 +107,8 @@ end ) system = System( file; - bus_name_formatter = x -> strip(string(x["name"])) * "-" * string(x["index"]), - runchecks = false, + bus_name_formatter=x -> strip(string(x["name"])) * "-" * string(x["index"]), + runchecks=false, ) pf_bus_result_file = joinpath(TEST_FILES_DIR, "test_data", "pf_bus_results.csv") @@ -135,35 +135,35 @@ end @testset "AC Multiple sources at ref" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b = ACBus(; - number = 1, - name = "01", - bustype = ACBusTypes.REF, - angle = 0.0, - magnitude = 1.1, - voltage_limits = (0.0, 2.0), - base_voltage = 230, + number=1, + name="01", + bustype=ACBusTypes.REF, + angle=0.0, + magnitude=1.1, + voltage_limits=(0.0, 2.0), + base_voltage=230, ) add_component!(sys, b) #Test two sources with equal and opposite P and Q s1 = Source(; - name = "source_1", - available = true, - bus = b, - active_power = 0.5, - reactive_power = 0.1, - R_th = 1e-5, - X_th = 1e-5, + name="source_1", + available=true, + bus=b, + active_power=0.5, + reactive_power=0.1, + R_th=1e-5, + X_th=1e-5, ) add_component!(sys, s1) s2 = Source(; - name = "source_2", - available = true, - bus = b, - active_power = -0.5, - reactive_power = -0.1, - R_th = 1e-5, - X_th = 1e-5, + name="source_2", + available=true, + bus=b, + active_power=-0.5, + reactive_power=-0.1, + R_th=1e-5, + X_th=1e-5, ) add_component!(sys, s2) @test solve_ac_powerflow!(ACPowerFlow(), sys) @@ -176,70 +176,70 @@ end @testset "AC PowerFlow with Multiple sources at PV" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b1 = ACBus(; - number = 1, - name = "01", - bustype = ACBusTypes.REF, - angle = 0.0, - magnitude = 1.1, - voltage_limits = (0.0, 2.0), - base_voltage = 230, + number=1, + name="01", + bustype=ACBusTypes.REF, + angle=0.0, + magnitude=1.1, + voltage_limits=(0.0, 2.0), + base_voltage=230, ) add_component!(sys, b1) b2 = ACBus(; - number = 2, - name = "02", - bustype = ACBusTypes.PV, - angle = 0.0, - magnitude = 1.1, - voltage_limits = (0.0, 2.0), - base_voltage = 230, + number=2, + name="02", + bustype=ACBusTypes.PV, + angle=0.0, + magnitude=1.1, + voltage_limits=(0.0, 2.0), + base_voltage=230, ) add_component!(sys, b2) - a = Arc(; from = b1, to = b2) + a = Arc(; from=b1, to=b2) add_component!(sys, a) l = Line(; - name = "l1", - available = true, - active_power_flow = 0.0, - reactive_power_flow = 0.0, - arc = a, - r = 1e-3, - x = 1e-3, - b = (from = 0.0, to = 0.0), - rating = 0.0, - angle_limits = (min = -pi / 2, max = pi / 2), + name="l1", + available=true, + active_power_flow=0.0, + reactive_power_flow=0.0, + arc=a, + r=1e-3, + x=1e-3, + b=(from=0.0, to=0.0), + rating=0.0, + angle_limits=(min=-pi / 2, max=pi / 2), ) add_component!(sys, l) #Test two sources with equal and opposite P and Q s1 = Source(; - name = "source_1", - available = true, - bus = b1, - active_power = 0.5, - reactive_power = 0.1, - R_th = 1e-5, - X_th = 1e-5, + name="source_1", + available=true, + bus=b1, + active_power=0.5, + reactive_power=0.1, + R_th=1e-5, + X_th=1e-5, ) add_component!(sys, s1) s2 = Source(; - name = "source_2", - available = true, - bus = b2, - active_power = 0.5, - reactive_power = 1.1, - R_th = 1e-5, - X_th = 1e-5, + name="source_2", + available=true, + bus=b2, + active_power=0.5, + reactive_power=1.1, + R_th=1e-5, + X_th=1e-5, ) add_component!(sys, s2) s3 = Source(; - name = "source_3", - available = true, - bus = b2, - active_power = -0.5, - reactive_power = -1.1, - R_th = 1e-5, - X_th = 1e-5, + name="source_3", + available=true, + bus=b2, + active_power=-0.5, + reactive_power=-1.1, + R_th=1e-5, + X_th=1e-5, ) add_component!(sys, s3) @@ -253,46 +253,46 @@ end @testset "AC PowerFlow Source + non-source at Ref" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b = ACBus(; - number = 1, - name = "01", - bustype = ACBusTypes.REF, - angle = 0.0, - magnitude = 1.1, - voltage_limits = (0.0, 2.0), - base_voltage = 230, + number=1, + name="01", + bustype=ACBusTypes.REF, + angle=0.0, + magnitude=1.1, + voltage_limits=(0.0, 2.0), + base_voltage=230, ) add_component!(sys, b) #Test two sources with equal and opposite P and Q s1 = Source(; - name = "source_1", - available = true, - bus = b, - active_power = 0.5, - reactive_power = 0.1, - R_th = 1e-5, - X_th = 1e-5, + name="source_1", + available=true, + bus=b, + active_power=0.5, + reactive_power=0.1, + R_th=1e-5, + X_th=1e-5, ) add_component!(sys, s1) g1 = ThermalStandard(; - name = "init", - available = true, - status = false, - bus = b, - active_power = 0.1, - reactive_power = 0.1, - rating = 0.0, - active_power_limits = (min = 0.0, max = 0.0), - reactive_power_limits = nothing, - ramp_limits = nothing, - operation_cost = ThermalGenerationCost(nothing), - base_power = 100.0, - time_limits = nothing, - prime_mover_type = PrimeMovers.OT, - fuel = ThermalFuels.OTHER, - services = Device[], - dynamic_injector = nothing, - ext = Dict{String, Any}(), + name="init", + available=true, + status=false, + bus=b, + active_power=0.1, + reactive_power=0.1, + rating=0.0, + active_power_limits=(min=0.0, max=0.0), + reactive_power_limits=nothing, + ramp_limits=nothing, + operation_cost=ThermalGenerationCost(nothing), + base_power=100.0, + time_limits=nothing, + prime_mover_type=PrimeMovers.OT, + fuel=ThermalFuels.OTHER, + services=Device[], + dynamic_injector=nothing, + ext=Dict{String,Any}(), ) add_component!(sys, g1) @@ -300,93 +300,93 @@ end @test isapprox( get_active_power(get_component(Source, sys, "source_1")), 0.5; - atol = 0.001, + atol=0.001, ) @test isapprox( get_reactive_power(get_component(Source, sys, "source_1")), 0.1; - atol = 0.001, + atol=0.001, ) end @testset "AC PowerFlow Source + non-source at PV" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b1 = ACBus(; - number = 1, - name = "01", - bustype = ACBusTypes.REF, - angle = 0.0, - magnitude = 1.1, - voltage_limits = (0.0, 2.0), - base_voltage = 230, + number=1, + name="01", + bustype=ACBusTypes.REF, + angle=0.0, + magnitude=1.1, + voltage_limits=(0.0, 2.0), + base_voltage=230, ) add_component!(sys, b1) b2 = ACBus(; - number = 2, - name = "02", - bustype = ACBusTypes.PV, - angle = 0.0, - magnitude = 1.1, - voltage_limits = (0.0, 2.0), - base_voltage = 230, + number=2, + name="02", + bustype=ACBusTypes.PV, + angle=0.0, + magnitude=1.1, + voltage_limits=(0.0, 2.0), + base_voltage=230, ) add_component!(sys, b2) - a = Arc(; from = b1, to = b2) + a = Arc(; from=b1, to=b2) add_component!(sys, a) l = Line(; - name = "l1", - available = true, - active_power_flow = 0.0, - reactive_power_flow = 0.0, - arc = a, - r = 1e-3, - x = 1e-3, - b = (from = 0.0, to = 0.0), - rating = 0.0, - angle_limits = (min = -pi / 2, max = pi / 2), + name="l1", + available=true, + active_power_flow=0.0, + reactive_power_flow=0.0, + arc=a, + r=1e-3, + x=1e-3, + b=(from=0.0, to=0.0), + rating=0.0, + angle_limits=(min=-pi / 2, max=pi / 2), ) add_component!(sys, l) #Test two sources with equal and opposite P and Q s1 = Source(; - name = "source_1", - available = true, - bus = b1, - active_power = 0.5, - reactive_power = 0.1, - R_th = 1e-5, - X_th = 1e-5, + name="source_1", + available=true, + bus=b1, + active_power=0.5, + reactive_power=0.1, + R_th=1e-5, + X_th=1e-5, ) add_component!(sys, s1) s2 = Source(; - name = "source_2", - available = true, - bus = b2, - active_power = 0.5, - reactive_power = 1.1, - R_th = 1e-5, - X_th = 1e-5, + name="source_2", + available=true, + bus=b2, + active_power=0.5, + reactive_power=1.1, + R_th=1e-5, + X_th=1e-5, ) add_component!(sys, s2) g1 = ThermalStandard(; - name = "init", - available = true, - status = false, - bus = b2, - active_power = 0.1, - reactive_power = 0.1, - rating = 0.0, - active_power_limits = (min = 0.0, max = 0.0), - reactive_power_limits = nothing, - ramp_limits = nothing, - operation_cost = ThermalGenerationCost(nothing), - base_power = 100.0, - time_limits = nothing, - prime_mover_type = PrimeMovers.OT, - fuel = ThermalFuels.OTHER, - services = Device[], - dynamic_injector = nothing, - ext = Dict{String, Any}(), + name="init", + available=true, + status=false, + bus=b2, + active_power=0.1, + reactive_power=0.1, + rating=0.0, + active_power_limits=(min=0.0, max=0.0), + reactive_power_limits=nothing, + ramp_limits=nothing, + operation_cost=ThermalGenerationCost(nothing), + base_power=100.0, + time_limits=nothing, + prime_mover_type=PrimeMovers.OT, + fuel=ThermalFuels.OTHER, + services=Device[], + dynamic_injector=nothing, + ext=Dict{String,Any}(), ) add_component!(sys, g1) @@ -394,11 +394,11 @@ end @test isapprox( get_active_power(get_component(Source, sys, "source_2")), 0.5; - atol = 0.001, + atol=0.001, ) @test isapprox( get_reactive_power(get_component(Source, sys, "source_2")), 1.1; - atol = 0.001, + atol=0.001, ) end diff --git a/test/test_powerflow_data.jl b/test/test_powerflow_data.jl index 5f91cddc..8ae0c305 100644 --- a/test/test_powerflow_data.jl +++ b/test/test_powerflow_data.jl @@ -1,5 +1,5 @@ @testset "PowerFlowData" begin - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) @test PowerFlowData(NLSolveACPowerFlow(), sys) isa PF.ACPowerFlowData @test PowerFlowData(DCPowerFlow(), sys) isa PF.ABAPowerFlowData @test PowerFlowData(PTDFDCPowerFlow(), sys) isa PF.PTDFPowerFlowData @@ -7,13 +7,13 @@ end @testset "PowerFlowData multiperiod" begin - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) time_steps = 24 # TODO: "multiperiod AC still to implement" - @test PowerFlowData(DCPowerFlow(), sys; time_steps = time_steps) isa PF.ABAPowerFlowData - @test PowerFlowData(PTDFDCPowerFlow(), sys; time_steps = time_steps) isa + @test PowerFlowData(DCPowerFlow(), sys; time_steps=time_steps) isa PF.ABAPowerFlowData + @test PowerFlowData(PTDFDCPowerFlow(), sys; time_steps=time_steps) isa PF.PTDFPowerFlowData - @test PowerFlowData(vPTDFDCPowerFlow(), sys; time_steps = time_steps) isa + @test PowerFlowData(vPTDFDCPowerFlow(), sys; time_steps=time_steps) isa PF.vPTDFPowerFlowData end diff --git a/test/test_psse_export.jl b/test/test_psse_export.jl index df7d925d..3e30f111 100644 --- a/test/test_psse_export.jl +++ b/test/test_psse_export.jl @@ -1,5 +1,5 @@ test_psse_export_dir = joinpath(TEST_FILES_DIR, "test_exports") -isdir(test_psse_export_dir) && rm(test_psse_export_dir; recursive = true) +isdir(test_psse_export_dir) && rm(test_psse_export_dir; recursive=true) function _log_assert(result, msg, comparison_name) result || @@ -10,7 +10,7 @@ end If the expression is false, log an error; in any case, pass through the result of the expression. Optionally accepts a name to include in the error log. """ -macro log_assert(ex, comparison_name = nothing) +macro log_assert(ex, comparison_name=nothing) return :(_log_assert($(esc(ex)), $(string(ex)), $(esc(comparison_name)))) end @@ -24,7 +24,7 @@ function compare_df_within_tolerance( comparison_name::String, df1::DataFrame, df2::DataFrame, - default_tol = SYSTEM_REIMPORT_COMPARISON_TOLERANCE; + default_tol=SYSTEM_REIMPORT_COMPARISON_TOLERANCE; kwargs..., ) result = true @@ -38,7 +38,7 @@ function compare_df_within_tolerance( my_tol = (Symbol(colname) in keys(kwargs)) ? kwargs[Symbol(colname)] : default_tol isnothing(my_tol) && continue inner_result = if (my_eltype <: AbstractFloat) - all(isapprox.(col1, col2; atol = my_tol)) + all(isapprox.(col1, col2; atol=my_tol)) else all(IS.isequivalent.(col1, col2)) end @@ -52,7 +52,7 @@ end compare_df_within_tolerance( df1::DataFrame, df2::DataFrame, - default_tol = SYSTEM_REIMPORT_COMPARISON_TOLERANCE; + default_tol=SYSTEM_REIMPORT_COMPARISON_TOLERANCE; kwargs..., ) = compare_df_within_tolerance("unnamed", df1, df2, default_tol; kwargs...) @@ -64,13 +64,13 @@ function reverse_composite_name(name::String) end loose_system_match_fn(a::Float64, b::Float64) = - isapprox(a, b; atol = SYSTEM_REIMPORT_COMPARISON_TOLERANCE) || IS.isequivalent(a, b) + isapprox(a, b; atol=SYSTEM_REIMPORT_COMPARISON_TOLERANCE) || IS.isequivalent(a, b) loose_system_match_fn(a, b) = IS.isequivalent(a, b) function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; - bus_name_mapping = Dict{String, String}(), + bus_name_mapping=Dict{String,String}(), # TODO when possible, also include: PSY.FixedAdmittance, PSY.Arc - include_types = [ + include_types=[ PSY.ACBus, PSY.Area, PSY.Line, @@ -83,14 +83,14 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; PSY.FixedAdmittance, ], # TODO when possible, don't exclude so many fields - exclude_fields = Set([ + exclude_fields=Set([ :ext, :ramp_limits, :time_limits, :services, :angle_limits, ]), - exclude_fields_for_type = Dict( + exclude_fields_for_type=Dict( PSY.ThermalStandard => Set([ :prime_mover_type, :rating, @@ -111,16 +111,16 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; :reactive_power_flow, ]), ), - generator_comparison_fns = [ # TODO rating + generator_comparison_fns=[ # TODO rating PSY.get_name, PSY.get_bus, PSY.get_active_power, PSY.get_reactive_power, PSY.get_base_power, ], - ignore_name_order = true, - ignore_extra_of_type = Union{PSY.ThermalStandard, PSY.StaticLoad}, - exclude_reactive_power = false) + ignore_name_order=true, + ignore_extra_of_type=Union{PSY.ThermalStandard,PSY.StaticLoad}, + exclude_reactive_power=false) result = true if exclude_reactive_power push!(exclude_fields, :reactive_power) @@ -129,7 +129,7 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; end # Compare everything about the systems except the actual components - result &= IS.compare_values(sys1, sys2; exclude = [:data]) + result &= IS.compare_values(sys1, sys2; exclude=[:data]) # Compare the components by concrete type for my_type in include_types @@ -178,7 +178,7 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; loose_system_match_fn, comp1, comp2; - exclude = my_excludes, + exclude=my_excludes, ) result &= comparison if !comparison @@ -189,7 +189,7 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; end # Extra checks for other types of generators - GenSource = Union{Generator, Source} + GenSource = Union{Generator,Source} gen1_names = sort(PSY.get_name.(PSY.get_components(GenSource, sys1))) gen2_names = sort(PSY.get_name.(PSY.get_components(GenSource, sys2))) if gen1_names != gen2_names @@ -205,13 +205,13 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; ) # Skip pairs we've already compared # e.g., if they're both ThermalStandards, we've already compared them - any(Union{typeof(gen1), typeof(gen2)} .<: include_types) && continue + any(Union{typeof(gen1),typeof(gen2)} .<: include_types) && continue for comp_fn in generator_comparison_fns comparison = IS.compare_values( loose_system_match_fn, comp_fn(gen1), comp_fn(gen2); - exclude = exclude_fields, + exclude=exclude_fields, ) result &= comparison if !comparison @@ -222,7 +222,7 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; return result end -function test_power_flow(sys1::System, sys2::System; exclude_reactive_flow = false) +function test_power_flow(sys1::System, sys2::System; exclude_reactive_flow=false) result1 = solve_powerflow(NLSolveACPowerFlow(), sys1) result2 = solve_powerflow(NLSolveACPowerFlow(), sys2) reactive_power_tol = @@ -232,8 +232,8 @@ function test_power_flow(sys1::System, sys2::System; exclude_reactive_flow = fal @test compare_df_within_tolerance("flow_results", sort(result1["flow_results"], names(result1["flow_results"])[2:end]), sort(result2["flow_results"], names(result2["flow_results"])[2:end]), - POWERFLOW_COMPARISON_TOLERANCE; line_name = nothing, Q_to_from = reactive_power_tol, - Q_from_to = reactive_power_tol, Q_losses = reactive_power_tol) + POWERFLOW_COMPARISON_TOLERANCE; line_name=nothing, Q_to_from=reactive_power_tol, + Q_from_to=reactive_power_tol, Q_losses=reactive_power_tol) end function read_system_and_metadata(raw_path, metadata_path) @@ -250,8 +250,8 @@ function test_psse_round_trip( exporter::PSSEExporter, scenario_name::AbstractString, export_location::AbstractString; - do_power_flow_test = true, - exclude_reactive_flow = false, + do_power_flow_test=true, + exclude_reactive_flow=false, ) raw_path, metadata_path = get_psse_export_paths(joinpath(export_location, scenario_name)) @@ -265,7 +265,7 @@ function test_psse_round_trip( sys2, sys2_metadata = read_system_and_metadata(raw_path, metadata_path) @test compare_systems_loosely(sys, sys2) do_power_flow_test && - test_power_flow(sys, sys2; exclude_reactive_flow = exclude_reactive_flow) + test_power_flow(sys, sys2; exclude_reactive_flow=exclude_reactive_flow) end "Test that the two raw files are exactly identical and the two metadata files parse to identical JSON" @@ -274,7 +274,7 @@ function test_psse_export_strict_equality( metadata1, raw2, metadata2; - exclude_metadata_keys = ["case_name"], + exclude_metadata_keys=["case_name"], ) open(raw1, "r") do handle1 open(raw2, "r") do handle2 @@ -329,7 +329,7 @@ end export_location = joinpath(test_psse_export_dir, "v33", "system_240") exporter = PSSEExporter(sys, :v33, export_location) test_psse_round_trip(sys, exporter, "basic", export_location; - exclude_reactive_flow = true) # TODO why is reactive flow not matching? + exclude_reactive_flow=true) # TODO why is reactive flow not matching? # Exporting the exact same thing again should result in the exact same files write_export(exporter, "basic2") @@ -360,7 +360,7 @@ end @test_logs((:error, r"values do not match"), match_mode = :any, min_level = Logging.Error, compare_systems_loosely(sys, reread_sys2)) - test_power_flow(sys2, reread_sys2; exclude_reactive_flow = true) # TODO why is reactive flow not matching? + test_power_flow(sys2, reread_sys2; exclude_reactive_flow=true) # TODO why is reactive flow not matching? end @testset "PSSE Exporter with RTS_GMLC_DA_sys, v33" begin @@ -374,7 +374,7 @@ end export_location = joinpath(test_psse_export_dir, "v33", "rts_gmlc") exporter = PSSEExporter(sys, :v33, export_location) test_psse_round_trip(sys, exporter, "basic", export_location; - exclude_reactive_flow = true) # TODO why is reactive flow not matching? + exclude_reactive_flow=true) # TODO why is reactive flow not matching? # Exporting the exact same thing again should result in the exact same files write_export(exporter, "basic2") @@ -404,7 +404,7 @@ end @test_logs((:error, r"values do not match"), match_mode = :any, min_level = Logging.Error, compare_systems_loosely(sys, reread_sys2)) - test_power_flow(sys2, reread_sys2; exclude_reactive_flow = true) # TODO why is reactive flow not matching? + test_power_flow(sys2, reread_sys2; exclude_reactive_flow=true) # TODO why is reactive flow not matching? # Updating with changed value should result in a different reimport (PowerFlowData version) exporter = PSSEExporter(sys, :v33, export_location) @@ -417,16 +417,16 @@ end reread_sys3, sys3_metadata = read_system_and_metadata(joinpath(export_location, "basic5")) @test compare_systems_loosely(sys2, reread_sys3; - exclude_reactive_power = true) # TODO why is reactive power not matching? + exclude_reactive_power=true) # TODO why is reactive power not matching? @test_logs((:error, r"values do not match"), match_mode = :any, min_level = Logging.Error, compare_systems_loosely(sys, reread_sys3)) - test_power_flow(sys2, reread_sys3; exclude_reactive_flow = true) # TODO why is reactive flow not matching? + test_power_flow(sys2, reread_sys3; exclude_reactive_flow=true) # TODO why is reactive flow not matching? # Exporting with write_comments should be comparable to original system - exporter = PSSEExporter(sys, :v33, export_location; write_comments = true) + exporter = PSSEExporter(sys, :v33, export_location; write_comments=true) test_psse_round_trip(sys, exporter, "basic6", export_location; - exclude_reactive_flow = true) # TODO why is reactive flow not matching? + exclude_reactive_flow=true) # TODO why is reactive flow not matching? end @testset "Test exporter helper functions" begin From c6995565ec2b2b959d76fd9944b927b35641843d Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Fri, 6 Dec 2024 15:13:12 -0700 Subject: [PATCH 06/23] formatting --- src/PowerFlowData.jl | 58 ++--- src/nlsolve_ac_powerflow.jl | 47 ++-- src/post_processing.jl | 150 ++++++------- test/test_nlsolve_powerflow.jl | 387 +++++++++++++++++---------------- test/test_powerflow_data.jl | 10 +- test/test_psse_export.jl | 68 +++--- 6 files changed, 373 insertions(+), 347 deletions(-) diff --git a/src/PowerFlowData.jl b/src/PowerFlowData.jl index ac0f198f..00c8c4a2 100644 --- a/src/PowerFlowData.jl +++ b/src/PowerFlowData.jl @@ -66,11 +66,11 @@ flows and angles, as well as these ones. - `neighbors::Vector{Set{Int}}`: Vector with the sets of adjacent buses. """ struct PowerFlowData{ - M<:PNM.PowerNetworkMatrix, - N<:Union{PNM.PowerNetworkMatrix,Nothing}, + M <: PNM.PowerNetworkMatrix, + N <: Union{PNM.PowerNetworkMatrix, Nothing}, } <: PowerFlowContainer - bus_lookup::Dict{Int,Int} - branch_lookup::Dict{String,Int} + bus_lookup::Dict{Int, Int} + branch_lookup::Dict{String, Int} bus_activepower_injection::Matrix{Float64} bus_reactivepower_injection::Matrix{Float64} bus_activepower_withdrawals::Matrix{Float64} @@ -81,7 +81,7 @@ struct PowerFlowData{ bus_magnitude::Matrix{Float64} bus_angles::Matrix{Float64} branch_flow_values::Matrix{Float64} - timestep_map::Dict{Int,String} + timestep_map::Dict{Int, String} valid_ix::Vector{Int} power_network_matrix::M aux_network_matrix::N @@ -119,8 +119,8 @@ end # TODO -> MULTI PERIOD: AC Power Flow Data function _calculate_neighbors( Yb::PNM.Ybus{ - Tuple{Vector{Int64},Vector{Int64}}, - Tuple{Dict{Int64,Int64},Dict{Int64,Int64}}, + Tuple{Vector{Int64}, Vector{Int64}}, + Tuple{Dict{Int64, Int64}, Dict{Int64, Int64}}, }, ) I, J, V = SparseArrays.findnz(Yb.data) @@ -157,11 +157,11 @@ NOTE: use it for AC power flow computations. WARNING: functions for the evaluation of the multi-period AC PF still to be implemented. """ function PowerFlowData( - ::Union{NLSolveACPowerFlow,KLUACPowerFlow}, + ::Union{NLSolveACPowerFlow, KLUACPowerFlow}, sys::PSY.System; - time_steps::Int=1, - timestep_names::Vector{String}=String[], - check_connectivity::Bool=true) + time_steps::Int = 1, + timestep_names::Vector{String} = String[], + check_connectivity::Bool = true) # assign timestep_names # timestep names are then allocated in a dictionary to map matrix columns @@ -174,7 +174,7 @@ function PowerFlowData( end # get data for calculations - power_network_matrix = PNM.Ybus(sys; check_connectivity=check_connectivity) + power_network_matrix = PNM.Ybus(sys; check_connectivity = check_connectivity) # get number of buses and branches n_buses = length(axes(power_network_matrix, 1)) @@ -185,8 +185,8 @@ function PowerFlowData( n_branches = length(branches) bus_lookup = power_network_matrix.lookup[2] - branch_lookup = Dict{String,Int}() - temp_bus_map = Dict{Int,String}( + branch_lookup = Dict{String, Int}() + temp_bus_map = Dict{Int, String}( PSY.get_number(b) => PSY.get_name(b) for b in PSY.get_components(PSY.Bus, sys) ) branch_types = Vector{DataType}(undef, n_branches) @@ -250,9 +250,9 @@ NOTE: use it for DC power flow computations. function PowerFlowData( ::DCPowerFlow, sys::PSY.System; - time_steps::Int=1, - timestep_names::Vector{String}=String[], - check_connectivity::Bool=true) + time_steps::Int = 1, + timestep_names::Vector{String} = String[], + check_connectivity::Bool = true) # assign timestep_names # timestep names are then allocated in a dictionary to map matrix columns @@ -263,7 +263,7 @@ function PowerFlowData( end # get the network matrices - power_network_matrix = PNM.ABA_Matrix(sys; factorize=true) + power_network_matrix = PNM.ABA_Matrix(sys; factorize = true) aux_network_matrix = PNM.BA_Matrix(sys) # get number of buses and branches @@ -272,7 +272,7 @@ function PowerFlowData( bus_lookup = aux_network_matrix.lookup[1] branch_lookup = aux_network_matrix.lookup[2] - temp_bus_map = Dict{Int,String}( + temp_bus_map = Dict{Int, String}( PSY.get_number(b) => PSY.get_name(b) for b in PSY.get_components(PSY.ACBus, sys) ) valid_ix = setdiff(1:n_buses, aux_network_matrix.ref_bus_positions) @@ -317,9 +317,9 @@ NOTE: use it for DC power flow computations. function PowerFlowData( ::PTDFDCPowerFlow, sys::PSY.System; - time_steps::Int=1, - timestep_names::Vector{String}=String[], - check_connectivity::Bool=true) + time_steps::Int = 1, + timestep_names::Vector{String} = String[], + check_connectivity::Bool = true) # assign timestep_names # timestep names are then allocated in a dictionary to map matrix columns @@ -333,7 +333,7 @@ function PowerFlowData( # get the network matrices power_network_matrix = PNM.PTDF(sys) - aux_network_matrix = PNM.ABA_Matrix(sys; factorize=true) + aux_network_matrix = PNM.ABA_Matrix(sys; factorize = true) # get number of buses and branches n_buses = length(axes(power_network_matrix, 1)) @@ -341,7 +341,7 @@ function PowerFlowData( bus_lookup = power_network_matrix.lookup[1] branch_lookup = power_network_matrix.lookup[2] - temp_bus_map = Dict{Int,String}( + temp_bus_map = Dict{Int, String}( PSY.get_number(b) => PSY.get_name(b) for b in PSY.get_components(PSY.Bus, sys) ) valid_ix = setdiff(1:n_buses, aux_network_matrix.ref_bus_positions) @@ -385,9 +385,9 @@ NOTE: use it for DC power flow computations. function PowerFlowData( ::vPTDFDCPowerFlow, sys::PSY.System; - time_steps::Int=1, - timestep_names::Vector{String}=String[], - check_connectivity::Bool=true) + time_steps::Int = 1, + timestep_names::Vector{String} = String[], + check_connectivity::Bool = true) # assign timestep_names # timestep names are then allocated in a dictionary to map matrix columns @@ -401,7 +401,7 @@ function PowerFlowData( # get the network matrices power_network_matrix = PNM.VirtualPTDF(sys) # evaluates an empty virtual PTDF - aux_network_matrix = PNM.ABA_Matrix(sys; factorize=true) + aux_network_matrix = PNM.ABA_Matrix(sys; factorize = true) # get number of buses and branches n_buses = length(axes(power_network_matrix, 2)) @@ -409,7 +409,7 @@ function PowerFlowData( bus_lookup = power_network_matrix.lookup[2] branch_lookup = power_network_matrix.lookup[1] - temp_bus_map = Dict{Int,String}( + temp_bus_map = Dict{Int, String}( PSY.get_number(b) => PSY.get_name(b) for b in PSY.get_components(PSY.Bus, sys) ) valid_ix = setdiff(1:n_buses, aux_network_matrix.ref_bus_positions) diff --git a/src/nlsolve_ac_powerflow.jl b/src/nlsolve_ac_powerflow.jl index 57468aca..6e08e67a 100644 --- a/src/nlsolve_ac_powerflow.jl +++ b/src/nlsolve_ac_powerflow.jl @@ -30,16 +30,20 @@ solve_ac_powerflow!(sys) solve_ac_powerflow!(sys, method=:newton) ``` """ -function solve_ac_powerflow!(pf::Union{NLSolveACPowerFlow,KLUACPowerFlow}, system::PSY.System; kwargs...) +function solve_ac_powerflow!( + pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, + system::PSY.System; + kwargs..., +) #Save per-unit flag settings_unit_cache = deepcopy(system.units_settings.unit_system) #Work in System per unit PSY.set_units_base_system!(system, "SYSTEM_BASE") check_reactive_power_limits = get(kwargs, :check_reactive_power_limits, false) data = PowerFlowData( - NLSolveACPowerFlow(; check_reactive_power_limits=check_reactive_power_limits), + NLSolveACPowerFlow(; check_reactive_power_limits = check_reactive_power_limits), system; - check_connectivity=get(kwargs, :check_connectivity, true), + check_connectivity = get(kwargs, :check_connectivity, true), ) max_iterations = DEFAULT_MAX_REDISTRIBUTION_ITERATIONS converged, x = _solve_powerflow!(pf, data, check_reactive_power_limits; kwargs...) @@ -68,7 +72,7 @@ res = solve_powerflow(sys, method=:newton) ``` """ function solve_powerflow( - pf::Union{NLSolveACPowerFlow,KLUACPowerFlow}, + pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, system::PSY.System; kwargs..., ) @@ -79,7 +83,7 @@ function solve_powerflow( data = PowerFlowData( pf, system; - check_connectivity=get(kwargs, :check_connectivity, true), + check_connectivity = get(kwargs, :check_connectivity, true), ) converged, x = _solve_powerflow!(pf, data, pf.check_reactive_power_limits; kwargs...) @@ -101,7 +105,7 @@ function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) within_limits = true for (ix, b) in enumerate(data.bus_type) if b == PSY.ACBusTypes.PV - Q_gen = zero[2*ix-1] + Q_gen = zero[2 * ix - 1] else continue end @@ -124,7 +128,7 @@ function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) end function _solve_powerflow!( - pf::Union{NLSolveACPowerFlow,KLUACPowerFlow}, + pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, data::ACPowerFlowData, check_reactive_power_limits; nlsolve_kwargs..., @@ -145,7 +149,11 @@ function _solve_powerflow!( end end -function _nlsolve_powerflow(pf::NLSolveACPowerFlow, data::ACPowerFlowData; nlsolve_kwargs...) +function _nlsolve_powerflow( + pf::NLSolveACPowerFlow, + data::ACPowerFlowData; + nlsolve_kwargs..., +) pf = PolarPowerFlow(data) J = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) @@ -176,7 +184,9 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k Ybus = pf.data.power_network_matrix.data - Sbus = data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + 1im * (data.bus_reactivepower_injection[:] - data.bus_reactivepower_withdrawals[:]) + Sbus = + data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + + 1im * (data.bus_reactivepower_injection[:] - data.bus_reactivepower_withdrawals[:]) mis = V .* conj(Ybus * V) - Sbus F = [real(mis[[pv; pq]]); imag(mis[pq])] @@ -208,8 +218,8 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k #dx = - (J_function.Jv \ F) Va[pv] .+= dx[1:npv] - Va[pq] .+= dx[(npv+1):(npv+npq)] - Vm[pq] .+= dx[(npv+npq+1):(npv+2*npq)] + Va[pq] .+= dx[(npv + 1):(npv + npq)] + Vm[pq] .+= dx[(npv + npq + 1):(npv + 2 * npq)] V = Vm .* exp.(1im * Va) @@ -221,7 +231,6 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k converged = LinearAlgebra.norm(F, Inf) < tol end - if !converged @error("The powerflow solver with KLU did not converge after $i iterations") else @@ -230,21 +239,21 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k # mock the expected x format, where the values depend on the type of the bus: n_buses = length(data.bus_type) - x = Float64[0.0 for _ in 1:(2*n_buses)] + x = Float64[0.0 for _ in 1:(2 * n_buses)] Sbus_result = V .* conj(Ybus * V) for (ix, b) in enumerate(data.bus_type) if b == PSY.ACBusTypes.REF # When bustype == REFERENCE PSY.Bus, state variables are Active and Reactive Power Generated - x[2*ix-1] = real(Sbus_result[ix]) + data.bus_activepower_withdrawals[ix] - x[2*ix] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] + x[2 * ix - 1] = real(Sbus_result[ix]) + data.bus_activepower_withdrawals[ix] + x[2 * ix] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] elseif b == PSY.ACBusTypes.PV # When bustype == PV PSY.Bus, state variables are Reactive Power Generated and Voltage Angle - x[2*ix-1] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] - x[2*ix] = Va[ix] + x[2 * ix - 1] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] + x[2 * ix] = Va[ix] elseif b == PSY.ACBusTypes.PQ # When bustype == PQ PSY.Bus, state variables are Voltage Magnitude and Voltage Angle - x[2*ix-1] = Vm[ix] - x[2*ix] = Va[ix] + x[2 * ix - 1] = Vm[ix] + x[2 * ix] = Va[ix] end end diff --git a/src/post_processing.jl b/src/post_processing.jl index f8473b2f..8916a378 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -147,7 +147,7 @@ function _get_fixed_admittance_power( if (l.bus == b) Vm_squared = if b.bustype == PSY.ACBusTypes.PQ - result[2*ix-1]^2 + result[2 * ix - 1]^2 else PSY.get_magnitude(b)^2 end @@ -163,11 +163,11 @@ function _get_limits_for_power_distribution(gen::PSY.StaticInjection) end function _get_limits_for_power_distribution(gen::PSY.RenewableDispatch) - return (min=0.0, max=PSY.get_max_active_power(gen)) + return (min = 0.0, max = PSY.get_max_active_power(gen)) end function _get_limits_for_power_distribution(gen::PSY.Storage) - return (min=0.0, max=PSY.get_output_active_power_limits(gen).max) + return (min = 0.0, max = PSY.get_output_active_power_limits(gen).max) end function _power_redistribution_ref( @@ -188,8 +188,8 @@ function _power_redistribution_ref( elseif length(sources) > 1 && length(non_source_devices) == 0 Psources = sum(PSY.get_active_power.(sources)) Qsources = sum(PSY.get_reactive_power.(sources)) - if isapprox(Psources, P_gen; atol=0.001) && - isapprox(Qsources, Q_gen; atol=0.001) + if isapprox(Psources, P_gen; atol = 0.001) && + isapprox(Qsources, Q_gen; atol = 0.001) @warn "Only sources found at reference bus --- no redistribution of active or reactive power will take place" return else @@ -204,7 +204,7 @@ function _power_redistribution_ref( return elseif length(devices_) > 1 devices = - sort(collect(devices_); by=x -> _get_limits_for_power_distribution(x).max) + sort(collect(devices_); by = x -> _get_limits_for_power_distribution(x).max) else error("No devices in bus $(PSY.get_name(bus))") end @@ -226,14 +226,14 @@ function _power_redistribution_ref( p_residual -= p_set_point end - if !isapprox(p_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) + if !isapprox(p_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) @debug "Ref Bus voltage residual $p_residual" removed_power = sum([ g.max for g in _get_limits_for_power_distribution.(devices[units_at_limit]) ]) reallocated_p = 0.0 it = 0 - while !isapprox(p_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) + while !isapprox(p_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) if length(devices) == length(units_at_limit) + 1 @warn "all devices at the active Power Limit" break @@ -255,7 +255,7 @@ function _power_redistribution_ref( reallocated_p += p_frac end p_residual -= reallocated_p - if isapprox(p_residual, 0; atol=ISAPPROX_ZERO_TOLERANCE) + if isapprox(p_residual, 0; atol = ISAPPROX_ZERO_TOLERANCE) break end it += 1 @@ -263,7 +263,7 @@ function _power_redistribution_ref( break end end - if !isapprox(p_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) + if !isapprox(p_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) remaining_unit_index = setdiff(1:length(devices), units_at_limit) @assert length(remaining_unit_index) == 1 remaining_unit_index device = devices[remaining_unit_index[1]] @@ -298,7 +298,7 @@ function _reactive_power_redistribution_pv( @warn "Found sources and non-source devices at the same bus. Reactive power re-distribution is not well defined for this case. Source reactive power will remain unchanged and remaining reactive power will be re-distributed among non-source devices." elseif length(sources) > 1 && length(non_source_devices) == 0 Qsources = sum(PSY.get_reactive_power.(sources)) - if isapprox(Qsources, Q_gen; atol=0.001) + if isapprox(Qsources, Q_gen; atol = 0.001) @warn "Only sources found at PV bus --- no redistribution of reactive power will take place" return else @@ -311,14 +311,14 @@ function _reactive_power_redistribution_pv( PSY.set_reactive_power!(first(devices_), Q_gen) return elseif length(devices_) > 1 - devices = sort(collect(devices_); by=x -> PSY.get_max_reactive_power(x)) + devices = sort(collect(devices_); by = x -> PSY.get_max_reactive_power(x)) else error("No devices in bus $(PSY.get_name(bus))") end total_active_power = sum(PSY.get_active_power.(devices)) - if isapprox(total_active_power, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) + if isapprox(total_active_power, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) @debug "Total Active Power Output at the bus is $(total_active_power). Using Unit's Base Power" sum_basepower = sum(PSY.get_base_power.(devices)) for d in devices @@ -333,8 +333,8 @@ function _reactive_power_redistribution_pv( for (ix, d) in enumerate(devices) q_limits = PSY.get_reactive_power_limits(d) - if isapprox(q_limits.max, 0.0; atol=BOUNDS_TOLERANCE) && - isapprox(q_limits.min, 0.0; atol=BOUNDS_TOLERANCE) + if isapprox(q_limits.max, 0.0; atol = BOUNDS_TOLERANCE) && + isapprox(q_limits.min, 0.0; atol = BOUNDS_TOLERANCE) push!(units_at_limit, ix) @info "Unit $(PSY.get_name(d)) has no Q control capability. Q_max = $(q_limits.max) Q_min = $(q_limits.min)" continue @@ -361,14 +361,14 @@ function _reactive_power_redistribution_pv( PSY.set_reactive_power!(d, q_set_point) q_residual -= q_set_point - if isapprox(q_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) + if isapprox(q_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) break end end - if !isapprox(q_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) + if !isapprox(q_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) it = 0 - while !isapprox(q_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) + while !isapprox(q_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) if length(devices) == length(units_at_limit) + 1 @debug "Only one device not at the limit in Bus" break @@ -407,7 +407,7 @@ function _reactive_power_redistribution_pv( PSY.set_reactive_power!(d, q_set_point) end q_residual -= reallocated_q - if isapprox(q_residual, 0; atol=ISAPPROX_ZERO_TOLERANCE) + if isapprox(q_residual, 0; atol = ISAPPROX_ZERO_TOLERANCE) break end it += 1 @@ -419,7 +419,7 @@ function _reactive_power_redistribution_pv( end # Last attempt to allocate reactive power - if !isapprox(q_residual, 0.0; atol=ISAPPROX_ZERO_TOLERANCE) + if !isapprox(q_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE) remaining_unit_index = setdiff(1:length(devices), units_at_limit) @assert length(remaining_unit_index) == 1 remaining_unit_index device = devices[remaining_unit_index[1]] @@ -436,7 +436,7 @@ function _reactive_power_redistribution_pv( @assert isapprox( sum(PSY.get_reactive_power.(devices)), Q_gen; - atol=ISAPPROX_ZERO_TOLERANCE, + atol = ISAPPROX_ZERO_TOLERANCE, ) return @@ -451,21 +451,21 @@ function write_powerflow_solution!( max_iterations::Int, ) buses = enumerate( - sort!(collect(PSY.get_components(PSY.Bus, sys)); by=x -> PSY.get_number(x)), + sort!(collect(PSY.get_components(PSY.Bus, sys)); by = x -> PSY.get_number(x)), ) for (ix, bus) in buses if bus.bustype == PSY.ACBusTypes.REF - P_gen = result[2*ix-1] - Q_gen = result[2*ix] + P_gen = result[2 * ix - 1] + Q_gen = result[2 * ix] _power_redistribution_ref(sys, P_gen, Q_gen, bus, max_iterations) elseif bus.bustype == PSY.ACBusTypes.PV - Q_gen = result[2*ix-1] - bus.angle = result[2*ix] + Q_gen = result[2 * ix - 1] + bus.angle = result[2 * ix] _reactive_power_redistribution_pv(sys, Q_gen, bus, max_iterations) elseif bus.bustype == PSY.ACBusTypes.PQ - Vm = result[2*ix-1] - θ = result[2*ix] + Vm = result[2 * ix - 1] + θ = result[2 * ix] PSY.set_magnitude!(bus, Vm) PSY.set_angle!(bus, θ) end @@ -481,7 +481,7 @@ function _get_branches_buses(data::ABAPowerFlowData) end # returns list of branches names and buses numbers: PTDF and virtual PTDF case -function _get_branches_buses(data::Union{PTDFPowerFlowData,vPTDFPowerFlowData}) +function _get_branches_buses(data::Union{PTDFPowerFlowData, vPTDFPowerFlowData}) PNM.get_branch_ax(data.power_network_matrix) PNM.get_bus_ax(data.power_network_matrix) return PNM.get_branch_ax(data.power_network_matrix), @@ -513,28 +513,28 @@ function _allocate_results_data( end bus_df = DataFrames.DataFrame(; - bus_number=buses, - Vm=bus_magnitude, - θ=bus_angles, - P_gen=P_gen_vect, - P_load=P_load_vect, - P_net=P_gen_vect - P_load_vect, - Q_gen=Q_gen_vect, - Q_load=Q_load_vect, - Q_net=Q_gen_vect - Q_load_vect, + bus_number = buses, + Vm = bus_magnitude, + θ = bus_angles, + P_gen = P_gen_vect, + P_load = P_load_vect, + P_net = P_gen_vect - P_load_vect, + Q_gen = Q_gen_vect, + Q_load = Q_load_vect, + Q_net = Q_gen_vect - Q_load_vect, ) DataFrames.sort!(bus_df, :bus_number) branch_df = DataFrames.DataFrame(; - line_name=branches, - bus_from=from_bus, - bus_to=to_bus, - P_from_to=P_from_to_vect, - Q_from_to=Q_from_to_vect, - P_to_from=P_to_from_vect, - Q_to_from=Q_to_from_vect, - P_losses=zeros(length(branches)), - Q_losses=zeros(length(branches)), + line_name = branches, + bus_from = from_bus, + bus_to = to_bus, + P_from_to = P_from_to_vect, + Q_from_to = Q_from_to_vect, + P_to_from = P_to_from_vect, + Q_to_from = Q_to_from_vect, + P_losses = zeros(length(branches)), + Q_losses = zeros(length(branches)), ) DataFrames.sort!(branch_df, [:bus_from, :bus_to]) @@ -553,7 +553,7 @@ results. container storing the systam information. """ function write_results( - data::Union{PTDFPowerFlowData,vPTDFPowerFlowData,ABAPowerFlowData}, + data::Union{PTDFPowerFlowData, vPTDFPowerFlowData, ABAPowerFlowData}, sys::PSY.System, ) @info("Voltages are exported in pu. Powers are exported in MW/MVAr.") @@ -572,7 +572,7 @@ function write_results( to_bus[i] = PSY.get_number(PSY.get_arc(br).to) end - result_dict = Dict{Union{String,Char},Dict{String,DataFrames.DataFrame}}() + result_dict = Dict{Union{String, Char}, Dict{String, DataFrames.DataFrame}}() for i in 1:length(data.timestep_map) temp_dict = _allocate_results_data( branches, @@ -608,13 +608,13 @@ dictionary will therefore feature just one key linked to one DataFrame. vector containing the reults for one single time-period. """ function write_results( - ::Union{NLSolveACPowerFlow,KLUACPowerFlow}, + ::Union{NLSolveACPowerFlow, KLUACPowerFlow}, sys::PSY.System, data::ACPowerFlowData, result::Vector{Float64}, ) @info("Voltages are exported in pu. Powers are exported in MW/MVAr.") - buses = sort!(collect(PSY.get_components(PSY.Bus, sys)); by=x -> PSY.get_number(x)) + buses = sort!(collect(PSY.get_components(PSY.Bus, sys)); by = x -> PSY.get_number(x)) N_BUS = length(buses) bus_map = Dict(buses .=> 1:N_BUS) sys_basepower = PSY.get_base_power(sys) @@ -634,16 +634,16 @@ function write_results( if bustype == PSY.ACBusTypes.REF Vm_vect[ix] = data.bus_magnitude[ix] θ_vect[ix] = data.bus_angles[ix] - P_gen_vect[ix] = result[2*ix-1] * sys_basepower - Q_gen_vect[ix] = result[2*ix] * sys_basepower + P_gen_vect[ix] = result[2 * ix - 1] * sys_basepower + Q_gen_vect[ix] = result[2 * ix] * sys_basepower elseif bustype == PSY.ACBusTypes.PV Vm_vect[ix] = data.bus_magnitude[ix] - θ_vect[ix] = result[2*ix] + θ_vect[ix] = result[2 * ix] P_gen_vect[ix] = data.bus_activepower_injection[ix] * sys_basepower - Q_gen_vect[ix] = result[2*ix-1] * sys_basepower + Q_gen_vect[ix] = result[2 * ix - 1] * sys_basepower elseif bustype == PSY.ACBusTypes.PQ - Vm_vect[ix] = result[2*ix-1] - θ_vect[ix] = result[2*ix] + Vm_vect[ix] = result[2 * ix - 1] + θ_vect[ix] = result[2 * ix] P_gen_vect[ix] = data.bus_activepower_injection[ix] * sys_basepower Q_gen_vect[ix] = data.bus_reactivepower_injection[ix] * sys_basepower end @@ -666,27 +666,27 @@ function write_results( end bus_df = DataFrames.DataFrame(; - bus_number=PSY.get_number.(buses), - Vm=Vm_vect, - θ=θ_vect, - P_gen=P_gen_vect, - P_load=P_load_vect, - P_net=P_gen_vect - P_load_vect, - Q_gen=Q_gen_vect, - Q_load=Q_load_vect, - Q_net=Q_gen_vect - Q_load_vect, + bus_number = PSY.get_number.(buses), + Vm = Vm_vect, + θ = θ_vect, + P_gen = P_gen_vect, + P_load = P_load_vect, + P_net = P_gen_vect - P_load_vect, + Q_gen = Q_gen_vect, + Q_load = Q_load_vect, + Q_net = Q_gen_vect - Q_load_vect, ) branch_df = DataFrames.DataFrame(; - line_name=PSY.get_name.(branches), - bus_from=PSY.get_number.(PSY.get_from.(PSY.get_arc.(branches))), - bus_to=PSY.get_number.(PSY.get_to.(PSY.get_arc.(branches))), - P_from_to=P_from_to_vect, - Q_from_to=Q_from_to_vect, - P_to_from=P_to_from_vect, - Q_to_from=Q_to_from_vect, - P_losses=P_from_to_vect + P_to_from_vect, - Q_losses=Q_from_to_vect + Q_to_from_vect, + line_name = PSY.get_name.(branches), + bus_from = PSY.get_number.(PSY.get_from.(PSY.get_arc.(branches))), + bus_to = PSY.get_number.(PSY.get_to.(PSY.get_arc.(branches))), + P_from_to = P_from_to_vect, + Q_from_to = Q_from_to_vect, + P_to_from = P_to_from_vect, + Q_to_from = Q_to_from_vect, + P_losses = P_from_to_vect + P_to_from_vect, + Q_losses = Q_from_to_vect + Q_to_from_vect, ) DataFrames.sort!(branch_df, [:bus_from, :bus_to]) diff --git a/test/test_nlsolve_powerflow.jl b/test/test_nlsolve_powerflow.jl index 138b3058..9d9d404f 100644 --- a/test/test_nlsolve_powerflow.jl +++ b/test/test_nlsolve_powerflow.jl @@ -1,4 +1,5 @@ -@testset "AC Power Flow 14-Bus testing" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) +@testset "AC Power Flow 14-Bus testing" for ACPowerFlow in + (NLSolveACPowerFlow, KLUACPowerFlow) result_14 = [ 2.3255081760423684 -0.15529254415401786 @@ -30,40 +31,42 @@ -0.2803812119374241 ] - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) - data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity=true) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) + data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) #Compare results between finite diff methods and Jacobian method converged1, x1 = PowerFlows._solve_powerflow!(ACPowerFlow(), data, false) @test LinearAlgebra.norm(result_14 - x1, Inf) <= 1e-6 - @test solve_ac_powerflow!(ACPowerFlow(), sys; method=:newton) + @test solve_ac_powerflow!(ACPowerFlow(), sys; method = :newton) # Test enforcing the reactive power Limits set_reactive_power!(get_component(PowerLoad, sys, "Bus4"), 0.0) - data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity=true) + data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) converged2, x2 = PowerFlows._solve_powerflow!(ACPowerFlow(), data, true) @test LinearAlgebra.norm(result_14 - x2, Inf) >= 1e-6 @test 1.08 <= x2[15] <= 1.09 end -@testset "AC Power Flow 14-Bus Line Configurations" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) +@testset "AC Power Flow 14-Bus Line Configurations" for ACPowerFlow in + (NLSolveACPowerFlow, KLUACPowerFlow) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) base_res = solve_powerflow(ACPowerFlow(), sys) branch = first(PSY.get_components(Line, sys)) dyn_branch = DynamicBranch(branch) add_component!(sys, dyn_branch) @test dyn_pf = solve_ac_powerflow!(ACPowerFlow(), sys) dyn_pf = solve_powerflow(ACPowerFlow(), sys) - @test LinearAlgebra.norm(dyn_pf["bus_results"].Vm - base_res["bus_results"].Vm, Inf) <= 1e-6 + @test LinearAlgebra.norm(dyn_pf["bus_results"].Vm - base_res["bus_results"].Vm, Inf) <= + 1e-6 - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) solve_ac_powerflow!(ACPowerFlow(), sys) @test PSY.get_active_power_flow(line) == 0.0 test_bus = get_component(PSY.Bus, sys, "Bus 4") - @test isapprox(PSY.get_magnitude(test_bus), 1.002; atol=1e-3, rtol=0) + @test isapprox(PSY.get_magnitude(test_bus), 1.002; atol = 1e-3, rtol = 0) - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) res = solve_powerflow(ACPowerFlow(), sys) @@ -71,7 +74,10 @@ end @test res["flow_results"].P_to_from[4] == 0.0 end -@testset "AC Power Flow 3-Bus Fixed FixedAdmittance testing" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) +@testset "AC Power Flow 3-Bus Fixed FixedAdmittance testing" for ACPowerFlow in ( + NLSolveACPowerFlow, + KLUACPowerFlow, +) p_gen_matpower_3bus = [20.3512373930753, 100.0, 100.0] q_gen_matpower_3bus = [45.516916781567232, 10.453799727283879, -31.992561631394636] sys_3bus = PSB.build_system(PSB.PSYTestSystems, "psse_3bus_gen_cls_sys") @@ -79,12 +85,13 @@ end fix_shunt = PSY.FixedAdmittance("FixAdmBus3", true, bus_103, 0.0 + 0.2im) add_component!(sys_3bus, fix_shunt) df = solve_powerflow(ACPowerFlow(), sys_3bus) - @test isapprox(df["bus_results"].P_gen, p_gen_matpower_3bus, atol=1e-4) - @test isapprox(df["bus_results"].Q_gen, q_gen_matpower_3bus, atol=1e-4) + @test isapprox(df["bus_results"].P_gen, p_gen_matpower_3bus, atol = 1e-4) + @test isapprox(df["bus_results"].Q_gen, q_gen_matpower_3bus, atol = 1e-4) end -@testset "AC Power Flow convergence fail testing" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) - pf_sys5_re = PSB.build_system(PSB.PSITestSystems, "c_sys5_re"; add_forecasts=false) +@testset "AC Power Flow convergence fail testing" for ACPowerFlow in + (NLSolveACPowerFlow, KLUACPowerFlow) + pf_sys5_re = PSB.build_system(PSB.PSITestSystems, "c_sys5_re"; add_forecasts = false) remove_component!(Line, pf_sys5_re, "1") remove_component!(Line, pf_sys5_re, "2") br = get_component(Line, pf_sys5_re, "6") @@ -99,7 +106,8 @@ end ) end -@testset "AC Test 240 Case PSS/e results" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) +@testset "AC Test 240 Case PSS/e results" for ACPowerFlow in + (NLSolveACPowerFlow, KLUACPowerFlow) file = joinpath( TEST_FILES_DIR, "test_data", @@ -107,8 +115,8 @@ end ) system = System( file; - bus_name_formatter=x -> strip(string(x["name"])) * "-" * string(x["index"]), - runchecks=false, + bus_name_formatter = x -> strip(string(x["name"])) * "-" * string(x["index"]), + runchecks = false, ) pf_bus_result_file = joinpath(TEST_FILES_DIR, "test_data", "pf_bus_results.csv") @@ -132,114 +140,118 @@ end @test norm(q_diff, 2) / length(q_diff) < DIFF_L2_TOLERANCE end -@testset "AC Multiple sources at ref" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) +@testset "AC Multiple sources at ref" for ACPowerFlow in + (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b = ACBus(; - number=1, - name="01", - bustype=ACBusTypes.REF, - angle=0.0, - magnitude=1.1, - voltage_limits=(0.0, 2.0), - base_voltage=230, + number = 1, + name = "01", + bustype = ACBusTypes.REF, + angle = 0.0, + magnitude = 1.1, + voltage_limits = (0.0, 2.0), + base_voltage = 230, ) add_component!(sys, b) #Test two sources with equal and opposite P and Q s1 = Source(; - name="source_1", - available=true, - bus=b, - active_power=0.5, - reactive_power=0.1, - R_th=1e-5, - X_th=1e-5, + name = "source_1", + available = true, + bus = b, + active_power = 0.5, + reactive_power = 0.1, + R_th = 1e-5, + X_th = 1e-5, ) add_component!(sys, s1) s2 = Source(; - name="source_2", - available=true, - bus=b, - active_power=-0.5, - reactive_power=-0.1, - R_th=1e-5, - X_th=1e-5, + name = "source_2", + available = true, + bus = b, + active_power = -0.5, + reactive_power = -0.1, + R_th = 1e-5, + X_th = 1e-5, ) add_component!(sys, s2) @test solve_ac_powerflow!(ACPowerFlow(), sys) #Create power mismatch, test for error set_active_power!(get_component(Source, sys, "source_1"), -0.4) - @test_throws ErrorException("Sources do not match P and/or Q requirements for reference bus.") solve_ac_powerflow!(ACPowerFlow(), sys) + @test_throws ErrorException( + "Sources do not match P and/or Q requirements for reference bus.", + ) solve_ac_powerflow!(ACPowerFlow(), sys) end -@testset "AC PowerFlow with Multiple sources at PV" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) +@testset "AC PowerFlow with Multiple sources at PV" for ACPowerFlow in + (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b1 = ACBus(; - number=1, - name="01", - bustype=ACBusTypes.REF, - angle=0.0, - magnitude=1.1, - voltage_limits=(0.0, 2.0), - base_voltage=230, + number = 1, + name = "01", + bustype = ACBusTypes.REF, + angle = 0.0, + magnitude = 1.1, + voltage_limits = (0.0, 2.0), + base_voltage = 230, ) add_component!(sys, b1) b2 = ACBus(; - number=2, - name="02", - bustype=ACBusTypes.PV, - angle=0.0, - magnitude=1.1, - voltage_limits=(0.0, 2.0), - base_voltage=230, + number = 2, + name = "02", + bustype = ACBusTypes.PV, + angle = 0.0, + magnitude = 1.1, + voltage_limits = (0.0, 2.0), + base_voltage = 230, ) add_component!(sys, b2) - a = Arc(; from=b1, to=b2) + a = Arc(; from = b1, to = b2) add_component!(sys, a) l = Line(; - name="l1", - available=true, - active_power_flow=0.0, - reactive_power_flow=0.0, - arc=a, - r=1e-3, - x=1e-3, - b=(from=0.0, to=0.0), - rating=0.0, - angle_limits=(min=-pi / 2, max=pi / 2), + name = "l1", + available = true, + active_power_flow = 0.0, + reactive_power_flow = 0.0, + arc = a, + r = 1e-3, + x = 1e-3, + b = (from = 0.0, to = 0.0), + rating = 0.0, + angle_limits = (min = -pi / 2, max = pi / 2), ) add_component!(sys, l) #Test two sources with equal and opposite P and Q s1 = Source(; - name="source_1", - available=true, - bus=b1, - active_power=0.5, - reactive_power=0.1, - R_th=1e-5, - X_th=1e-5, + name = "source_1", + available = true, + bus = b1, + active_power = 0.5, + reactive_power = 0.1, + R_th = 1e-5, + X_th = 1e-5, ) add_component!(sys, s1) s2 = Source(; - name="source_2", - available=true, - bus=b2, - active_power=0.5, - reactive_power=1.1, - R_th=1e-5, - X_th=1e-5, + name = "source_2", + available = true, + bus = b2, + active_power = 0.5, + reactive_power = 1.1, + R_th = 1e-5, + X_th = 1e-5, ) add_component!(sys, s2) s3 = Source(; - name="source_3", - available=true, - bus=b2, - active_power=-0.5, - reactive_power=-1.1, - R_th=1e-5, - X_th=1e-5, + name = "source_3", + available = true, + bus = b2, + active_power = -0.5, + reactive_power = -1.1, + R_th = 1e-5, + X_th = 1e-5, ) add_component!(sys, s3) @@ -247,52 +259,56 @@ end #Create power mismatch, test for error set_reactive_power!(get_component(Source, sys, "source_3"), -0.5) - @test_throws ErrorException("Sources do not match Q requirements for PV bus.") solve_ac_powerflow!(ACPowerFlow(), sys) + @test_throws ErrorException("Sources do not match Q requirements for PV bus.") solve_ac_powerflow!( + ACPowerFlow(), + sys, + ) end -@testset "AC PowerFlow Source + non-source at Ref" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) +@testset "AC PowerFlow Source + non-source at Ref" for ACPowerFlow in + (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b = ACBus(; - number=1, - name="01", - bustype=ACBusTypes.REF, - angle=0.0, - magnitude=1.1, - voltage_limits=(0.0, 2.0), - base_voltage=230, + number = 1, + name = "01", + bustype = ACBusTypes.REF, + angle = 0.0, + magnitude = 1.1, + voltage_limits = (0.0, 2.0), + base_voltage = 230, ) add_component!(sys, b) #Test two sources with equal and opposite P and Q s1 = Source(; - name="source_1", - available=true, - bus=b, - active_power=0.5, - reactive_power=0.1, - R_th=1e-5, - X_th=1e-5, + name = "source_1", + available = true, + bus = b, + active_power = 0.5, + reactive_power = 0.1, + R_th = 1e-5, + X_th = 1e-5, ) add_component!(sys, s1) g1 = ThermalStandard(; - name="init", - available=true, - status=false, - bus=b, - active_power=0.1, - reactive_power=0.1, - rating=0.0, - active_power_limits=(min=0.0, max=0.0), - reactive_power_limits=nothing, - ramp_limits=nothing, - operation_cost=ThermalGenerationCost(nothing), - base_power=100.0, - time_limits=nothing, - prime_mover_type=PrimeMovers.OT, - fuel=ThermalFuels.OTHER, - services=Device[], - dynamic_injector=nothing, - ext=Dict{String,Any}(), + name = "init", + available = true, + status = false, + bus = b, + active_power = 0.1, + reactive_power = 0.1, + rating = 0.0, + active_power_limits = (min = 0.0, max = 0.0), + reactive_power_limits = nothing, + ramp_limits = nothing, + operation_cost = ThermalGenerationCost(nothing), + base_power = 100.0, + time_limits = nothing, + prime_mover_type = PrimeMovers.OT, + fuel = ThermalFuels.OTHER, + services = Device[], + dynamic_injector = nothing, + ext = Dict{String, Any}(), ) add_component!(sys, g1) @@ -300,93 +316,94 @@ end @test isapprox( get_active_power(get_component(Source, sys, "source_1")), 0.5; - atol=0.001, + atol = 0.001, ) @test isapprox( get_reactive_power(get_component(Source, sys, "source_1")), 0.1; - atol=0.001, + atol = 0.001, ) end -@testset "AC PowerFlow Source + non-source at PV" for ACPowerFlow in (NLSolveACPowerFlow, KLUACPowerFlow) +@testset "AC PowerFlow Source + non-source at PV" for ACPowerFlow in + (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b1 = ACBus(; - number=1, - name="01", - bustype=ACBusTypes.REF, - angle=0.0, - magnitude=1.1, - voltage_limits=(0.0, 2.0), - base_voltage=230, + number = 1, + name = "01", + bustype = ACBusTypes.REF, + angle = 0.0, + magnitude = 1.1, + voltage_limits = (0.0, 2.0), + base_voltage = 230, ) add_component!(sys, b1) b2 = ACBus(; - number=2, - name="02", - bustype=ACBusTypes.PV, - angle=0.0, - magnitude=1.1, - voltage_limits=(0.0, 2.0), - base_voltage=230, + number = 2, + name = "02", + bustype = ACBusTypes.PV, + angle = 0.0, + magnitude = 1.1, + voltage_limits = (0.0, 2.0), + base_voltage = 230, ) add_component!(sys, b2) - a = Arc(; from=b1, to=b2) + a = Arc(; from = b1, to = b2) add_component!(sys, a) l = Line(; - name="l1", - available=true, - active_power_flow=0.0, - reactive_power_flow=0.0, - arc=a, - r=1e-3, - x=1e-3, - b=(from=0.0, to=0.0), - rating=0.0, - angle_limits=(min=-pi / 2, max=pi / 2), + name = "l1", + available = true, + active_power_flow = 0.0, + reactive_power_flow = 0.0, + arc = a, + r = 1e-3, + x = 1e-3, + b = (from = 0.0, to = 0.0), + rating = 0.0, + angle_limits = (min = -pi / 2, max = pi / 2), ) add_component!(sys, l) #Test two sources with equal and opposite P and Q s1 = Source(; - name="source_1", - available=true, - bus=b1, - active_power=0.5, - reactive_power=0.1, - R_th=1e-5, - X_th=1e-5, + name = "source_1", + available = true, + bus = b1, + active_power = 0.5, + reactive_power = 0.1, + R_th = 1e-5, + X_th = 1e-5, ) add_component!(sys, s1) s2 = Source(; - name="source_2", - available=true, - bus=b2, - active_power=0.5, - reactive_power=1.1, - R_th=1e-5, - X_th=1e-5, + name = "source_2", + available = true, + bus = b2, + active_power = 0.5, + reactive_power = 1.1, + R_th = 1e-5, + X_th = 1e-5, ) add_component!(sys, s2) g1 = ThermalStandard(; - name="init", - available=true, - status=false, - bus=b2, - active_power=0.1, - reactive_power=0.1, - rating=0.0, - active_power_limits=(min=0.0, max=0.0), - reactive_power_limits=nothing, - ramp_limits=nothing, - operation_cost=ThermalGenerationCost(nothing), - base_power=100.0, - time_limits=nothing, - prime_mover_type=PrimeMovers.OT, - fuel=ThermalFuels.OTHER, - services=Device[], - dynamic_injector=nothing, - ext=Dict{String,Any}(), + name = "init", + available = true, + status = false, + bus = b2, + active_power = 0.1, + reactive_power = 0.1, + rating = 0.0, + active_power_limits = (min = 0.0, max = 0.0), + reactive_power_limits = nothing, + ramp_limits = nothing, + operation_cost = ThermalGenerationCost(nothing), + base_power = 100.0, + time_limits = nothing, + prime_mover_type = PrimeMovers.OT, + fuel = ThermalFuels.OTHER, + services = Device[], + dynamic_injector = nothing, + ext = Dict{String, Any}(), ) add_component!(sys, g1) @@ -394,11 +411,11 @@ end @test isapprox( get_active_power(get_component(Source, sys, "source_2")), 0.5; - atol=0.001, + atol = 0.001, ) @test isapprox( get_reactive_power(get_component(Source, sys, "source_2")), 1.1; - atol=0.001, + atol = 0.001, ) end diff --git a/test/test_powerflow_data.jl b/test/test_powerflow_data.jl index 8ae0c305..5f91cddc 100644 --- a/test/test_powerflow_data.jl +++ b/test/test_powerflow_data.jl @@ -1,5 +1,5 @@ @testset "PowerFlowData" begin - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) @test PowerFlowData(NLSolveACPowerFlow(), sys) isa PF.ACPowerFlowData @test PowerFlowData(DCPowerFlow(), sys) isa PF.ABAPowerFlowData @test PowerFlowData(PTDFDCPowerFlow(), sys) isa PF.PTDFPowerFlowData @@ -7,13 +7,13 @@ end @testset "PowerFlowData multiperiod" begin - sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts=false) + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) time_steps = 24 # TODO: "multiperiod AC still to implement" - @test PowerFlowData(DCPowerFlow(), sys; time_steps=time_steps) isa PF.ABAPowerFlowData - @test PowerFlowData(PTDFDCPowerFlow(), sys; time_steps=time_steps) isa + @test PowerFlowData(DCPowerFlow(), sys; time_steps = time_steps) isa PF.ABAPowerFlowData + @test PowerFlowData(PTDFDCPowerFlow(), sys; time_steps = time_steps) isa PF.PTDFPowerFlowData - @test PowerFlowData(vPTDFDCPowerFlow(), sys; time_steps=time_steps) isa + @test PowerFlowData(vPTDFDCPowerFlow(), sys; time_steps = time_steps) isa PF.vPTDFPowerFlowData end diff --git a/test/test_psse_export.jl b/test/test_psse_export.jl index 3e30f111..df7d925d 100644 --- a/test/test_psse_export.jl +++ b/test/test_psse_export.jl @@ -1,5 +1,5 @@ test_psse_export_dir = joinpath(TEST_FILES_DIR, "test_exports") -isdir(test_psse_export_dir) && rm(test_psse_export_dir; recursive=true) +isdir(test_psse_export_dir) && rm(test_psse_export_dir; recursive = true) function _log_assert(result, msg, comparison_name) result || @@ -10,7 +10,7 @@ end If the expression is false, log an error; in any case, pass through the result of the expression. Optionally accepts a name to include in the error log. """ -macro log_assert(ex, comparison_name=nothing) +macro log_assert(ex, comparison_name = nothing) return :(_log_assert($(esc(ex)), $(string(ex)), $(esc(comparison_name)))) end @@ -24,7 +24,7 @@ function compare_df_within_tolerance( comparison_name::String, df1::DataFrame, df2::DataFrame, - default_tol=SYSTEM_REIMPORT_COMPARISON_TOLERANCE; + default_tol = SYSTEM_REIMPORT_COMPARISON_TOLERANCE; kwargs..., ) result = true @@ -38,7 +38,7 @@ function compare_df_within_tolerance( my_tol = (Symbol(colname) in keys(kwargs)) ? kwargs[Symbol(colname)] : default_tol isnothing(my_tol) && continue inner_result = if (my_eltype <: AbstractFloat) - all(isapprox.(col1, col2; atol=my_tol)) + all(isapprox.(col1, col2; atol = my_tol)) else all(IS.isequivalent.(col1, col2)) end @@ -52,7 +52,7 @@ end compare_df_within_tolerance( df1::DataFrame, df2::DataFrame, - default_tol=SYSTEM_REIMPORT_COMPARISON_TOLERANCE; + default_tol = SYSTEM_REIMPORT_COMPARISON_TOLERANCE; kwargs..., ) = compare_df_within_tolerance("unnamed", df1, df2, default_tol; kwargs...) @@ -64,13 +64,13 @@ function reverse_composite_name(name::String) end loose_system_match_fn(a::Float64, b::Float64) = - isapprox(a, b; atol=SYSTEM_REIMPORT_COMPARISON_TOLERANCE) || IS.isequivalent(a, b) + isapprox(a, b; atol = SYSTEM_REIMPORT_COMPARISON_TOLERANCE) || IS.isequivalent(a, b) loose_system_match_fn(a, b) = IS.isequivalent(a, b) function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; - bus_name_mapping=Dict{String,String}(), + bus_name_mapping = Dict{String, String}(), # TODO when possible, also include: PSY.FixedAdmittance, PSY.Arc - include_types=[ + include_types = [ PSY.ACBus, PSY.Area, PSY.Line, @@ -83,14 +83,14 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; PSY.FixedAdmittance, ], # TODO when possible, don't exclude so many fields - exclude_fields=Set([ + exclude_fields = Set([ :ext, :ramp_limits, :time_limits, :services, :angle_limits, ]), - exclude_fields_for_type=Dict( + exclude_fields_for_type = Dict( PSY.ThermalStandard => Set([ :prime_mover_type, :rating, @@ -111,16 +111,16 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; :reactive_power_flow, ]), ), - generator_comparison_fns=[ # TODO rating + generator_comparison_fns = [ # TODO rating PSY.get_name, PSY.get_bus, PSY.get_active_power, PSY.get_reactive_power, PSY.get_base_power, ], - ignore_name_order=true, - ignore_extra_of_type=Union{PSY.ThermalStandard,PSY.StaticLoad}, - exclude_reactive_power=false) + ignore_name_order = true, + ignore_extra_of_type = Union{PSY.ThermalStandard, PSY.StaticLoad}, + exclude_reactive_power = false) result = true if exclude_reactive_power push!(exclude_fields, :reactive_power) @@ -129,7 +129,7 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; end # Compare everything about the systems except the actual components - result &= IS.compare_values(sys1, sys2; exclude=[:data]) + result &= IS.compare_values(sys1, sys2; exclude = [:data]) # Compare the components by concrete type for my_type in include_types @@ -178,7 +178,7 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; loose_system_match_fn, comp1, comp2; - exclude=my_excludes, + exclude = my_excludes, ) result &= comparison if !comparison @@ -189,7 +189,7 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; end # Extra checks for other types of generators - GenSource = Union{Generator,Source} + GenSource = Union{Generator, Source} gen1_names = sort(PSY.get_name.(PSY.get_components(GenSource, sys1))) gen2_names = sort(PSY.get_name.(PSY.get_components(GenSource, sys2))) if gen1_names != gen2_names @@ -205,13 +205,13 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; ) # Skip pairs we've already compared # e.g., if they're both ThermalStandards, we've already compared them - any(Union{typeof(gen1),typeof(gen2)} .<: include_types) && continue + any(Union{typeof(gen1), typeof(gen2)} .<: include_types) && continue for comp_fn in generator_comparison_fns comparison = IS.compare_values( loose_system_match_fn, comp_fn(gen1), comp_fn(gen2); - exclude=exclude_fields, + exclude = exclude_fields, ) result &= comparison if !comparison @@ -222,7 +222,7 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; return result end -function test_power_flow(sys1::System, sys2::System; exclude_reactive_flow=false) +function test_power_flow(sys1::System, sys2::System; exclude_reactive_flow = false) result1 = solve_powerflow(NLSolveACPowerFlow(), sys1) result2 = solve_powerflow(NLSolveACPowerFlow(), sys2) reactive_power_tol = @@ -232,8 +232,8 @@ function test_power_flow(sys1::System, sys2::System; exclude_reactive_flow=false @test compare_df_within_tolerance("flow_results", sort(result1["flow_results"], names(result1["flow_results"])[2:end]), sort(result2["flow_results"], names(result2["flow_results"])[2:end]), - POWERFLOW_COMPARISON_TOLERANCE; line_name=nothing, Q_to_from=reactive_power_tol, - Q_from_to=reactive_power_tol, Q_losses=reactive_power_tol) + POWERFLOW_COMPARISON_TOLERANCE; line_name = nothing, Q_to_from = reactive_power_tol, + Q_from_to = reactive_power_tol, Q_losses = reactive_power_tol) end function read_system_and_metadata(raw_path, metadata_path) @@ -250,8 +250,8 @@ function test_psse_round_trip( exporter::PSSEExporter, scenario_name::AbstractString, export_location::AbstractString; - do_power_flow_test=true, - exclude_reactive_flow=false, + do_power_flow_test = true, + exclude_reactive_flow = false, ) raw_path, metadata_path = get_psse_export_paths(joinpath(export_location, scenario_name)) @@ -265,7 +265,7 @@ function test_psse_round_trip( sys2, sys2_metadata = read_system_and_metadata(raw_path, metadata_path) @test compare_systems_loosely(sys, sys2) do_power_flow_test && - test_power_flow(sys, sys2; exclude_reactive_flow=exclude_reactive_flow) + test_power_flow(sys, sys2; exclude_reactive_flow = exclude_reactive_flow) end "Test that the two raw files are exactly identical and the two metadata files parse to identical JSON" @@ -274,7 +274,7 @@ function test_psse_export_strict_equality( metadata1, raw2, metadata2; - exclude_metadata_keys=["case_name"], + exclude_metadata_keys = ["case_name"], ) open(raw1, "r") do handle1 open(raw2, "r") do handle2 @@ -329,7 +329,7 @@ end export_location = joinpath(test_psse_export_dir, "v33", "system_240") exporter = PSSEExporter(sys, :v33, export_location) test_psse_round_trip(sys, exporter, "basic", export_location; - exclude_reactive_flow=true) # TODO why is reactive flow not matching? + exclude_reactive_flow = true) # TODO why is reactive flow not matching? # Exporting the exact same thing again should result in the exact same files write_export(exporter, "basic2") @@ -360,7 +360,7 @@ end @test_logs((:error, r"values do not match"), match_mode = :any, min_level = Logging.Error, compare_systems_loosely(sys, reread_sys2)) - test_power_flow(sys2, reread_sys2; exclude_reactive_flow=true) # TODO why is reactive flow not matching? + test_power_flow(sys2, reread_sys2; exclude_reactive_flow = true) # TODO why is reactive flow not matching? end @testset "PSSE Exporter with RTS_GMLC_DA_sys, v33" begin @@ -374,7 +374,7 @@ end export_location = joinpath(test_psse_export_dir, "v33", "rts_gmlc") exporter = PSSEExporter(sys, :v33, export_location) test_psse_round_trip(sys, exporter, "basic", export_location; - exclude_reactive_flow=true) # TODO why is reactive flow not matching? + exclude_reactive_flow = true) # TODO why is reactive flow not matching? # Exporting the exact same thing again should result in the exact same files write_export(exporter, "basic2") @@ -404,7 +404,7 @@ end @test_logs((:error, r"values do not match"), match_mode = :any, min_level = Logging.Error, compare_systems_loosely(sys, reread_sys2)) - test_power_flow(sys2, reread_sys2; exclude_reactive_flow=true) # TODO why is reactive flow not matching? + test_power_flow(sys2, reread_sys2; exclude_reactive_flow = true) # TODO why is reactive flow not matching? # Updating with changed value should result in a different reimport (PowerFlowData version) exporter = PSSEExporter(sys, :v33, export_location) @@ -417,16 +417,16 @@ end reread_sys3, sys3_metadata = read_system_and_metadata(joinpath(export_location, "basic5")) @test compare_systems_loosely(sys2, reread_sys3; - exclude_reactive_power=true) # TODO why is reactive power not matching? + exclude_reactive_power = true) # TODO why is reactive power not matching? @test_logs((:error, r"values do not match"), match_mode = :any, min_level = Logging.Error, compare_systems_loosely(sys, reread_sys3)) - test_power_flow(sys2, reread_sys3; exclude_reactive_flow=true) # TODO why is reactive flow not matching? + test_power_flow(sys2, reread_sys3; exclude_reactive_flow = true) # TODO why is reactive flow not matching? # Exporting with write_comments should be comparable to original system - exporter = PSSEExporter(sys, :v33, export_location; write_comments=true) + exporter = PSSEExporter(sys, :v33, export_location; write_comments = true) test_psse_round_trip(sys, exporter, "basic6", export_location; - exclude_reactive_flow=true) # TODO why is reactive flow not matching? + exclude_reactive_flow = true) # TODO why is reactive flow not matching? end @testset "Test exporter helper functions" begin From b0bc56522f9254cfcd9166c76002d13a0e9399a4 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Mon, 16 Dec 2024 14:45:09 -0700 Subject: [PATCH 07/23] Addressed review comments except for using functor --- Project.toml | 2 +- src/PowerFlowData.jl | 4 +- src/PowerFlows.jl | 4 +- src/definitions.jl | 3 + ...ac_powerflow.jl => newton_ac_powerflow.jl} | 45 ++++++----- src/post_processing.jl | 2 +- src/powerflow_types.jl | 12 ++- ...werflow.jl => test_newton_ac_powerflow.jl} | 74 +++++++++++-------- test/test_powerflow_data.jl | 15 ++-- test/test_psse_export.jl | 33 +++++---- 10 files changed, 111 insertions(+), 83 deletions(-) rename src/{nlsolve_ac_powerflow.jl => newton_ac_powerflow.jl} (89%) rename test/{test_nlsolve_powerflow.jl => test_newton_ac_powerflow.jl} (87%) diff --git a/Project.toml b/Project.toml index b339e3f3..708b8ecb 100644 --- a/Project.toml +++ b/Project.toml @@ -23,7 +23,7 @@ DataStructures = "0.18" Dates = "1" InfrastructureSystems = "2" JSON3 = "1" -KLU = "0.6.0" +KLU = "^0.6" LinearAlgebra = "1" Logging = "1" NLsolve = "4" diff --git a/src/PowerFlowData.jl b/src/PowerFlowData.jl index 00c8c4a2..2fdfae9c 100644 --- a/src/PowerFlowData.jl +++ b/src/PowerFlowData.jl @@ -157,7 +157,7 @@ NOTE: use it for AC power flow computations. WARNING: functions for the evaluation of the multi-period AC PF still to be implemented. """ function PowerFlowData( - ::Union{NLSolveACPowerFlow, KLUACPowerFlow}, + ::ACPowerFlow{<: ACPowerFlowSolverType}, sys::PSY.System; time_steps::Int = 1, timestep_names::Vector{String} = String[], @@ -440,7 +440,7 @@ Create an appropriate `PowerFlowContainer` for the given `PowerFlowEvaluationMod """ function make_power_flow_container end -make_power_flow_container(pfem::NLSolveACPowerFlow, sys::PSY.System; kwargs...) = +make_power_flow_container(pfem::ACPowerFlow{<: ACPowerFlowSolverType}, sys::PSY.System; kwargs...) = PowerFlowData(pfem, sys; kwargs...) make_power_flow_container(pfem::DCPowerFlow, sys::PSY.System; kwargs...) = diff --git a/src/PowerFlows.jl b/src/PowerFlows.jl index 499ac42c..e63802b2 100644 --- a/src/PowerFlows.jl +++ b/src/PowerFlows.jl @@ -6,6 +6,8 @@ export PowerFlowData export DCPowerFlow export NLSolveACPowerFlow export KLUACPowerFlow +export ACPowerFlow +export ACPowerFlowSolverType export PTDFDCPowerFlow export vPTDFDCPowerFlow export PSSEExportPowerFlow @@ -42,6 +44,6 @@ include("psse_export.jl") include("solve_dc_powerflow.jl") include("ac_power_flow.jl") include("ac_power_flow_jacobian.jl") -include("nlsolve_ac_powerflow.jl") +include("newton_ac_powerflow.jl") include("post_processing.jl") end diff --git a/src/definitions.jl b/src/definitions.jl index d8ecad1a..36e53b60 100644 --- a/src/definitions.jl +++ b/src/definitions.jl @@ -10,4 +10,7 @@ const DEFAULT_MAX_REDISTRIBUTION_ITERATIONS = 10 const ISAPPROX_ZERO_TOLERANCE = 1e-6 +const DEFAULT_NR_MAX_ITER::Int64 = 30 # default maxIter for the NR power flow +const DEFAULT_NR_TOL::Float64 = 1e-9 # default tolerance for the NR power flow + const AC_PF_KW = [] diff --git a/src/nlsolve_ac_powerflow.jl b/src/newton_ac_powerflow.jl similarity index 89% rename from src/nlsolve_ac_powerflow.jl rename to src/newton_ac_powerflow.jl index 6e08e67a..a909edb5 100644 --- a/src/nlsolve_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -31,7 +31,7 @@ solve_ac_powerflow!(sys, method=:newton) ``` """ function solve_ac_powerflow!( - pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, + pf::ACPowerFlow{<: ACPowerFlowSolverType}, system::PSY.System; kwargs..., ) @@ -41,7 +41,7 @@ function solve_ac_powerflow!( PSY.set_units_base_system!(system, "SYSTEM_BASE") check_reactive_power_limits = get(kwargs, :check_reactive_power_limits, false) data = PowerFlowData( - NLSolveACPowerFlow(; check_reactive_power_limits = check_reactive_power_limits), + pf, system; check_connectivity = get(kwargs, :check_connectivity, true), ) @@ -72,7 +72,7 @@ res = solve_powerflow(sys, method=:newton) ``` """ function solve_powerflow( - pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, + pf::ACPowerFlow{<: ACPowerFlowSolverType}, system::PSY.System; kwargs..., ) @@ -128,14 +128,14 @@ function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) end function _solve_powerflow!( - pf::Union{NLSolveACPowerFlow, KLUACPowerFlow}, + pf::ACPowerFlow{<: ACPowerFlowSolverType}, data::ACPowerFlowData, check_reactive_power_limits; nlsolve_kwargs..., ) if check_reactive_power_limits for _ in 1:MAX_REACTIVE_POWER_ITERATIONS - converged, x = _nlsolve_powerflow(pf, data; nlsolve_kwargs...) + converged, x = _newton_powerflow(pf, data; nlsolve_kwargs...) if converged if _check_q_limit_bounds!(data, x) return converged, x @@ -145,12 +145,12 @@ function _solve_powerflow!( end end else - return _nlsolve_powerflow(pf, data; nlsolve_kwargs...) + return _newton_powerflow(pf, data; nlsolve_kwargs...) end end -function _nlsolve_powerflow( - pf::NLSolveACPowerFlow, +function _newton_powerflow( + pf::ACPowerFlow{NLSolveACPowerFlow}, data::ACPowerFlowData; nlsolve_kwargs..., ) @@ -160,29 +160,31 @@ function _nlsolve_powerflow( df = NLsolve.OnceDifferentiable(pf, J, pf.x0, pf.residual, J.Jv) res = NLsolve.nlsolve(df, pf.x0; nlsolve_kwargs...) if !res.f_converged - @error("The powerflow solver returned convergence = $(res.f_converged)") + @error("The powerflow solver NLSolve did not converge (returned convergence = $(res.f_converged))") end return res.f_converged, res.zero end -function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_kwargs...) - pf = PolarPowerFlow(data) - #J_function = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) - - maxIter = 30 # TODO - tol = 1e-6 # TODO +function _newton_powerflow(pf::ACPowerFlow{KLUACPowerFlow}, data::ACPowerFlowData; nlsolve_kwargs...) + # Fetch maxIter and tol from kwargs, or use defaults if not provided + maxIter = get(nlsolve_kwargs, :maxIter, DEFAULT_NR_MAX_ITER) + tol = get(nlsolve_kwargs, :tol, DEFAULT_NR_TOL) i = 0 - Vm = data.bus_magnitude[:] - Va = data.bus_angles[:] - V = Vm .* exp.(1im * Va) + pf = PolarPowerFlow(data) # Find indices for each bus type ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) - Ybus = pf.data.power_network_matrix.data + Vm = data.bus_magnitude[:] + # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: + Vm[pq] = clamp.(Vm[pq], 0.9, 1.1) + Va = data.bus_angles[:] + V = Vm .* exp.(1im * Va) + + Ybus = data.power_network_matrix.data Sbus = data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + @@ -214,9 +216,6 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k factor_J = KLU.klu(J) dx = -(factor_J \ F) - #J_function(J_function.Jv, x) - #dx = - (J_function.Jv \ F) - Va[pv] .+= dx[1:npv] Va[pq] .+= dx[(npv + 1):(npv + npq)] Vm[pq] .+= dx[(npv + npq + 1):(npv + 2 * npq)] @@ -239,7 +238,7 @@ function _nlsolve_powerflow(pf::KLUACPowerFlow, data::ACPowerFlowData; nlsolve_k # mock the expected x format, where the values depend on the type of the bus: n_buses = length(data.bus_type) - x = Float64[0.0 for _ in 1:(2 * n_buses)] + x = zeros(Float64, 2 * n_buses) Sbus_result = V .* conj(Ybus * V) for (ix, b) in enumerate(data.bus_type) if b == PSY.ACBusTypes.REF diff --git a/src/post_processing.jl b/src/post_processing.jl index 8916a378..8ae1d687 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -608,7 +608,7 @@ dictionary will therefore feature just one key linked to one DataFrame. vector containing the reults for one single time-period. """ function write_results( - ::Union{NLSolveACPowerFlow, KLUACPowerFlow}, + ::ACPowerFlow{<: ACPowerFlowSolverType}, sys::PSY.System, data::ACPowerFlowData, result::Vector{Float64}, diff --git a/src/powerflow_types.jl b/src/powerflow_types.jl index 65b6b241..cc0c5c20 100644 --- a/src/powerflow_types.jl +++ b/src/powerflow_types.jl @@ -1,11 +1,17 @@ abstract type PowerFlowEvaluationModel end +abstract type ACPowerFlowSolverType end -Base.@kwdef struct NLSolveACPowerFlow <: PowerFlowEvaluationModel + +struct KLUACPowerFlow <: ACPowerFlowSolverType end +struct NLSolveACPowerFlow <: ACPowerFlowSolverType end + +Base.@kwdef struct ACPowerFlow{ACSolver <: ACPowerFlowSolverType} <: PowerFlowEvaluationModel check_reactive_power_limits::Bool = false end -Base.@kwdef struct KLUACPowerFlow <: PowerFlowEvaluationModel - check_reactive_power_limits::Bool = false +# Create a constructor that defaults to KLUACPowerFlow +function ACPowerFlow(; check_reactive_power_limits::Bool = false, ACSolver::Type{<:ACPowerFlowSolverType} = KLUACPowerFlow) + return ACPowerFlow{ACSolver}(check_reactive_power_limits) end struct DCPowerFlow <: PowerFlowEvaluationModel end diff --git a/test/test_nlsolve_powerflow.jl b/test/test_newton_ac_powerflow.jl similarity index 87% rename from test/test_nlsolve_powerflow.jl rename to test/test_newton_ac_powerflow.jl index 9d9d404f..7322b074 100644 --- a/test/test_nlsolve_powerflow.jl +++ b/test/test_newton_ac_powerflow.jl @@ -1,4 +1,4 @@ -@testset "AC Power Flow 14-Bus testing" for ACPowerFlow in +@testset "AC Power Flow 14-Bus testing" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) result_14 = [ 2.3255081760423684 @@ -32,36 +32,38 @@ ] sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) - data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) + pf = ACPowerFlow{ACSolver}() + data = PowerFlows.PowerFlowData(pf, sys; check_connectivity = true) #Compare results between finite diff methods and Jacobian method - converged1, x1 = PowerFlows._solve_powerflow!(ACPowerFlow(), data, false) + converged1, x1 = PowerFlows._solve_powerflow!(pf, data, false) @test LinearAlgebra.norm(result_14 - x1, Inf) <= 1e-6 - @test solve_ac_powerflow!(ACPowerFlow(), sys; method = :newton) + @test solve_ac_powerflow!(pf, sys; method = :newton) # Test enforcing the reactive power Limits set_reactive_power!(get_component(PowerLoad, sys, "Bus4"), 0.0) - data = PowerFlows.PowerFlowData(ACPowerFlow(), sys; check_connectivity = true) - converged2, x2 = PowerFlows._solve_powerflow!(ACPowerFlow(), data, true) + data = PowerFlows.PowerFlowData(pf, sys; check_connectivity = true) + converged2, x2 = PowerFlows._solve_powerflow!(pf, data, true) @test LinearAlgebra.norm(result_14 - x2, Inf) >= 1e-6 @test 1.08 <= x2[15] <= 1.09 end -@testset "AC Power Flow 14-Bus Line Configurations" for ACPowerFlow in +@testset "AC Power Flow 14-Bus Line Configurations" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) - base_res = solve_powerflow(ACPowerFlow(), sys) + pf = ACPowerFlow{ACSolver}() + base_res = solve_powerflow(pf, sys) branch = first(PSY.get_components(Line, sys)) dyn_branch = DynamicBranch(branch) add_component!(sys, dyn_branch) - @test dyn_pf = solve_ac_powerflow!(ACPowerFlow(), sys) - dyn_pf = solve_powerflow(ACPowerFlow(), sys) + @test dyn_pf = solve_ac_powerflow!(pf, sys) + dyn_pf = solve_powerflow(pf, sys) @test LinearAlgebra.norm(dyn_pf["bus_results"].Vm - base_res["bus_results"].Vm, Inf) <= 1e-6 sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) - solve_ac_powerflow!(ACPowerFlow(), sys) + solve_ac_powerflow!(pf, sys) @test PSY.get_active_power_flow(line) == 0.0 test_bus = get_component(PSY.Bus, sys, "Bus 4") @test isapprox(PSY.get_magnitude(test_bus), 1.002; atol = 1e-3, rtol = 0) @@ -69,12 +71,12 @@ end sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) - res = solve_powerflow(ACPowerFlow(), sys) + res = solve_powerflow(pf, sys) @test res["flow_results"].P_from_to[4] == 0.0 @test res["flow_results"].P_to_from[4] == 0.0 end -@testset "AC Power Flow 3-Bus Fixed FixedAdmittance testing" for ACPowerFlow in ( +@testset "AC Power Flow 3-Bus Fixed FixedAdmittance testing" for ACSolver in ( NLSolveACPowerFlow, KLUACPowerFlow, ) @@ -84,12 +86,13 @@ end bus_103 = get_component(PSY.Bus, sys_3bus, "BUS 3") fix_shunt = PSY.FixedAdmittance("FixAdmBus3", true, bus_103, 0.0 + 0.2im) add_component!(sys_3bus, fix_shunt) - df = solve_powerflow(ACPowerFlow(), sys_3bus) + pf = ACPowerFlow{ACSolver}() + df = solve_powerflow(pf, sys_3bus) @test isapprox(df["bus_results"].P_gen, p_gen_matpower_3bus, atol = 1e-4) @test isapprox(df["bus_results"].Q_gen, q_gen_matpower_3bus, atol = 1e-4) end -@testset "AC Power Flow convergence fail testing" for ACPowerFlow in +@testset "AC Power Flow convergence fail testing" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) pf_sys5_re = PSB.build_system(PSB.PSITestSystems, "c_sys5_re"; add_forecasts = false) remove_component!(Line, pf_sys5_re, "1") @@ -98,15 +101,17 @@ end PSY.set_x!(br, 20.0) PSY.set_r!(br, 2.0) + pf = ACPowerFlow{ACSolver}() + # This is a negative test. The data passed for sys5_re is known to be infeasible. @test_logs( (:error, "The powerflow solver returned convergence = false"), match_mode = :any, - @test !solve_ac_powerflow!(ACPowerFlow(), pf_sys5_re) + @test !solve_ac_powerflow!(pf, pf_sys5_re) ) end -@testset "AC Test 240 Case PSS/e results" for ACPowerFlow in +@testset "AC Test 240 Case PSS/e results" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) file = joinpath( TEST_FILES_DIR, @@ -122,9 +127,11 @@ end pf_bus_result_file = joinpath(TEST_FILES_DIR, "test_data", "pf_bus_results.csv") pf_gen_result_file = joinpath(TEST_FILES_DIR, "test_data", "pf_gen_results.csv") - pf = solve_ac_powerflow!(ACPowerFlow(), system) - @test pf - pf_result_df = solve_powerflow(ACPowerFlow(), system) + pf = ACPowerFlow{ACSolver}() + + pf1 = solve_ac_powerflow!(pf, system) + @test pf1 + pf_result_df = solve_powerflow(pf, system) v_diff, angle_diff, number = psse_bus_results_compare(pf_bus_result_file, pf_result_df) p_diff, q_diff, names = psse_gen_results_compare(pf_gen_result_file, system) @@ -140,7 +147,7 @@ end @test norm(q_diff, 2) / length(q_diff) < DIFF_L2_TOLERANCE end -@testset "AC Multiple sources at ref" for ACPowerFlow in +@testset "AC Multiple sources at ref" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b = ACBus(; @@ -175,16 +182,17 @@ end X_th = 1e-5, ) add_component!(sys, s2) - @test solve_ac_powerflow!(ACPowerFlow(), sys) + pf = ACPowerFlow{ACSolver}() + @test solve_ac_powerflow!(pf, sys) #Create power mismatch, test for error set_active_power!(get_component(Source, sys, "source_1"), -0.4) @test_throws ErrorException( "Sources do not match P and/or Q requirements for reference bus.", - ) solve_ac_powerflow!(ACPowerFlow(), sys) + ) solve_ac_powerflow!(pf, sys) end -@testset "AC PowerFlow with Multiple sources at PV" for ACPowerFlow in +@testset "AC PowerFlow with Multiple sources at PV" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b1 = ACBus(; @@ -255,17 +263,19 @@ end ) add_component!(sys, s3) - @test solve_ac_powerflow!(ACPowerFlow(), sys) + pf = ACPowerFlow{ACSolver}() + + @test solve_ac_powerflow!(pf, sys) #Create power mismatch, test for error set_reactive_power!(get_component(Source, sys, "source_3"), -0.5) @test_throws ErrorException("Sources do not match Q requirements for PV bus.") solve_ac_powerflow!( - ACPowerFlow(), + pf, sys, ) end -@testset "AC PowerFlow Source + non-source at Ref" for ACPowerFlow in +@testset "AC PowerFlow Source + non-source at Ref" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b = ACBus(; @@ -312,7 +322,9 @@ end ) add_component!(sys, g1) - @test solve_ac_powerflow!(ACPowerFlow(), sys) + pf = ACPowerFlow{ACSolver}() + + @test solve_ac_powerflow!(pf, sys) @test isapprox( get_active_power(get_component(Source, sys, "source_1")), 0.5; @@ -325,7 +337,7 @@ end ) end -@testset "AC PowerFlow Source + non-source at PV" for ACPowerFlow in +@testset "AC PowerFlow Source + non-source at PV" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) sys = System(100.0) b1 = ACBus(; @@ -407,7 +419,9 @@ end ) add_component!(sys, g1) - @test solve_ac_powerflow!(ACPowerFlow(), sys) + pf = ACPowerFlow{ACSolver}() + + @test solve_ac_powerflow!(pf, sys) @test isapprox( get_active_power(get_component(Source, sys, "source_2")), 0.5; diff --git a/test/test_powerflow_data.jl b/test/test_powerflow_data.jl index 5f91cddc..1f4152ad 100644 --- a/test/test_powerflow_data.jl +++ b/test/test_powerflow_data.jl @@ -1,6 +1,7 @@ @testset "PowerFlowData" begin sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) - @test PowerFlowData(NLSolveACPowerFlow(), sys) isa PF.ACPowerFlowData + @test PowerFlowData(ACPowerFlow{NLSolveACPowerFlow}(), sys) isa PF.ACPowerFlowData + @test PowerFlowData(ACPowerFlow{KLUACPowerFlow}(), sys) isa PF.ACPowerFlowData @test PowerFlowData(DCPowerFlow(), sys) isa PF.ABAPowerFlowData @test PowerFlowData(PTDFDCPowerFlow(), sys) isa PF.PTDFPowerFlowData @test PowerFlowData(vPTDFDCPowerFlow(), sys) isa PF.vPTDFPowerFlowData @@ -17,23 +18,23 @@ end PF.vPTDFPowerFlowData end -@testset "System <-> PowerFlowData round trip" begin +@testset "System <-> PowerFlowData round trip" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) # TODO currently only tested with ACPowerFlow # TODO test that update_system! errors if the PowerFlowData doesn't correspond to the system sys_original = build_system(PSISystems, "RTS_GMLC_DA_sys") - data_original = PowerFlowData(NLSolveACPowerFlow(), sys_original) + data_original = PowerFlowData(ACPowerFlow{ACSolver}(), sys_original) sys_modified = deepcopy(sys_original) modify_rts_system!(sys_modified) - data_modified = PowerFlowData(NLSolveACPowerFlow(), sys_original) + data_modified = PowerFlowData(ACPowerFlow{ACSolver}(), sys_original) modify_rts_powerflow!(data_modified) # update_system! with unmodified PowerFlowData should result in system that yields unmodified PowerFlowData # (NOTE does NOT necessarily yield original system due to power redistribution) sys_null_updated = deepcopy(sys_original) PF.update_system!(sys_null_updated, data_original) - data_null_updated = PowerFlowData(NLSolveACPowerFlow(), sys_null_updated) + data_null_updated = PowerFlowData(ACPowerFlow{ACSolver}(), sys_null_updated) @test IS.compare_values(powerflow_match_fn, data_null_updated, data_original) # Modified versions should not be the same as unmodified versions @@ -47,7 +48,7 @@ end # Constructing PowerFlowData from modified system should result in data_modified @test IS.compare_values( powerflow_match_fn, - PowerFlowData(NLSolveACPowerFlow(), sys_modified), + PowerFlowData(ACPowerFlow{ACSolver}(), sys_modified), data_modified, ) @@ -56,6 +57,6 @@ end sys_modify_updated = deepcopy(sys_original) PF.update_system!(sys_modify_updated, data_modified) sys_mod_redist = deepcopy(sys_modified) - PF.update_system!(sys_mod_redist, PowerFlowData(NLSolveACPowerFlow(), sys_mod_redist)) + PF.update_system!(sys_mod_redist, PowerFlowData(ACPowerFlow{ACSolver}(), sys_mod_redist)) @test IS.compare_values(powerflow_match_fn, sys_modify_updated, sys_mod_redist) end diff --git a/test/test_psse_export.jl b/test/test_psse_export.jl index df7d925d..58fe743c 100644 --- a/test/test_psse_export.jl +++ b/test/test_psse_export.jl @@ -222,9 +222,9 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; return result end -function test_power_flow(sys1::System, sys2::System; exclude_reactive_flow = false) - result1 = solve_powerflow(NLSolveACPowerFlow(), sys1) - result2 = solve_powerflow(NLSolveACPowerFlow(), sys2) +function test_power_flow(pf::ACPowerFlow{<:ACPowerFlowSolverType}, sys1::System, sys2::System; exclude_reactive_flow = false) + result1 = solve_powerflow(pf, sys1) + result2 = solve_powerflow(pf, sys2) reactive_power_tol = exclude_reactive_flow ? nothing : POWERFLOW_COMPARISON_TOLERANCE @test compare_df_within_tolerance("bus_results", result1["bus_results"], @@ -246,6 +246,7 @@ read_system_and_metadata(export_subdir) = read_system_and_metadata( get_psse_export_paths(export_subdir)...) function test_psse_round_trip( + pf::ACPowerFlow{<:ACPowerFlowSolverType}, sys::System, exporter::PSSEExporter, scenario_name::AbstractString, @@ -265,7 +266,7 @@ function test_psse_round_trip( sys2, sys2_metadata = read_system_and_metadata(raw_path, metadata_path) @test compare_systems_loosely(sys, sys2) do_power_flow_test && - test_power_flow(sys, sys2; exclude_reactive_flow = exclude_reactive_flow) + test_power_flow(pf, sys, sys2; exclude_reactive_flow = exclude_reactive_flow) end "Test that the two raw files are exactly identical and the two metadata files parse to identical JSON" @@ -318,17 +319,18 @@ end @test compare_systems_loosely(sys, deepcopy(sys)) end -@testset "PSSE Exporter with system_240[32].json, v33" begin +@testset "PSSE Exporter with system_240[32].json, v33" for (ACSolver, folder_name) in ((NLSolveACPowerFlow, "system_240_NLSolve"), (KLUACPowerFlow, "system_240_KLU")) sys = load_test_system() + pf = ACPowerFlow{ACSolver}() isnothing(sys) && return # PSS/E version must be one of the supported ones @test_throws ArgumentError PSSEExporter(sys, :vNonexistent, test_psse_export_dir) # Reimported export should be comparable to original system - export_location = joinpath(test_psse_export_dir, "v33", "system_240") + export_location = joinpath(test_psse_export_dir, "v33", folder_name) exporter = PSSEExporter(sys, :v33, export_location) - test_psse_round_trip(sys, exporter, "basic", export_location; + test_psse_round_trip(pf, sys, exporter, "basic", export_location; exclude_reactive_flow = true) # TODO why is reactive flow not matching? # Exporting the exact same thing again should result in the exact same files @@ -360,20 +362,21 @@ end @test_logs((:error, r"values do not match"), match_mode = :any, min_level = Logging.Error, compare_systems_loosely(sys, reread_sys2)) - test_power_flow(sys2, reread_sys2; exclude_reactive_flow = true) # TODO why is reactive flow not matching? + test_power_flow(pf, sys2, reread_sys2; exclude_reactive_flow = true) # TODO why is reactive flow not matching? end -@testset "PSSE Exporter with RTS_GMLC_DA_sys, v33" begin +@testset "PSSE Exporter with RTS_GMLC_DA_sys, v33" for (ACSolver, folder_name) in ((NLSolveACPowerFlow, "rts_gmlc_NLSolve"), (KLUACPowerFlow, "rts_gmlc_KLU")) sys = create_pf_friendly_rts_gmlc() + pf = ACPowerFlow{ACSolver}() set_units_base_system!(sys, UnitSystem.SYSTEM_BASE) # PSS/E version must be one of the supported ones @test_throws ArgumentError PSSEExporter(sys, :vNonexistent, test_psse_export_dir) # Reimported export should be comparable to original system - export_location = joinpath(test_psse_export_dir, "v33", "rts_gmlc") + export_location = joinpath(test_psse_export_dir, "v33", folder_name) exporter = PSSEExporter(sys, :v33, export_location) - test_psse_round_trip(sys, exporter, "basic", export_location; + test_psse_round_trip(pf, sys, exporter, "basic", export_location; exclude_reactive_flow = true) # TODO why is reactive flow not matching? # Exporting the exact same thing again should result in the exact same files @@ -404,11 +407,11 @@ end @test_logs((:error, r"values do not match"), match_mode = :any, min_level = Logging.Error, compare_systems_loosely(sys, reread_sys2)) - test_power_flow(sys2, reread_sys2; exclude_reactive_flow = true) # TODO why is reactive flow not matching? + test_power_flow(pf, sys2, reread_sys2; exclude_reactive_flow = true) # TODO why is reactive flow not matching? # Updating with changed value should result in a different reimport (PowerFlowData version) exporter = PSSEExporter(sys, :v33, export_location) - pf2 = PowerFlowData(NLSolveACPowerFlow(), sys) + pf2 = PowerFlowData(pf, sys) # This modifies the PowerFlowData in the same way that modify_rts_system! modifies the # system, so the reimport should be comparable to sys2 from above modify_rts_powerflow!(pf2) @@ -421,11 +424,11 @@ end @test_logs((:error, r"values do not match"), match_mode = :any, min_level = Logging.Error, compare_systems_loosely(sys, reread_sys3)) - test_power_flow(sys2, reread_sys3; exclude_reactive_flow = true) # TODO why is reactive flow not matching? + test_power_flow(pf, sys2, reread_sys3; exclude_reactive_flow = true) # TODO why is reactive flow not matching? # Exporting with write_comments should be comparable to original system exporter = PSSEExporter(sys, :v33, export_location; write_comments = true) - test_psse_round_trip(sys, exporter, "basic6", export_location; + test_psse_round_trip(pf, sys, exporter, "basic6", export_location; exclude_reactive_flow = true) # TODO why is reactive flow not matching? end From 42f02ab728c35fe8ab7a6e5f9a385999a82d5e96 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Mon, 16 Dec 2024 14:46:38 -0700 Subject: [PATCH 08/23] formatter --- src/PowerFlowData.jl | 8 ++++++-- src/newton_ac_powerflow.jl | 18 ++++++++++++------ src/post_processing.jl | 2 +- src/powerflow_types.jl | 9 ++++++--- test/test_powerflow_data.jl | 8 ++++++-- test/test_psse_export.jl | 17 ++++++++++++++--- 6 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/PowerFlowData.jl b/src/PowerFlowData.jl index 2fdfae9c..c22ca912 100644 --- a/src/PowerFlowData.jl +++ b/src/PowerFlowData.jl @@ -157,7 +157,7 @@ NOTE: use it for AC power flow computations. WARNING: functions for the evaluation of the multi-period AC PF still to be implemented. """ function PowerFlowData( - ::ACPowerFlow{<: ACPowerFlowSolverType}, + ::ACPowerFlow{<:ACPowerFlowSolverType}, sys::PSY.System; time_steps::Int = 1, timestep_names::Vector{String} = String[], @@ -440,7 +440,11 @@ Create an appropriate `PowerFlowContainer` for the given `PowerFlowEvaluationMod """ function make_power_flow_container end -make_power_flow_container(pfem::ACPowerFlow{<: ACPowerFlowSolverType}, sys::PSY.System; kwargs...) = +make_power_flow_container( + pfem::ACPowerFlow{<:ACPowerFlowSolverType}, + sys::PSY.System; + kwargs..., +) = PowerFlowData(pfem, sys; kwargs...) make_power_flow_container(pfem::DCPowerFlow, sys::PSY.System; kwargs...) = diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index a909edb5..e44d9aa6 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -31,7 +31,7 @@ solve_ac_powerflow!(sys, method=:newton) ``` """ function solve_ac_powerflow!( - pf::ACPowerFlow{<: ACPowerFlowSolverType}, + pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; kwargs..., ) @@ -72,7 +72,7 @@ res = solve_powerflow(sys, method=:newton) ``` """ function solve_powerflow( - pf::ACPowerFlow{<: ACPowerFlowSolverType}, + pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; kwargs..., ) @@ -128,7 +128,7 @@ function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) end function _solve_powerflow!( - pf::ACPowerFlow{<: ACPowerFlowSolverType}, + pf::ACPowerFlow{<:ACPowerFlowSolverType}, data::ACPowerFlowData, check_reactive_power_limits; nlsolve_kwargs..., @@ -160,12 +160,18 @@ function _newton_powerflow( df = NLsolve.OnceDifferentiable(pf, J, pf.x0, pf.residual, J.Jv) res = NLsolve.nlsolve(df, pf.x0; nlsolve_kwargs...) if !res.f_converged - @error("The powerflow solver NLSolve did not converge (returned convergence = $(res.f_converged))") + @error( + "The powerflow solver NLSolve did not converge (returned convergence = $(res.f_converged))" + ) end return res.f_converged, res.zero end -function _newton_powerflow(pf::ACPowerFlow{KLUACPowerFlow}, data::ACPowerFlowData; nlsolve_kwargs...) +function _newton_powerflow( + pf::ACPowerFlow{KLUACPowerFlow}, + data::ACPowerFlowData; + nlsolve_kwargs..., +) # Fetch maxIter and tol from kwargs, or use defaults if not provided maxIter = get(nlsolve_kwargs, :maxIter, DEFAULT_NR_MAX_ITER) tol = get(nlsolve_kwargs, :tol, DEFAULT_NR_TOL) @@ -180,7 +186,7 @@ function _newton_powerflow(pf::ACPowerFlow{KLUACPowerFlow}, data::ACPowerFlowDat Vm = data.bus_magnitude[:] # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: - Vm[pq] = clamp.(Vm[pq], 0.9, 1.1) + Vm[pq] = clamp.(Vm[pq], 0.9, 1.1) Va = data.bus_angles[:] V = Vm .* exp.(1im * Va) diff --git a/src/post_processing.jl b/src/post_processing.jl index 8ae1d687..c8229201 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -608,7 +608,7 @@ dictionary will therefore feature just one key linked to one DataFrame. vector containing the reults for one single time-period. """ function write_results( - ::ACPowerFlow{<: ACPowerFlowSolverType}, + ::ACPowerFlow{<:ACPowerFlowSolverType}, sys::PSY.System, data::ACPowerFlowData, result::Vector{Float64}, diff --git a/src/powerflow_types.jl b/src/powerflow_types.jl index cc0c5c20..fa9a1ae0 100644 --- a/src/powerflow_types.jl +++ b/src/powerflow_types.jl @@ -1,16 +1,19 @@ abstract type PowerFlowEvaluationModel end abstract type ACPowerFlowSolverType end - struct KLUACPowerFlow <: ACPowerFlowSolverType end struct NLSolveACPowerFlow <: ACPowerFlowSolverType end -Base.@kwdef struct ACPowerFlow{ACSolver <: ACPowerFlowSolverType} <: PowerFlowEvaluationModel +Base.@kwdef struct ACPowerFlow{ACSolver <: ACPowerFlowSolverType} <: + PowerFlowEvaluationModel check_reactive_power_limits::Bool = false end # Create a constructor that defaults to KLUACPowerFlow -function ACPowerFlow(; check_reactive_power_limits::Bool = false, ACSolver::Type{<:ACPowerFlowSolverType} = KLUACPowerFlow) +function ACPowerFlow(; + check_reactive_power_limits::Bool = false, + ACSolver::Type{<:ACPowerFlowSolverType} = KLUACPowerFlow, +) return ACPowerFlow{ACSolver}(check_reactive_power_limits) end diff --git a/test/test_powerflow_data.jl b/test/test_powerflow_data.jl index 1f4152ad..2d45a148 100644 --- a/test/test_powerflow_data.jl +++ b/test/test_powerflow_data.jl @@ -18,7 +18,8 @@ end PF.vPTDFPowerFlowData end -@testset "System <-> PowerFlowData round trip" for ACSolver in (NLSolveACPowerFlow, KLUACPowerFlow) +@testset "System <-> PowerFlowData round trip" for ACSolver in + (NLSolveACPowerFlow, KLUACPowerFlow) # TODO currently only tested with ACPowerFlow # TODO test that update_system! errors if the PowerFlowData doesn't correspond to the system @@ -57,6 +58,9 @@ end sys_modify_updated = deepcopy(sys_original) PF.update_system!(sys_modify_updated, data_modified) sys_mod_redist = deepcopy(sys_modified) - PF.update_system!(sys_mod_redist, PowerFlowData(ACPowerFlow{ACSolver}(), sys_mod_redist)) + PF.update_system!( + sys_mod_redist, + PowerFlowData(ACPowerFlow{ACSolver}(), sys_mod_redist), + ) @test IS.compare_values(powerflow_match_fn, sys_modify_updated, sys_mod_redist) end diff --git a/test/test_psse_export.jl b/test/test_psse_export.jl index 58fe743c..802eee3f 100644 --- a/test/test_psse_export.jl +++ b/test/test_psse_export.jl @@ -222,7 +222,12 @@ function compare_systems_loosely(sys1::PSY.System, sys2::PSY.System; return result end -function test_power_flow(pf::ACPowerFlow{<:ACPowerFlowSolverType}, sys1::System, sys2::System; exclude_reactive_flow = false) +function test_power_flow( + pf::ACPowerFlow{<:ACPowerFlowSolverType}, + sys1::System, + sys2::System; + exclude_reactive_flow = false, +) result1 = solve_powerflow(pf, sys1) result2 = solve_powerflow(pf, sys2) reactive_power_tol = @@ -319,7 +324,10 @@ end @test compare_systems_loosely(sys, deepcopy(sys)) end -@testset "PSSE Exporter with system_240[32].json, v33" for (ACSolver, folder_name) in ((NLSolveACPowerFlow, "system_240_NLSolve"), (KLUACPowerFlow, "system_240_KLU")) +@testset "PSSE Exporter with system_240[32].json, v33" for (ACSolver, folder_name) in ( + (NLSolveACPowerFlow, "system_240_NLSolve"), + (KLUACPowerFlow, "system_240_KLU"), +) sys = load_test_system() pf = ACPowerFlow{ACSolver}() isnothing(sys) && return @@ -365,7 +373,10 @@ end test_power_flow(pf, sys2, reread_sys2; exclude_reactive_flow = true) # TODO why is reactive flow not matching? end -@testset "PSSE Exporter with RTS_GMLC_DA_sys, v33" for (ACSolver, folder_name) in ((NLSolveACPowerFlow, "rts_gmlc_NLSolve"), (KLUACPowerFlow, "rts_gmlc_KLU")) +@testset "PSSE Exporter with RTS_GMLC_DA_sys, v33" for (ACSolver, folder_name) in ( + (NLSolveACPowerFlow, "rts_gmlc_NLSolve"), + (KLUACPowerFlow, "rts_gmlc_KLU"), +) sys = create_pf_friendly_rts_gmlc() pf = ACPowerFlow{ACSolver}() set_units_base_system!(sys, UnitSystem.SYSTEM_BASE) From e6a0364de0ab375c5d9a535eab7777a1c5e79475 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Tue, 17 Dec 2024 14:14:58 -0700 Subject: [PATCH 09/23] small fix ACPowerFlow type; add test for results consistency for 2000 bus system --- src/powerflow_types.jl | 3 +-- test/test_newton_ac_powerflow.jl | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/powerflow_types.jl b/src/powerflow_types.jl index fa9a1ae0..e490e1f5 100644 --- a/src/powerflow_types.jl +++ b/src/powerflow_types.jl @@ -10,9 +10,8 @@ Base.@kwdef struct ACPowerFlow{ACSolver <: ACPowerFlowSolverType} <: end # Create a constructor that defaults to KLUACPowerFlow -function ACPowerFlow(; +function ACPowerFlow(ACSolver::Type{<:ACPowerFlowSolverType} = KLUACPowerFlow; check_reactive_power_limits::Bool = false, - ACSolver::Type{<:ACPowerFlowSolverType} = KLUACPowerFlow, ) return ACPowerFlow{ACSolver}(check_reactive_power_limits) end diff --git a/test/test_newton_ac_powerflow.jl b/test/test_newton_ac_powerflow.jl index 7322b074..7a0b31d8 100644 --- a/test/test_newton_ac_powerflow.jl +++ b/test/test_newton_ac_powerflow.jl @@ -433,3 +433,23 @@ end atol = 0.001, ) end + + +@testset "Compare larger grid results KLU vs NLSolve" begin + sys = build_system(MatpowerTestSystems, "matpower_ACTIVSg2000_sys") + + pf_default = ACPowerFlow() + pf_klu = ACPowerFlow(KLUACPowerFlow) + pf_nlsolve = ACPowerFlow(NLSolveACPowerFlow) + + res_default = solve_powerflow(pf_default, sys) # must be the same as KLU + res_klu = solve_powerflow(pf_klu, sys) + res_nlsolve = solve_powerflow(pf_nlsolve, sys) + + + @test all(isapprox.(res_klu["bus_results"][!, :Vm], res_default["bus_results"][!, :Vm], rtol=0, atol=1e-12)) + @test all(isapprox.(res_klu["bus_results"][!, :θ], res_default["bus_results"][!, :θ], rtol=0, atol=1e-12)) + + @test all(isapprox.(res_klu["bus_results"][!, :Vm], res_nlsolve["bus_results"][!, :Vm], rtol=0, atol=1e-8)) + @test all(isapprox.(res_klu["bus_results"][!, :θ], res_nlsolve["bus_results"][!, :θ], rtol=0, atol=1e-8)) +end \ No newline at end of file From da200a12b3af28a7cfa81f607841f78efcf66eaa Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Tue, 17 Dec 2024 14:18:59 -0700 Subject: [PATCH 10/23] small change --- src/newton_ac_powerflow.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index e44d9aa6..2d428553 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -177,8 +177,6 @@ function _newton_powerflow( tol = get(nlsolve_kwargs, :tol, DEFAULT_NR_TOL) i = 0 - pf = PolarPowerFlow(data) - # Find indices for each bus type ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) From b385c6530d736a85e36e4f495507a2b78635df86 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 19 Dec 2024 10:51:38 -0700 Subject: [PATCH 11/23] optimize NR code to reduce memory allocations --- src/PowerFlows.jl | 2 +- src/newton_ac_powerflow.jl | 128 ++++++++++++++++++++++++++++--------- 2 files changed, 99 insertions(+), 31 deletions(-) diff --git a/src/PowerFlows.jl b/src/PowerFlows.jl index e63802b2..00907a8b 100644 --- a/src/PowerFlows.jl +++ b/src/PowerFlows.jl @@ -27,7 +27,7 @@ import KLU import SparseArrays import InfrastructureSystems import PowerNetworkMatrices -import SparseArrays: SparseMatrixCSC +import SparseArrays: SparseMatrixCSC, sparse import JSON3 import DataStructures: OrderedDict import Dates diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index 2d428553..4ef55631 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -181,12 +181,31 @@ function _newton_powerflow( ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) + pvpq = [pv; pq] + + #nref = length(ref) + npv = length(pv) + npq = length(pq) + npvpq = npv + npq + n_buses = length(data.bus_type) Vm = data.bus_magnitude[:] # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: - Vm[pq] = clamp.(Vm[pq], 0.9, 1.1) + Vm[pq] .= clamp.(Vm[pq], 0.9, 1.1) Va = data.bus_angles[:] - V = Vm .* exp.(1im * Va) + V = zeros(Complex{Float64}, length(Vm)) + V .= Vm .* exp.(1im * Va) + + Va_pv = view(Va, pv) + Va_pq = view(Va, pq) + Vm_pq = view(Vm, pq) + + # pre-allocate dx + dx = zeros(Float64, npv + 2 * npq) + + dx_Va_pv = view(dx, 1:npv) + dx_Va_pq = view(dx, (npv + 1):(npv + npq)) + dx_Vm_pq = view(dx, (npv + npq + 1):(npv + 2 * npq)) Ybus = data.power_network_matrix.data @@ -194,43 +213,92 @@ function _newton_powerflow( data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + 1im * (data.bus_reactivepower_injection[:] - data.bus_reactivepower_withdrawals[:]) - mis = V .* conj(Ybus * V) - Sbus - F = [real(mis[[pv; pq]]); imag(mis[pq])] - - # nref = length(ref) - npv = length(pv) - npq = length(pq) - - converged = (npv + npq) == 0 # if only ref buses present, we do not need to enter the loop + # Pre-allocate mis and F and create views for the respective real and imaginary sections of the arrays: + mis = zeros(Complex{Float64}, length(V)) + mis_pvpq = view(mis, pvpq) + mis_pq = view(mis, pq) + + F = zeros(Float64, npvpq + npq) + F_real = view(F, 1:npvpq) + F_imag = view(F, npvpq + 1:npvpq + npq) + + mis .= V .* conj(Ybus * V) .- Sbus + F_real .= real(mis_pvpq) # In-place assignment to the real part, using views + F_imag .= imag(mis_pq) # In-place assignment to the imaginary part, using views + + converged = npvpq == 0 # if only ref buses present, we do not need to enter the loop + + # preallocate Jacobian matrix + rows = vcat(1:npvpq, 1:npvpq, npvpq+1:npvpq+npq, npvpq+1:npvpq+npq) + cols = vcat(1:npvpq, npvpq+1:npvpq+npq, 1:npvpq, npvpq+1:npvpq+npq) + J = sparse(rows, cols, Float64(0)) + + # we need to define lookups for mappings of pv, pq buses onto the internal J indexing + pvpq_lookup = zeros(Int64, maximum([ref; pvpq]) + 1) + pvpq_lookup[pvpq] .= 1:npvpq + pq_lookup = zeros(Int64, maximum([ref; pvpq]) + 1) + pq_lookup[pq] .= 1:npq + + # with the pre-allocated J and lookups, we can define views into the sub-matrices of the J matrix for updating the J matrix in the NR loop + j11 = view(J, pvpq_lookup[pvpq], pvpq_lookup[pvpq]) + j12 = view(J, pvpq_lookup[pvpq], npvpq .+ pq_lookup[pq]) + j21 = view(J, npvpq .+ pq_lookup[pq], pvpq_lookup[pvpq]) + j22 = view(J, npvpq .+ pq_lookup[pq], npvpq .+ pq_lookup[pq]) + + # pre-allocate the dSbus_dVm, dSbus_dVa to have the same structure as Ybus + # they will follow the structure of Ybus except maybe when Ybus has zero values in its diagonal, which we do not expect here + rows, cols, _ = SparseArrays.findnz(Ybus) + dSbus_dVm = sparse(rows, cols, Complex{Float64}(0)) + dSbus_dVa = sparse(rows, cols, Complex{Float64}(0)) + + # create views for the sub-arrays of Sbus_dVa, Sbus_dVm for updating the J: + Sbus_dVa_j11 = view(dSbus_dVa, pvpq, pvpq) + Sbus_dVm_j12 = view(dSbus_dVm, pvpq, pq) + Sbus_dVa_j21 = view(dSbus_dVa, pq, pvpq) + Sbus_dVm_j22 = view(dSbus_dVm, pq, pq) + + # we need views of the diagonals to avoid using LinearAlgebra.Diagonal: + diagV = sparse(1:n_buses, 1:n_buses, Complex{Float64}(1)) + diag_idx = LinearAlgebra.diagind(diagV) + diagV_diag = view(diagV, diag_idx) + + diagIbus = sparse(1:n_buses, 1:n_buses, Complex{Float64}(1)) + diagIbus_diag = view(diagIbus, diag_idx) + + diagVnorm = sparse(1:n_buses, 1:n_buses, Complex{Float64}(1)) + diagVnorm_diag = view(diagVnorm, diag_idx) while i < maxIter && !converged i += 1 - diagV = LinearAlgebra.Diagonal(V) - diagIbus = LinearAlgebra.Diagonal(Ybus * V) - diagVnorm = LinearAlgebra.Diagonal(V ./ abs.(V)) - dSbus_dVm = diagV * conj(Ybus * diagVnorm) + conj(diagIbus) * diagVnorm - dSbus_dVa = 1im * diagV * conj(diagIbus - Ybus * diagV) - - j11 = real(dSbus_dVa[[pv; pq], [pv; pq]]) - j12 = real(dSbus_dVm[[pv; pq], pq]) - j21 = imag(dSbus_dVa[pq, [pv; pq]]) - j22 = imag(dSbus_dVm[pq, pq]) - J = [j11 j12; j21 j22] + + ## use the new value of V to update dSbus_dVa, dSbus_dVm: + diagV_diag .= V + diagIbus_diag .= Ybus * V + diagVnorm_diag .= V ./ abs.(V) + dSbus_dVm .= diagV * conj(Ybus * diagVnorm) + conj(diagIbus) * diagVnorm + dSbus_dVa .= 1im * diagV * conj(diagIbus - Ybus * diagV) + + # update the Jacobian by setting values through the pre-defined views for j11, j12, j21, j22 + j11 .= real(Sbus_dVa_j11) + j12 .= real(Sbus_dVm_j12) + j21 .= imag(Sbus_dVa_j21) + j22 .= imag(Sbus_dVm_j22) factor_J = KLU.klu(J) - dx = -(factor_J \ F) + dx .= -(factor_J \ F) - Va[pv] .+= dx[1:npv] - Va[pq] .+= dx[(npv + 1):(npv + npq)] - Vm[pq] .+= dx[(npv + npq + 1):(npv + 2 * npq)] + Va_pv .+= dx_Va_pv + Va_pq .+= dx_Va_pq + Vm_pq .+= dx_Vm_pq - V = Vm .* exp.(1im * Va) + V .= Vm .* exp.(1im * Va) - Vm = abs.(V) - Va = angle.(V) + Vm .= abs.(V) + Va .= angle.(V) - mis = V .* conj(Ybus * V) - Sbus - F = [real(mis[[pv; pq]]); imag(mis[pq])] + mis .= V .* conj(Ybus * V) .- Sbus + F_real .= real(mis_pvpq) # In-place assignment to the real part + F_imag .= imag(mis_pq) # In-place assignment to the imaginary part converged = LinearAlgebra.norm(F, Inf) < tol end From d7f7c88aaa20a516976c6f82e71a012b00dbbc39 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 19 Dec 2024 13:34:11 -0700 Subject: [PATCH 12/23] trying to reduce memory allocation --- src/newton_ac_powerflow.jl | 64 ++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index 4ef55631..014384b3 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -191,10 +191,10 @@ function _newton_powerflow( Vm = data.bus_magnitude[:] # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: - Vm[pq] .= clamp.(Vm[pq], 0.9, 1.1) + @. Vm[pq] = clamp.(Vm[pq], 0.9, 1.1) Va = data.bus_angles[:] V = zeros(Complex{Float64}, length(Vm)) - V .= Vm .* exp.(1im * Va) + @. V = Vm .* exp.(1im * Va) Va_pv = view(Va, pv) Va_pq = view(Va, pq) @@ -212,7 +212,7 @@ function _newton_powerflow( Sbus = data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + 1im * (data.bus_reactivepower_injection[:] - data.bus_reactivepower_withdrawals[:]) - + # Pre-allocate mis and F and create views for the respective real and imaginary sections of the arrays: mis = zeros(Complex{Float64}, length(V)) mis_pvpq = view(mis, pvpq) @@ -222,9 +222,9 @@ function _newton_powerflow( F_real = view(F, 1:npvpq) F_imag = view(F, npvpq + 1:npvpq + npq) - mis .= V .* conj(Ybus * V) .- Sbus - F_real .= real(mis_pvpq) # In-place assignment to the real part, using views - F_imag .= imag(mis_pq) # In-place assignment to the imaginary part, using views + mis .= V .* conj.(Ybus * V) .- Sbus + @. F_real = real(mis_pvpq) # In-place assignment to the real part, using views + @. F_imag = imag(mis_pq) # In-place assignment to the imaginary part, using views converged = npvpq == 0 # if only ref buses present, we do not need to enter the loop @@ -244,18 +244,6 @@ function _newton_powerflow( j12 = view(J, pvpq_lookup[pvpq], npvpq .+ pq_lookup[pq]) j21 = view(J, npvpq .+ pq_lookup[pq], pvpq_lookup[pvpq]) j22 = view(J, npvpq .+ pq_lookup[pq], npvpq .+ pq_lookup[pq]) - - # pre-allocate the dSbus_dVm, dSbus_dVa to have the same structure as Ybus - # they will follow the structure of Ybus except maybe when Ybus has zero values in its diagonal, which we do not expect here - rows, cols, _ = SparseArrays.findnz(Ybus) - dSbus_dVm = sparse(rows, cols, Complex{Float64}(0)) - dSbus_dVa = sparse(rows, cols, Complex{Float64}(0)) - - # create views for the sub-arrays of Sbus_dVa, Sbus_dVm for updating the J: - Sbus_dVa_j11 = view(dSbus_dVa, pvpq, pvpq) - Sbus_dVm_j12 = view(dSbus_dVm, pvpq, pq) - Sbus_dVa_j21 = view(dSbus_dVa, pq, pvpq) - Sbus_dVm_j22 = view(dSbus_dVm, pq, pq) # we need views of the diagonals to avoid using LinearAlgebra.Diagonal: diagV = sparse(1:n_buses, 1:n_buses, Complex{Float64}(1)) @@ -268,21 +256,37 @@ function _newton_powerflow( diagVnorm = sparse(1:n_buses, 1:n_buses, Complex{Float64}(1)) diagVnorm_diag = view(diagVnorm, diag_idx) + # pre-allocate the dSbus_dVm, dSbus_dVa to have the same structure as Ybus + # they will follow the structure of Ybus except maybe when Ybus has zero values in its diagonal, which we do not expect here + #rows, cols, _ = SparseArrays.findnz(Ybus) + #dSbus_dVm = sparse(rows, cols, Complex{Float64}(0)) + #dSbus_dVa = sparse(rows, cols, Complex{Float64}(0)) + + # preallocate dSbus_dVm, dSbus_dVa with correct structure: + dSbus_dVm = diagV * conj.(Ybus * diagVnorm) + conj.(diagIbus) * diagVnorm + dSbus_dVa = 1im * diagV * conj.(diagIbus - Ybus * diagV) + + # create views for the sub-arrays of Sbus_dVa, Sbus_dVm for updating the J: + Sbus_dVa_j11 = view(dSbus_dVa, pvpq, pvpq) + Sbus_dVm_j12 = view(dSbus_dVm, pvpq, pq) + Sbus_dVa_j21 = view(dSbus_dVa, pq, pvpq) + Sbus_dVm_j22 = view(dSbus_dVm, pq, pq) + while i < maxIter && !converged i += 1 ## use the new value of V to update dSbus_dVa, dSbus_dVm: diagV_diag .= V diagIbus_diag .= Ybus * V - diagVnorm_diag .= V ./ abs.(V) - dSbus_dVm .= diagV * conj(Ybus * diagVnorm) + conj(diagIbus) * diagVnorm - dSbus_dVa .= 1im * diagV * conj(diagIbus - Ybus * diagV) + @. diagVnorm_diag = V ./ abs.(V) + dSbus_dVm .= diagV * conj.(Ybus * diagVnorm) + conj.(diagIbus) * diagVnorm + dSbus_dVa .= 1im * diagV * conj.(diagIbus - Ybus * diagV) # update the Jacobian by setting values through the pre-defined views for j11, j12, j21, j22 - j11 .= real(Sbus_dVa_j11) - j12 .= real(Sbus_dVm_j12) - j21 .= imag(Sbus_dVa_j21) - j22 .= imag(Sbus_dVm_j22) + @. j11 = real(Sbus_dVa_j11) + @. j12 = real(Sbus_dVm_j12) + @. j21 = imag(Sbus_dVa_j21) + @. j22 = imag(Sbus_dVm_j22) factor_J = KLU.klu(J) dx .= -(factor_J \ F) @@ -291,21 +295,21 @@ function _newton_powerflow( Va_pq .+= dx_Va_pq Vm_pq .+= dx_Vm_pq - V .= Vm .* exp.(1im * Va) + @. V = Vm .* exp.(1im * Va) Vm .= abs.(V) Va .= angle.(V) - mis .= V .* conj(Ybus * V) .- Sbus - F_real .= real(mis_pvpq) # In-place assignment to the real part - F_imag .= imag(mis_pq) # In-place assignment to the imaginary part + mis .= V .* conj.(Ybus * V) .- Sbus + @. F_real = real(mis_pvpq) # In-place assignment to the real part + @. F_imag = imag(mis_pq) # In-place assignment to the imaginary part converged = LinearAlgebra.norm(F, Inf) < tol end if !converged @error("The powerflow solver with KLU did not converge after $i iterations") else - @debug("The powerflow solver with KLU converged after $i iterations") + @info("The powerflow solver with KLU converged after $i iterations") end # mock the expected x format, where the values depend on the type of the bus: From 9d3b09904fb6eae73332bddce27c554d96e78140 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Wed, 8 Jan 2025 15:29:14 -0700 Subject: [PATCH 13/23] NR PF non-allocating --- src/PowerFlows.jl | 1 + src/newton_ac_powerflow.jl | 517 +++++++++++++++++++----- src/powerflow_types.jl | 3 +- test/Project.toml | 1 + test/runtests.jl | 2 + test/test_newton_ac_powerflow.jl | 226 ++++++++++- test/test_utils/psse_results_compare.jl | 6 +- 7 files changed, 633 insertions(+), 123 deletions(-) diff --git a/src/PowerFlows.jl b/src/PowerFlows.jl index 00907a8b..51289003 100644 --- a/src/PowerFlows.jl +++ b/src/PowerFlows.jl @@ -6,6 +6,7 @@ export PowerFlowData export DCPowerFlow export NLSolveACPowerFlow export KLUACPowerFlow +export LUACPowerFlow export ACPowerFlow export ACPowerFlowSolverType export PTDFDCPowerFlow diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index 014384b3..b47472c8 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -167,6 +167,224 @@ function _newton_powerflow( return res.f_converged, res.zero end +function _update_V!(dx::Vector{Float64}, V::Vector{Complex{Float64}}, Vm::Vector{Float64}, + Va::Vector{Float64}, + pv::Vector{Int64}, pq::Vector{Int64}, dx_Va_pv::Vector{Int64}, dx_Va_pq::Vector{Int64}, + dx_Vm_pq::Vector{Int64}) + for (i, j) in zip(pv, dx_Va_pv) + Va[i] -= dx[j] + end + + for (i, j) in zip(pq, dx_Va_pq) + Va[i] -= dx[j] + end + + for (i, j) in zip(pq, dx_Vm_pq) + Vm[i] -= dx[j] + end + + V .= Vm .* exp.(1im .* Va) + + Vm .= abs.(V) + Va .= angle.(V) + return +end + +function _update_F!(F::Vector{Float64}, mis::Vector{Complex{Float64}}, + dx_Va_pv::Vector{Int64}, dx_Va_pq::Vector{Int64}, dx_Vm_pq::Vector{Int64}, + V::Vector{Complex{Float64}}, Ybus::SparseMatrixCSC{Complex{Float64}, Int64}, + Sbus::Vector{Complex{Float64}}, + pv::Vector{Int64}, pq::Vector{Int64}) + + #mis .= V .* conj.(Ybus * V) .- Sbus + LinearAlgebra.mul!(mis, Ybus, V) + mis .= V .* conj.(mis) .- Sbus + + for (i, j) in zip(dx_Va_pv, pv) + F[i] = real(mis[j]) + end + + for (i, j) in zip(dx_Va_pq, pq) + F[i] = real(mis[j]) + end + + for (i, j) in zip(dx_Vm_pq, pq) + F[i] = imag(mis[j]) + end + return +end + +function _update_dSbus_dV!(rows::Vector{Int64}, cols::Vector{Int64}, + V::Vector{Complex{Float64}}, Ybus::SparseMatrixCSC{Complex{Float64}, Int64}, + diagV::LinearAlgebra.Diagonal{Complex{Float64}, Vector{Complex{Float64}}}, + diagVnorm::LinearAlgebra.Diagonal{Complex{Float64}, Vector{Complex{Float64}}}, + diagIbus::LinearAlgebra.Diagonal{Complex{Float64}, Vector{Complex{Float64}}}, + diagIbus_diag::Vector{Complex{Float64}}, + dSbus_dVa::SparseMatrixCSC{Complex{Float64}, Int64}, + dSbus_dVm::SparseMatrixCSC{Complex{Float64}, Int64}, + r_dSbus_dVa::SparseMatrixCSC{Float64, Int64}, + r_dSbus_dVm::SparseMatrixCSC{Float64, Int64}, + i_dSbus_dVa::SparseMatrixCSC{Float64, Int64}, + i_dSbus_dVm::SparseMatrixCSC{Float64, Int64}, + Ybus_diagVnorm::SparseMatrixCSC{Complex{Float64}, Int64}, + conj_Ybus_diagVnorm::SparseMatrixCSC{Complex{Float64}, Int64}, + diagV_conj_Ybus_diagVnorm::SparseMatrixCSC{Complex{Float64}, Int64}, + conj_diagIbus::LinearAlgebra.Diagonal{Complex{Float64}, Vector{Complex{Float64}}}, + conj_diagIbus_diagVnorm::LinearAlgebra.Diagonal{ + Complex{Float64}, + Vector{Complex{Float64}}, + }, + Ybus_diagV::SparseMatrixCSC{Complex{Float64}, Int64}, + conj_Ybus_diagV::SparseMatrixCSC{Complex{Float64}, Int64}) + for i in eachindex(V) + diagV[i, i] = V[i] + diagVnorm[i, i] = V[i] / abs(V[i]) + end + + # manually calculate the diagIbus matrix + LinearAlgebra.mul!(diagIbus_diag, Ybus, V) + for i in eachindex(V) + diagIbus[i, i] = diagIbus_diag[i] + end + + # use the available matrices temporarily to calculate the dSbus_dV matrices + # original formula: + # dSbus_dVm .= diagV * conj.(Ybus * diagVnorm) + conj.(diagIbus) * diagVnorm + # non-allocating version: + + LinearAlgebra.mul!(Ybus_diagVnorm, Ybus, diagVnorm) + conj_Ybus_diagVnorm .= conj.(Ybus_diagVnorm) + LinearAlgebra.mul!(diagV_conj_Ybus_diagVnorm, diagV, conj_Ybus_diagVnorm) + conj_diagIbus .= conj.(diagIbus) + LinearAlgebra.mul!(conj_diagIbus_diagVnorm, conj_diagIbus, diagVnorm) + + dSbus_dVm .= diagV_conj_Ybus_diagVnorm + LinearAlgebra.axpy!(1, conj_diagIbus_diagVnorm, dSbus_dVm) + + # original formula: + # dSbus_dVa .= 1im * diagV * conj.(diagIbus - Ybus * diagV) + # non-allocating version: + LinearAlgebra.mul!(Ybus_diagV, Ybus, diagV) + # Take the conjugate of the result (conj(Ybus * diagV)); conj_diagIbus is already available + conj_Ybus_diagV .= conj.(Ybus_diagV) + + # write the result of conj.(diagIbus - Ybus * diagV) in conj_Ybus_diagV + LinearAlgebra.axpby!(1, conj_diagIbus, -1, conj_Ybus_diagV) + + # Multiply the result by diagV + LinearAlgebra.mul!(dSbus_dVa, diagV, conj_Ybus_diagV) + + # Now multiply by 1im to get the final result + #dSbus_dVa .*= 1im + LinearAlgebra.mul!(dSbus_dVa, dSbus_dVa, 1im) + + for c in cols + for r in rows + r_dSbus_dVa[r, c] = real(dSbus_dVa[r, c]) + i_dSbus_dVa[r, c] = imag(dSbus_dVa[r, c]) + r_dSbus_dVm[r, c] = real(dSbus_dVm[r, c]) + i_dSbus_dVm[r, c] = imag(dSbus_dVm[r, c]) + end + end + + # sometimes can allocate so we have to use the for loop above + # r_dSbus_dVa .= real.(dSbus_dVa) + # r_dSbus_dVm .= real.(dSbus_dVm) + # i_dSbus_dVa .= imag.(dSbus_dVa) + # i_dSbus_dVm .= imag.(dSbus_dVm) + return +end + +# this function is for testing purposes only +function _legacy_dSbus_dV( + V::Vector{Complex{Float64}}, + Ybus::SparseMatrixCSC{Complex{Float64}, Int64}, +) + diagV = LinearAlgebra.Diagonal(V) + diagVnorm = LinearAlgebra.Diagonal(V ./ abs.(V)) + diagIbus = LinearAlgebra.Diagonal(Ybus * V) + dSbus_dVm = diagV * conj.(Ybus * diagVnorm) + conj.(diagIbus) * diagVnorm + dSbus_dVa = 1im * diagV * conj.(diagIbus - Ybus * diagV) + return dSbus_dVa, dSbus_dVm +end + +# this function is for testing purposes only +function _legacy_J( + dSbus_dVa::SparseMatrixCSC{Complex{Float64}, Int64}, + dSbus_dVm::SparseMatrixCSC{Complex{Float64}, Int64}, + pvpq::Vector{Int64}, + pq::Vector{Int64}, +) + j11 = real(dSbus_dVa[pvpq, pvpq]) + j12 = real(dSbus_dVm[pvpq, pq]) + j21 = imag(dSbus_dVa[pq, pvpq]) + j22 = imag(dSbus_dVm[pq, pq]) + J = sparse([j11 j12; j21 j22]) + return J +end + +function _update_submatrix!( + A::SparseMatrixCSC, + B::SparseMatrixCSC, + rows_A::Vector{Int64}, + cols_A::Vector{Int64}, + rows_B::Vector{Int64}, + cols_B::Vector{Int64}, +) + for idj in eachindex(cols_A) + for idi in eachindex(rows_A) + A[rows_A[idi], cols_A[idj]] = B[rows_B[idi], cols_B[idj]] + end + end + return +end + +function _update_J!(J::SparseMatrixCSC, + r_dSbus_dVa::SparseMatrixCSC, + r_dSbus_dVm::SparseMatrixCSC, + i_dSbus_dVa::SparseMatrixCSC, + i_dSbus_dVm::SparseMatrixCSC, + pvpq::Vector{Int64}, + pq::Vector{Int64}, + j_pvpq::Vector{Int64}, + j_pq::Vector{Int64}, +) + _update_submatrix!(J, r_dSbus_dVa, j_pvpq, j_pvpq, pvpq, pvpq) + _update_submatrix!(J, r_dSbus_dVm, j_pvpq, j_pq, pvpq, pq) + _update_submatrix!(J, i_dSbus_dVa, j_pq, j_pvpq, pq, pvpq) + _update_submatrix!(J, i_dSbus_dVm, j_pq, j_pq, pq, pq) + return +end + +function _calc_x( + data::ACPowerFlowData, + V::Vector{Complex{Float64}}, + Va::Vector{Float64}, + Vm::Vector{Float64}, + Ybus::SparseMatrixCSC{Complex{Float64}, Int64}, + n_buses::Int64, +) + # mock the expected x format, where the values depend on the type of the bus: + x = zeros(Float64, 2 * n_buses) + Sbus_result = V .* conj(Ybus * V) # todo preallocate and pass as parameter + for (ix, b) in enumerate(data.bus_type) + if b == PSY.ACBusTypes.REF + # When bustype == REFERENCE PSY.Bus, state variables are Active and Reactive Power Generated + x[2 * ix - 1] = real(Sbus_result[ix]) + data.bus_activepower_withdrawals[ix] + x[2 * ix] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] + elseif b == PSY.ACBusTypes.PV + # When bustype == PV PSY.Bus, state variables are Reactive Power Generated and Voltage Angle + x[2 * ix - 1] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] + x[2 * ix] = Va[ix] + elseif b == PSY.ACBusTypes.PQ + # When bustype == PQ PSY.Bus, state variables are Voltage Magnitude and Voltage Angle + x[2 * ix - 1] = Vm[ix] + x[2 * ix] = Va[ix] + end + end + return x +end + function _newton_powerflow( pf::ACPowerFlow{KLUACPowerFlow}, data::ACPowerFlowData; @@ -177,13 +395,15 @@ function _newton_powerflow( tol = get(nlsolve_kwargs, :tol, DEFAULT_NR_TOL) i = 0 + Ybus = data.power_network_matrix.data + # Find indices for each bus type ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) pvpq = [pv; pq] - #nref = length(ref) + # nref = length(ref) npv = length(pv) npq = length(pq) npvpq = npv + npq @@ -191,118 +411,223 @@ function _newton_powerflow( Vm = data.bus_magnitude[:] # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: - @. Vm[pq] = clamp.(Vm[pq], 0.9, 1.1) + Vm[pq] .= clamp.(Vm[pq], 0.9, 1.1) Va = data.bus_angles[:] V = zeros(Complex{Float64}, length(Vm)) - @. V = Vm .* exp.(1im * Va) + V .= Vm .* exp.(1im .* Va) - Va_pv = view(Va, pv) - Va_pq = view(Va, pq) - Vm_pq = view(Vm, pq) + # early return if only ref buses present - no need to solve the power flow + converged = npvpq == 0 + if converged + # if only ref buses present, we do not need to enter the power flow loop + x = _calc_x(data, V, Va, Vm, Ybus, n_buses) + return (converged, x) + end - # pre-allocate dx - dx = zeros(Float64, npv + 2 * npq) + # we need to define lookups for mappings of pv, pq buses onto the internal J indexing + pvpq_lookup = zeros(Int64, maximum([ref; pvpq]) + 1) + pvpq_lookup[pvpq] .= 1:npvpq + pq_lookup = zeros(Int64, maximum([ref; pvpq]) + 1) + pq_lookup[pq] .= 1:npq - dx_Va_pv = view(dx, 1:npv) - dx_Va_pq = view(dx, (npv + 1):(npv + npq)) - dx_Vm_pq = view(dx, (npv + npq + 1):(npv + 2 * npq)) + # define the internal J indexing using the lookup arrays + j_pvpq = pvpq_lookup[pvpq] + j_pq = npvpq .+ pq_lookup[pq] - Ybus = data.power_network_matrix.data + # indices for updating of V + dx_Va_pv = Vector{Int64}([1:npv...]) + dx_Va_pq = Vector{Int64}([(npv + 1):(npv + npq)...]) + dx_Vm_pq = Vector{Int64}([(npv + npq + 1):(npv + 2 * npq)...]) Sbus = data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + 1im * (data.bus_reactivepower_injection[:] - data.bus_reactivepower_withdrawals[:]) - + # Pre-allocate mis and F and create views for the respective real and imaginary sections of the arrays: mis = zeros(Complex{Float64}, length(V)) - mis_pvpq = view(mis, pvpq) - mis_pq = view(mis, pq) - F = zeros(Float64, npvpq + npq) - F_real = view(F, 1:npvpq) - F_imag = view(F, npvpq + 1:npvpq + npq) - mis .= V .* conj.(Ybus * V) .- Sbus - @. F_real = real(mis_pvpq) # In-place assignment to the real part, using views - @. F_imag = imag(mis_pq) # In-place assignment to the imaginary part, using views + F .= [real(mis[pvpq]); imag(mis[pq])] + + # preallocate Jacobian matrix and arrays for calculating dSbus_dVa, dSbus_dVm + rows, cols = SparseArrays.findnz(Ybus) + + #diagV = sparse(1:n_buses, 1:n_buses, V) + diagV = LinearAlgebra.Diagonal(V) + diagIbus_diag = zeros(Complex{Float64}, size(V, 1)) + diagIbus = LinearAlgebra.Diagonal(diagIbus_diag) + + diagVnorm = LinearAlgebra.Diagonal(V ./ abs.(V)) + + Ybus_diagVnorm = sparse(rows, cols, Complex{Float64}(0)) + conj_Ybus_diagVnorm = sparse(rows, cols, Complex{Float64}(0)) + diagV_conj_Ybus_diagVnorm = sparse(rows, cols, Complex{Float64}(0)) + conj_diagIbus = conj.(diagIbus) + conj_diagIbus_diagVnorm = conj.(diagIbus) + Ybus_diagV = sparse(rows, cols, Complex{Float64}(0)) + conj_Ybus_diagV = sparse(rows, cols, Complex{Float64}(0)) + + dSbus_dVm = sparse(rows, cols, Complex{Float64}(0)) + dSbus_dVa = sparse(rows, cols, Complex{Float64}(0)) + r_dSbus_dVa = sparse(rows, cols, Float64(0)) + r_dSbus_dVm = sparse(rows, cols, Float64(0)) + i_dSbus_dVa = sparse(rows, cols, Float64(0)) + i_dSbus_dVm = sparse(rows, cols, Float64(0)) + + # maybe use this in the future? + # pvpq_rows = pvpq_lookup[rows][pvpq_lookup[rows] .!= 0] + # pvpq_cols = pvpq_lookup[cols][pvpq_lookup[cols] .!= 0] + # pq_rows = pq_lookup[rows][pq_lookup[rows] .!= 0] + # pq_cols = pq_lookup[cols][pq_lookup[cols] .!= 0] + + J_block = sparse(rows, cols, Float64(0), maximum(rows), maximum(cols)) + J = [J_block[pvpq, pvpq] J_block[pvpq, pq]; J_block[pq, pvpq] J_block[pq, pq]] + + # preallocate the KLU factorization object - symbolic object only + colptr = KLU.decrement(J.colptr) + rowval = KLU.decrement(J.rowval) + n = size(J, 1) + factor_J = KLU.KLUFactorization(n, colptr, rowval, J.nzval) + KLU.klu_analyze!(factor_J) + rf = Ref(factor_J.common) + # factorization for the numeric object does not work here: + # factor_J._numeric = KLU.klu_l_factor(colptr, rowval, J.nzval, factor_J._symbolic, rf) - converged = npvpq == 0 # if only ref buses present, we do not need to enter the loop + while i < maxIter && !converged + i += 1 - # preallocate Jacobian matrix - rows = vcat(1:npvpq, 1:npvpq, npvpq+1:npvpq+npq, npvpq+1:npvpq+npq) - cols = vcat(1:npvpq, npvpq+1:npvpq+npq, 1:npvpq, npvpq+1:npvpq+npq) - J = sparse(rows, cols, Float64(0)) + _update_dSbus_dV!(rows, cols, V, Ybus, diagV, diagVnorm, diagIbus, diagIbus_diag, + dSbus_dVa, dSbus_dVm, r_dSbus_dVa, r_dSbus_dVm, i_dSbus_dVa, i_dSbus_dVm, + Ybus_diagVnorm, conj_Ybus_diagVnorm, diagV_conj_Ybus_diagVnorm, + conj_diagIbus, conj_diagIbus_diagVnorm, Ybus_diagV, conj_Ybus_diagV) + + # todo: improve pvpq, pq, j_pvpq, j_pq (use more specific indices) + _update_J!( + J, + r_dSbus_dVa, + r_dSbus_dVm, + i_dSbus_dVa, + i_dSbus_dVm, + pvpq, + pq, + j_pvpq, + j_pq, + ) - # we need to define lookups for mappings of pv, pq buses onto the internal J indexing - pvpq_lookup = zeros(Int64, maximum([ref; pvpq]) + 1) - pvpq_lookup[pvpq] .= 1:npvpq - pq_lookup = zeros(Int64, maximum([ref; pvpq]) + 1) - pq_lookup[pq] .= 1:npq + # Workaround for the issue with KLU.klu_l_factor + # background: KLU.klu_l_factor does not work properly with the preallocated J matrix with dummy values + # the workaround is to initialize the numeric object here in the loop once and then refactorize the matrix in the loop inplace + if i == 1 + # works when J values are ok: + factor_J._numeric = + KLU.klu_l_factor(colptr, rowval, J.nzval, factor_J._symbolic, rf) + end - # with the pre-allocated J and lookups, we can define views into the sub-matrices of the J matrix for updating the J matrix in the NR loop - j11 = view(J, pvpq_lookup[pvpq], pvpq_lookup[pvpq]) - j12 = view(J, pvpq_lookup[pvpq], npvpq .+ pq_lookup[pq]) - j21 = view(J, npvpq .+ pq_lookup[pq], pvpq_lookup[pvpq]) - j22 = view(J, npvpq .+ pq_lookup[pq], npvpq .+ pq_lookup[pq]) - - # we need views of the diagonals to avoid using LinearAlgebra.Diagonal: - diagV = sparse(1:n_buses, 1:n_buses, Complex{Float64}(1)) - diag_idx = LinearAlgebra.diagind(diagV) - diagV_diag = view(diagV, diag_idx) - - diagIbus = sparse(1:n_buses, 1:n_buses, Complex{Float64}(1)) - diagIbus_diag = view(diagIbus, diag_idx) - - diagVnorm = sparse(1:n_buses, 1:n_buses, Complex{Float64}(1)) - diagVnorm_diag = view(diagVnorm, diag_idx) - - # pre-allocate the dSbus_dVm, dSbus_dVa to have the same structure as Ybus - # they will follow the structure of Ybus except maybe when Ybus has zero values in its diagonal, which we do not expect here - #rows, cols, _ = SparseArrays.findnz(Ybus) - #dSbus_dVm = sparse(rows, cols, Complex{Float64}(0)) - #dSbus_dVa = sparse(rows, cols, Complex{Float64}(0)) - - # preallocate dSbus_dVm, dSbus_dVa with correct structure: - dSbus_dVm = diagV * conj.(Ybus * diagVnorm) + conj.(diagIbus) * diagVnorm - dSbus_dVa = 1im * diagV * conj.(diagIbus - Ybus * diagV) + # factorize the numeric object of KLU inplace, while reusing the symbolic object + KLU.klu_l_refactor( + colptr, + rowval, + J.nzval, + factor_J._symbolic, + factor_J._numeric, + rf, + ) - # create views for the sub-arrays of Sbus_dVa, Sbus_dVm for updating the J: - Sbus_dVa_j11 = view(dSbus_dVa, pvpq, pvpq) - Sbus_dVm_j12 = view(dSbus_dVm, pvpq, pq) - Sbus_dVa_j21 = view(dSbus_dVa, pq, pvpq) - Sbus_dVm_j22 = view(dSbus_dVm, pq, pq) + # solve inplace - the results are written to F, so that we must use F instead of dx for updating V + KLU.klu_l_solve( + factor_J._symbolic, + factor_J._numeric, + size(F, 1), + size(F, 2), + F, + rf, + ) - while i < maxIter && !converged - i += 1 - - ## use the new value of V to update dSbus_dVa, dSbus_dVm: - diagV_diag .= V - diagIbus_diag .= Ybus * V - @. diagVnorm_diag = V ./ abs.(V) - dSbus_dVm .= diagV * conj.(Ybus * diagVnorm) + conj.(diagIbus) * diagVnorm - dSbus_dVa .= 1im * diagV * conj.(diagIbus - Ybus * diagV) + # KLU.solve! overwrites F with the solution instead of returning it as dx, so -F is used here to update V + _update_V!(F, V, Vm, Va, pv, pq, dx_Va_pv, dx_Va_pq, dx_Vm_pq) + + # here F is mismatch again + _update_F!(F, mis, dx_Va_pv, dx_Va_pq, dx_Vm_pq, V, Ybus, Sbus, pv, pq) + + converged = LinearAlgebra.norm(F, Inf) < tol + end + + if !converged + @error("The powerflow solver with KLU did not converge after $i iterations") + else + @info("The powerflow solver with KLU converged after $i iterations") + end + + x = _calc_x(data, V, Va, Vm, Ybus, n_buses) + + return (converged, x) +end + +# legacy NR implementation - here we do not care about allocations, we use this function only for testing purposes +function _newton_powerflow( + pf::ACPowerFlow{LUACPowerFlow}, + data::ACPowerFlowData; + nlsolve_kwargs..., +) + # Fetch maxIter and tol from kwargs, or use defaults if not provided + maxIter = get(nlsolve_kwargs, :maxIter, DEFAULT_NR_MAX_ITER) + tol = get(nlsolve_kwargs, :tol, DEFAULT_NR_TOL) + i = 0 + + Ybus = data.power_network_matrix.data + + # Find indices for each bus type + #ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) + pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) + pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) + pvpq = [pv; pq] - # update the Jacobian by setting values through the pre-defined views for j11, j12, j21, j22 - @. j11 = real(Sbus_dVa_j11) - @. j12 = real(Sbus_dVm_j12) - @. j21 = imag(Sbus_dVa_j21) - @. j22 = imag(Sbus_dVm_j22) + #nref = length(ref) + npv = length(pv) + npq = length(pq) + npvpq = npv + npq + n_buses = length(data.bus_type) - factor_J = KLU.klu(J) - dx .= -(factor_J \ F) + Vm = data.bus_magnitude[:] + # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: + Vm[pq] .= clamp.(Vm[pq], 0.9, 1.1) + Va = data.bus_angles[:] + V = zeros(Complex{Float64}, length(Vm)) + V .= Vm .* exp.(1im .* Va) - Va_pv .+= dx_Va_pv - Va_pq .+= dx_Va_pq - Vm_pq .+= dx_Vm_pq + # pre-allocate dx + dx = zeros(Float64, npv + 2 * npq) - @. V = Vm .* exp.(1im * Va) + Ybus = data.power_network_matrix.data + + Sbus = + data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + + 1im * (data.bus_reactivepower_injection[:] - data.bus_reactivepower_withdrawals[:]) + + mis = V .* conj.(Ybus * V) .- Sbus + F = [real(mis[pvpq]); imag(mis[pq])] + + converged = npvpq == 0 + + while i < maxIter && !converged + i += 1 + dSbus_dVa, dSbus_dVm = _legacy_dSbus_dV(V, Ybus) + J = _legacy_J(dSbus_dVa, dSbus_dVm, pvpq, pq) + # using a different factorization that KLU for testing + factor_J = LinearAlgebra.lu(J) + dx .= factor_J \ F + + Va[pv] .-= dx[1:npv] + Va[pq] .-= dx[(npv + 1):(npv + npq)] + Vm[pq] .-= dx[(npv + npq + 1):(npv + 2 * npq)] + V .= Vm .* exp.(1im .* Va) Vm .= abs.(V) Va .= angle.(V) - mis .= V .* conj.(Ybus * V) .- Sbus - @. F_real = real(mis_pvpq) # In-place assignment to the real part - @. F_imag = imag(mis_pq) # In-place assignment to the imaginary part + mis = V .* conj.(Ybus * V) .- Sbus + F .= [real(mis[pvpq]); imag(mis[pq])] + converged = LinearAlgebra.norm(F, Inf) < tol end @@ -312,25 +637,7 @@ function _newton_powerflow( @info("The powerflow solver with KLU converged after $i iterations") end - # mock the expected x format, where the values depend on the type of the bus: - n_buses = length(data.bus_type) - x = zeros(Float64, 2 * n_buses) - Sbus_result = V .* conj(Ybus * V) - for (ix, b) in enumerate(data.bus_type) - if b == PSY.ACBusTypes.REF - # When bustype == REFERENCE PSY.Bus, state variables are Active and Reactive Power Generated - x[2 * ix - 1] = real(Sbus_result[ix]) + data.bus_activepower_withdrawals[ix] - x[2 * ix] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] - elseif b == PSY.ACBusTypes.PV - # When bustype == PV PSY.Bus, state variables are Reactive Power Generated and Voltage Angle - x[2 * ix - 1] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] - x[2 * ix] = Va[ix] - elseif b == PSY.ACBusTypes.PQ - # When bustype == PQ PSY.Bus, state variables are Voltage Magnitude and Voltage Angle - x[2 * ix - 1] = Vm[ix] - x[2 * ix] = Va[ix] - end - end + x = _calc_x(data, V, Va, Vm, Ybus, n_buses) - return converged, x + return (converged, x) end diff --git a/src/powerflow_types.jl b/src/powerflow_types.jl index e490e1f5..60d08c18 100644 --- a/src/powerflow_types.jl +++ b/src/powerflow_types.jl @@ -3,13 +3,14 @@ abstract type ACPowerFlowSolverType end struct KLUACPowerFlow <: ACPowerFlowSolverType end struct NLSolveACPowerFlow <: ACPowerFlowSolverType end +struct LUACPowerFlow <: ACPowerFlowSolverType end # Only for testing, a basic implementation using LinearAlgebra.lu, allocates a lot of memory Base.@kwdef struct ACPowerFlow{ACSolver <: ACPowerFlowSolverType} <: PowerFlowEvaluationModel check_reactive_power_limits::Bool = false end -# Create a constructor that defaults to KLUACPowerFlow +# Create a constructor for ACPowerFlow that defaults to KLUACPowerFlow function ACPowerFlow(ACSolver::Type{<:ACPowerFlowSolverType} = KLUACPowerFlow; check_reactive_power_limits::Bool = false, ) diff --git a/test/Project.toml b/test/Project.toml index 7ab27d53..f931fbfa 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -13,6 +13,7 @@ PowerFlows = "94fada2c-fd9a-4e89-8d82-81405f5cb4f6" PowerNetworkMatrices = "bed98974-b02a-5e2f-9fe0-a103f5c450dd" PowerSystemCaseBuilder = "f00506e0-b84f-492a-93c2-c0a9afc4364e" PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtests.jl b/test/runtests.jl index 3f8827fc..f798c10e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,6 +10,8 @@ using CSV using DataFrames using JSON3 using DataStructures +import SparseArrays +import SparseArrays: SparseMatrixCSC, sparse const IS = InfrastructureSystems const PSB = PowerSystemCaseBuilder diff --git a/test/test_newton_ac_powerflow.jl b/test/test_newton_ac_powerflow.jl index 7a0b31d8..4564a293 100644 --- a/test/test_newton_ac_powerflow.jl +++ b/test/test_newton_ac_powerflow.jl @@ -1,5 +1,9 @@ @testset "AC Power Flow 14-Bus testing" for ACSolver in - (NLSolveACPowerFlow, KLUACPowerFlow) + ( + NLSolveACPowerFlow, + KLUACPowerFlow, + LUACPowerFlow, +) result_14 = [ 2.3255081760423684 -0.15529254415401786 @@ -48,7 +52,11 @@ end @testset "AC Power Flow 14-Bus Line Configurations" for ACSolver in - (NLSolveACPowerFlow, KLUACPowerFlow) + ( + NLSolveACPowerFlow, + KLUACPowerFlow, + LUACPowerFlow, +) sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) pf = ACPowerFlow{ACSolver}() base_res = solve_powerflow(pf, sys) @@ -78,7 +86,7 @@ end @testset "AC Power Flow 3-Bus Fixed FixedAdmittance testing" for ACSolver in ( NLSolveACPowerFlow, - KLUACPowerFlow, + KLUACPowerFlow, LUACPowerFlow, ) p_gen_matpower_3bus = [20.3512373930753, 100.0, 100.0] q_gen_matpower_3bus = [45.516916781567232, 10.453799727283879, -31.992561631394636] @@ -93,7 +101,11 @@ end end @testset "AC Power Flow convergence fail testing" for ACSolver in - (NLSolveACPowerFlow, KLUACPowerFlow) + ( + NLSolveACPowerFlow, + KLUACPowerFlow, + LUACPowerFlow, +) pf_sys5_re = PSB.build_system(PSB.PSITestSystems, "c_sys5_re"; add_forecasts = false) remove_component!(Line, pf_sys5_re, "1") remove_component!(Line, pf_sys5_re, "2") @@ -112,7 +124,11 @@ end end @testset "AC Test 240 Case PSS/e results" for ACSolver in - (NLSolveACPowerFlow, KLUACPowerFlow) + ( + NLSolveACPowerFlow, + KLUACPowerFlow, + LUACPowerFlow, +) file = joinpath( TEST_FILES_DIR, "test_data", @@ -148,7 +164,11 @@ end end @testset "AC Multiple sources at ref" for ACSolver in - (NLSolveACPowerFlow, KLUACPowerFlow) + ( + NLSolveACPowerFlow, + KLUACPowerFlow, + LUACPowerFlow, +) sys = System(100.0) b = ACBus(; number = 1, @@ -193,7 +213,11 @@ end end @testset "AC PowerFlow with Multiple sources at PV" for ACSolver in - (NLSolveACPowerFlow, KLUACPowerFlow) + ( + NLSolveACPowerFlow, + KLUACPowerFlow, + LUACPowerFlow, +) sys = System(100.0) b1 = ACBus(; number = 1, @@ -276,7 +300,11 @@ end end @testset "AC PowerFlow Source + non-source at Ref" for ACSolver in - (NLSolveACPowerFlow, KLUACPowerFlow) + ( + NLSolveACPowerFlow, + KLUACPowerFlow, + LUACPowerFlow, +) sys = System(100.0) b = ACBus(; number = 1, @@ -338,7 +366,11 @@ end end @testset "AC PowerFlow Source + non-source at PV" for ACSolver in - (NLSolveACPowerFlow, KLUACPowerFlow) + ( + NLSolveACPowerFlow, + KLUACPowerFlow, + LUACPowerFlow, +) sys = System(100.0) b1 = ACBus(; number = 1, @@ -434,22 +466,184 @@ end ) end - +# in this test, the following aspects are checked: +# 1. The results of the power flow are consistent for the KLU and NLSolve solvers +# 2. The results of the power flow are consistent for the KLU solver and the legacy implementation +# 3. The Jacobian matrix is the same for the KLU solver and the legacy implementation @testset "Compare larger grid results KLU vs NLSolve" begin sys = build_system(MatpowerTestSystems, "matpower_ACTIVSg2000_sys") pf_default = ACPowerFlow() pf_klu = ACPowerFlow(KLUACPowerFlow) pf_nlsolve = ACPowerFlow(NLSolveACPowerFlow) - + + PSY.set_units_base_system!(sys, "SYSTEM_BASE") + data = PowerFlowData( + pf_default, + sys; + check_connectivity = true) + + ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) + pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) + pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) + pvpq = [pv; pq] + + npvpq = length(pvpq) + npq = length(pq) + + # we need to define lookups for mappings of pv, pq buses onto the internal J indexing + pvpq_lookup = zeros(Int64, maximum([ref; pvpq]) + 1) + pvpq_lookup[pvpq] .= 1:npvpq + pq_lookup = zeros(Int64, maximum([ref; pvpq]) + 1) + pq_lookup[pq] .= 1:npq + + # define the internal J indexing using the lookup arrays + j_pvpq = pvpq_lookup[pvpq] + j_pq = npvpq .+ pq_lookup[pq] + + Vm0 = data.bus_magnitude[:] + Va0 = data.bus_angles[:] + V0 = Vm0 .* exp.(1im * Va0) + + Ybus = data.power_network_matrix.data + rows, cols = SparseArrays.findnz(Ybus) + + diagV = LinearAlgebra.Diagonal(V0) + diagIbus_diag = zeros(Complex{Float64}, size(V0, 1)) + diagIbus = LinearAlgebra.Diagonal(diagIbus_diag) + + diagVnorm = LinearAlgebra.Diagonal(V0 ./ abs.(V0)) + + Ybus_diagVnorm = sparse(rows, cols, Complex{Float64}(0)) + conj_Ybus_diagVnorm = sparse(rows, cols, Complex{Float64}(0)) + diagV_conj_Ybus_diagVnorm = sparse(rows, cols, Complex{Float64}(0)) + conj_diagIbus = conj.(diagIbus) + conj_diagIbus_diagVnorm = conj.(diagIbus) + Ybus_diagV = sparse(rows, cols, Complex{Float64}(0)) + conj_Ybus_diagV = sparse(rows, cols, Complex{Float64}(0)) + + dSbus_dVm = sparse(rows, cols, Complex{Float64}(0)) + dSbus_dVa = sparse(rows, cols, Complex{Float64}(0)) + r_dSbus_dVa = sparse(rows, cols, Float64(0)) + r_dSbus_dVm = sparse(rows, cols, Float64(0)) + i_dSbus_dVa = sparse(rows, cols, Float64(0)) + i_dSbus_dVm = sparse(rows, cols, Float64(0)) + + J_block = sparse(rows, cols, Float64(0), maximum(rows), maximum(cols), unique) + J0_KLU = [J_block[pvpq, pvpq] J_block[pvpq, pq]; J_block[pq, pvpq] J_block[pq, pq]] + PF._update_dSbus_dV!(rows, cols, V0, Ybus, diagV, diagVnorm, diagIbus, diagIbus_diag, + dSbus_dVa, dSbus_dVm, r_dSbus_dVa, r_dSbus_dVm, i_dSbus_dVa, i_dSbus_dVm, + Ybus_diagVnorm, conj_Ybus_diagVnorm, diagV_conj_Ybus_diagVnorm, conj_diagIbus, + conj_diagIbus_diagVnorm, Ybus_diagV, conj_Ybus_diagV) + PF._update_J!( + J0_KLU, + r_dSbus_dVa, + r_dSbus_dVm, + i_dSbus_dVa, + i_dSbus_dVm, + pvpq, + pq, + j_pvpq, + j_pq, + ) + + dSbus_dVa0_LU, dSbus_dVm0_LU = PF._legacy_dSbus_dV(V0, Ybus) + J0_LU = PF._legacy_J(dSbus_dVa, dSbus_dVm, pvpq, pq) + + @test all(isapprox.(J0_LU, J0_KLU, rtol = 0, atol = 1e-12)) + @test all(isapprox.(J0_LU.nzval, J0_KLU.nzval, rtol = 0, atol = 1e-12)) + @test J0_KLU.rowval == J0_LU.rowval + @test J0_KLU.colptr == J0_LU.colptr + res_default = solve_powerflow(pf_default, sys) # must be the same as KLU res_klu = solve_powerflow(pf_klu, sys) res_nlsolve = solve_powerflow(pf_nlsolve, sys) + @test all( + isapprox.( + res_klu["bus_results"][!, :Vm], + res_default["bus_results"][!, :Vm], + rtol = 0, + atol = 1e-12, + ), + ) + @test all( + isapprox.( + res_klu["bus_results"][!, :θ], + res_default["bus_results"][!, :θ], + rtol = 0, + atol = 1e-12, + ), + ) + + @test all( + isapprox.( + res_klu["bus_results"][!, :Vm], + res_nlsolve["bus_results"][!, :Vm], + rtol = 0, + atol = 1e-8, + ), + ) + @test all( + isapprox.( + res_klu["bus_results"][!, :θ], + res_nlsolve["bus_results"][!, :θ], + rtol = 0, + atol = 1e-8, + ), + ) + + # test against legacy implementation + pf_legacy = ACPowerFlow(LUACPowerFlow) + res_legacy = solve_powerflow(pf_legacy, sys) + + @test all( + isapprox.( + res_klu["bus_results"][!, :Vm], + res_legacy["bus_results"][!, :Vm], + rtol = 0, + atol = 1e-12, + ), + ) + @test all( + isapprox.( + res_klu["bus_results"][!, :θ], + res_legacy["bus_results"][!, :θ], + rtol = 0, + atol = 1e-12, + ), + ) - @test all(isapprox.(res_klu["bus_results"][!, :Vm], res_default["bus_results"][!, :Vm], rtol=0, atol=1e-12)) - @test all(isapprox.(res_klu["bus_results"][!, :θ], res_default["bus_results"][!, :θ], rtol=0, atol=1e-12)) + Vm1_KLU = res_klu["bus_results"][!, :Vm] + Va1_KLU = res_klu["bus_results"][!, :θ] + V1_KLU = Vm1_KLU .* exp.(1im * Va1_KLU) + + Vm1_LU = res_legacy["bus_results"][!, :Vm] + Va1_LU = res_legacy["bus_results"][!, :θ] + V1_LU = Vm1_LU .* exp.(1im * Va1_LU) + + J1_KLU = [J_block[pvpq, pvpq] J_block[pvpq, pq]; J_block[pq, pvpq] J_block[pq, pq]] + PF._update_dSbus_dV!(rows, cols, V1_KLU, Ybus, diagV, diagVnorm, diagIbus, + diagIbus_diag, dSbus_dVa, dSbus_dVm, r_dSbus_dVa, r_dSbus_dVm, i_dSbus_dVa, + i_dSbus_dVm, Ybus_diagVnorm, conj_Ybus_diagVnorm, diagV_conj_Ybus_diagVnorm, + conj_diagIbus, conj_diagIbus_diagVnorm, Ybus_diagV, conj_Ybus_diagV) + PF._update_J!( + J1_KLU, + r_dSbus_dVa, + r_dSbus_dVm, + i_dSbus_dVa, + i_dSbus_dVm, + pvpq, + pq, + j_pvpq, + j_pq, + ) - @test all(isapprox.(res_klu["bus_results"][!, :Vm], res_nlsolve["bus_results"][!, :Vm], rtol=0, atol=1e-8)) - @test all(isapprox.(res_klu["bus_results"][!, :θ], res_nlsolve["bus_results"][!, :θ], rtol=0, atol=1e-8)) -end \ No newline at end of file + dSbus_dVa1_LU, dSbus_dVm1_LU = PF._legacy_dSbus_dV(V1_LU, Ybus) + J1_LU = PF._legacy_J(dSbus_dVa1_LU, dSbus_dVm1_LU, pvpq, pq) + + @test all(isapprox.(J1_LU, J1_KLU, rtol = 0, atol = 1e-10)) + @test all(isapprox.(J1_LU.nzval, J1_KLU.nzval, rtol = 0, atol = 1e-10)) + @test J1_KLU.rowval == J1_LU.rowval + @test J1_KLU.colptr == J1_LU.colptr +end diff --git a/test/test_utils/psse_results_compare.jl b/test/test_utils/psse_results_compare.jl index 361cda84..3d35e7c8 100644 --- a/test/test_utils/psse_results_compare.jl +++ b/test/test_utils/psse_results_compare.jl @@ -1,4 +1,4 @@ -function psse_bus_results_compare(file_name, pf_results) +function psse_bus_results_compare(file_name::String, pf_results::Dict) pf_result_bus = CSV.read(file_name, DataFrame) v_diff = Float64[] @@ -15,6 +15,10 @@ function psse_bus_results_compare(file_name, pf_results) return v_diff, angle_diff, number end +function psse_bus_results_compare(file_name::String, pf_results::Bool) + throw(ArgumentError("pf_results not available - calculation failed")) +end + function psse_gen_results_compare(file_name, system::PSY.System) base_power = get_base_power(system) pf_result_gen = CSV.read(file_name, DataFrame) From d688b5856c87a8457c27aec17e3dd6c3e37fc749 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Wed, 8 Jan 2025 15:41:33 -0700 Subject: [PATCH 14/23] vectorized update for dSbus_dV --- src/newton_ac_powerflow.jl | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index b47472c8..231e9005 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -278,20 +278,21 @@ function _update_dSbus_dV!(rows::Vector{Int64}, cols::Vector{Int64}, #dSbus_dVa .*= 1im LinearAlgebra.mul!(dSbus_dVa, dSbus_dVa, 1im) - for c in cols - for r in rows - r_dSbus_dVa[r, c] = real(dSbus_dVa[r, c]) - i_dSbus_dVa[r, c] = imag(dSbus_dVa[r, c]) - r_dSbus_dVm[r, c] = real(dSbus_dVm[r, c]) - i_dSbus_dVm[r, c] = imag(dSbus_dVm[r, c]) - end - end + # this loop is slower so we should use vectorize assignments below + # for c in cols + # for r in rows + # r_dSbus_dVa[r, c] = real(dSbus_dVa[r, c]) + # i_dSbus_dVa[r, c] = imag(dSbus_dVa[r, c]) + # r_dSbus_dVm[r, c] = real(dSbus_dVm[r, c]) + # i_dSbus_dVm[r, c] = imag(dSbus_dVm[r, c]) + # end + # end # sometimes can allocate so we have to use the for loop above - # r_dSbus_dVa .= real.(dSbus_dVa) - # r_dSbus_dVm .= real.(dSbus_dVm) - # i_dSbus_dVa .= imag.(dSbus_dVa) - # i_dSbus_dVm .= imag.(dSbus_dVm) + r_dSbus_dVa .= real.(dSbus_dVa) + r_dSbus_dVm .= real.(dSbus_dVm) + i_dSbus_dVa .= imag.(dSbus_dVa) + i_dSbus_dVm .= imag.(dSbus_dVm) return end From 11d86a11272f08f65ab16bf024a0ada1be424d64 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 9 Jan 2025 14:29:23 -0700 Subject: [PATCH 15/23] fixes Add `solve_powerflow!` for AC power flow #49 --- src/PowerFlows.jl | 2 +- src/newton_ac_powerflow.jl | 96 +++++++++++++++---------- test/test_newton_ac_powerflow.jl | 22 +++--- test/test_utils/psse_results_compare.jl | 4 +- 4 files changed, 73 insertions(+), 51 deletions(-) diff --git a/src/PowerFlows.jl b/src/PowerFlows.jl index 51289003..eac152c5 100644 --- a/src/PowerFlows.jl +++ b/src/PowerFlows.jl @@ -1,7 +1,7 @@ module PowerFlows export solve_powerflow -export solve_ac_powerflow! +export solve_powerflow! export PowerFlowData export DCPowerFlow export NLSolveACPowerFlow diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index 231e9005..5a5f44b0 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -30,32 +30,34 @@ solve_ac_powerflow!(sys) solve_ac_powerflow!(sys, method=:newton) ``` """ -function solve_ac_powerflow!( +function solve_powerflow!( pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; - kwargs..., + kwargs... ) #Save per-unit flag settings_unit_cache = deepcopy(system.units_settings.unit_system) #Work in System per unit PSY.set_units_base_system!(system, "SYSTEM_BASE") - check_reactive_power_limits = get(kwargs, :check_reactive_power_limits, false) + data = PowerFlowData( pf, system; check_connectivity = get(kwargs, :check_connectivity, true), ) - max_iterations = DEFAULT_MAX_REDISTRIBUTION_ITERATIONS - converged, x = _solve_powerflow!(pf, data, check_reactive_power_limits; kwargs...) + + converged, x = _ac_powereflow(data, pf, system; kwargs...) + if converged - write_powerflow_solution!(system, x, max_iterations) + write_powerflow_solution!(system, x, get(kwargs, :maxIter, DEFAULT_NR_MAX_ITER)) @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) - return converged + else + @error("The powerflow solver returned convergence = $(converged)") end - @error("The powerflow solver returned convergence = $(converged)") + + #Restore original per unit base PSY.set_units_base_system!(system, settings_unit_cache) + return converged end @@ -74,33 +76,57 @@ res = solve_powerflow(sys, method=:newton) function solve_powerflow( pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; - kwargs..., + kwargs... ) #Save per-unit flag settings_unit_cache = deepcopy(system.units_settings.unit_system) #Work in System per unit PSY.set_units_base_system!(system, "SYSTEM_BASE") + data = PowerFlowData( pf, system; check_connectivity = get(kwargs, :check_connectivity, true), ) + # @error(typeof(data)) - converged, x = _solve_powerflow!(pf, data, pf.check_reactive_power_limits; kwargs...) - + converged, x = _ac_powereflow(data, pf, system; kwargs...) + if converged @info("PowerFlow solve converged, the results are exported in DataFrames") df_results = write_results(pf, system, data, x) - #Restore original per unit base - PSY.set_units_base_system!(system, settings_unit_cache) - return df_results + else + df_results = missing + @error("The powerflow solver returned convergence = $(converged)") end - @error("The powerflow solver returned convergence = $(converged)") + + #Restore original per unit base PSY.set_units_base_system!(system, settings_unit_cache) - return converged + + return df_results end -function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) + +function _ac_powereflow( + data::PowerFlowData, + pf::ACPowerFlow{<:ACPowerFlowSolverType}, + system::PSY.System; + kwargs... + ) + check_reactive_power_limits = get(kwargs, :check_reactive_power_limits, false) + + for _ in 1:MAX_REACTIVE_POWER_ITERATIONS + converged, x = _newton_powerflow(pf, data; kwargs...) + if !converged || !check_reactive_power_limits || _check_q_limit_bounds!(data, x) + return converged, x + end + end + + @error("could not enforce reactive power limits after $MAX_REACTIVE_POWER_ITERATIONS") + return converged, x +end + +function _check_q_limit_bounds!(data::PowerFlowData, zero::Vector{Float64}) bus_names = data.power_network_matrix.axes[1] within_limits = true for (ix, b) in enumerate(data.bus_type) @@ -133,20 +159,16 @@ function _solve_powerflow!( check_reactive_power_limits; nlsolve_kwargs..., ) - if check_reactive_power_limits - for _ in 1:MAX_REACTIVE_POWER_ITERATIONS - converged, x = _newton_powerflow(pf, data; nlsolve_kwargs...) - if converged - if _check_q_limit_bounds!(data, x) - return converged, x - end - else - return converged, x - end + + for _ in 1:MAX_REACTIVE_POWER_ITERATIONS + converged, x = _newton_powerflow(pf, data; nlsolve_kwargs...) + if !converged || !check_reactive_power_limits || _check_q_limit_bounds!(data, x) + return converged, x end - else - return _newton_powerflow(pf, data; nlsolve_kwargs...) end + # todo: throw error? set converged to false? + @error("could not enforce reactive power limits after $MAX_REACTIVE_POWER_ITERATIONS") + return converged, x end function _newton_powerflow( @@ -389,11 +411,11 @@ end function _newton_powerflow( pf::ACPowerFlow{KLUACPowerFlow}, data::ACPowerFlowData; - nlsolve_kwargs..., + kwargs..., ) # Fetch maxIter and tol from kwargs, or use defaults if not provided - maxIter = get(nlsolve_kwargs, :maxIter, DEFAULT_NR_MAX_ITER) - tol = get(nlsolve_kwargs, :tol, DEFAULT_NR_TOL) + maxIter = get(kwargs, :maxIter, DEFAULT_NR_MAX_ITER) + tol = get(kwargs, :tol, DEFAULT_NR_TOL) i = 0 Ybus = data.power_network_matrix.data @@ -568,11 +590,11 @@ end function _newton_powerflow( pf::ACPowerFlow{LUACPowerFlow}, data::ACPowerFlowData; - nlsolve_kwargs..., + kwargs..., ) # Fetch maxIter and tol from kwargs, or use defaults if not provided - maxIter = get(nlsolve_kwargs, :maxIter, DEFAULT_NR_MAX_ITER) - tol = get(nlsolve_kwargs, :tol, DEFAULT_NR_TOL) + maxIter = get(kwargs, :maxIter, DEFAULT_NR_MAX_ITER) + tol = get(kwargs, :tol, DEFAULT_NR_TOL) i = 0 Ybus = data.power_network_matrix.data diff --git a/test/test_newton_ac_powerflow.jl b/test/test_newton_ac_powerflow.jl index 4564a293..dbc4912c 100644 --- a/test/test_newton_ac_powerflow.jl +++ b/test/test_newton_ac_powerflow.jl @@ -41,7 +41,7 @@ #Compare results between finite diff methods and Jacobian method converged1, x1 = PowerFlows._solve_powerflow!(pf, data, false) @test LinearAlgebra.norm(result_14 - x1, Inf) <= 1e-6 - @test solve_ac_powerflow!(pf, sys; method = :newton) + @test solve_powerflow!(pf, sys; method = :newton) # Test enforcing the reactive power Limits set_reactive_power!(get_component(PowerLoad, sys, "Bus4"), 0.0) @@ -63,7 +63,7 @@ end branch = first(PSY.get_components(Line, sys)) dyn_branch = DynamicBranch(branch) add_component!(sys, dyn_branch) - @test dyn_pf = solve_ac_powerflow!(pf, sys) + @test dyn_pf = solve_powerflow!(pf, sys) dyn_pf = solve_powerflow(pf, sys) @test LinearAlgebra.norm(dyn_pf["bus_results"].Vm - base_res["bus_results"].Vm, Inf) <= 1e-6 @@ -71,7 +71,7 @@ end sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) line = get_component(Line, sys, "Line4") PSY.set_available!(line, false) - solve_ac_powerflow!(pf, sys) + solve_powerflow!(pf, sys) @test PSY.get_active_power_flow(line) == 0.0 test_bus = get_component(PSY.Bus, sys, "Bus 4") @test isapprox(PSY.get_magnitude(test_bus), 1.002; atol = 1e-3, rtol = 0) @@ -119,7 +119,7 @@ end @test_logs( (:error, "The powerflow solver returned convergence = false"), match_mode = :any, - @test !solve_ac_powerflow!(pf, pf_sys5_re) + @test !solve_powerflow!(pf, pf_sys5_re) ) end @@ -145,7 +145,7 @@ end pf = ACPowerFlow{ACSolver}() - pf1 = solve_ac_powerflow!(pf, system) + pf1 = solve_powerflow!(pf, system) @test pf1 pf_result_df = solve_powerflow(pf, system) @@ -203,13 +203,13 @@ end ) add_component!(sys, s2) pf = ACPowerFlow{ACSolver}() - @test solve_ac_powerflow!(pf, sys) + @test solve_powerflow!(pf, sys) #Create power mismatch, test for error set_active_power!(get_component(Source, sys, "source_1"), -0.4) @test_throws ErrorException( "Sources do not match P and/or Q requirements for reference bus.", - ) solve_ac_powerflow!(pf, sys) + ) solve_powerflow!(pf, sys) end @testset "AC PowerFlow with Multiple sources at PV" for ACSolver in @@ -289,11 +289,11 @@ end pf = ACPowerFlow{ACSolver}() - @test solve_ac_powerflow!(pf, sys) + @test solve_powerflow!(pf, sys) #Create power mismatch, test for error set_reactive_power!(get_component(Source, sys, "source_3"), -0.5) - @test_throws ErrorException("Sources do not match Q requirements for PV bus.") solve_ac_powerflow!( + @test_throws ErrorException("Sources do not match Q requirements for PV bus.") solve_powerflow!( pf, sys, ) @@ -352,7 +352,7 @@ end pf = ACPowerFlow{ACSolver}() - @test solve_ac_powerflow!(pf, sys) + @test solve_powerflow!(pf, sys) @test isapprox( get_active_power(get_component(Source, sys, "source_1")), 0.5; @@ -453,7 +453,7 @@ end pf = ACPowerFlow{ACSolver}() - @test solve_ac_powerflow!(pf, sys) + @test solve_powerflow!(pf, sys) @test isapprox( get_active_power(get_component(Source, sys, "source_2")), 0.5; diff --git a/test/test_utils/psse_results_compare.jl b/test/test_utils/psse_results_compare.jl index 3d35e7c8..dfe162b7 100644 --- a/test/test_utils/psse_results_compare.jl +++ b/test/test_utils/psse_results_compare.jl @@ -15,8 +15,8 @@ function psse_bus_results_compare(file_name::String, pf_results::Dict) return v_diff, angle_diff, number end -function psse_bus_results_compare(file_name::String, pf_results::Bool) - throw(ArgumentError("pf_results not available - calculation failed")) +function psse_bus_results_compare(file_name::String, pf_results::Missing) + throw(ArgumentError("pf_results are missing - calculation failed")) end function psse_gen_results_compare(file_name, system::PSY.System) From 2214214e5cd9db49f4b2117765399c2a5824bc2c Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 9 Jan 2025 14:31:36 -0700 Subject: [PATCH 16/23] formatting --- src/newton_ac_powerflow.jl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index 5a5f44b0..dc22b6ff 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -33,7 +33,7 @@ solve_ac_powerflow!(sys, method=:newton) function solve_powerflow!( pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; - kwargs... + kwargs..., ) #Save per-unit flag settings_unit_cache = deepcopy(system.units_settings.unit_system) @@ -45,7 +45,7 @@ function solve_powerflow!( system; check_connectivity = get(kwargs, :check_connectivity, true), ) - + converged, x = _ac_powereflow(data, pf, system; kwargs...) if converged @@ -76,7 +76,7 @@ res = solve_powerflow(sys, method=:newton) function solve_powerflow( pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; - kwargs... + kwargs..., ) #Save per-unit flag settings_unit_cache = deepcopy(system.units_settings.unit_system) @@ -91,7 +91,7 @@ function solve_powerflow( # @error(typeof(data)) converged, x = _ac_powereflow(data, pf, system; kwargs...) - + if converged @info("PowerFlow solve converged, the results are exported in DataFrames") df_results = write_results(pf, system, data, x) @@ -106,13 +106,12 @@ function solve_powerflow( return df_results end - function _ac_powereflow( data::PowerFlowData, pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; - kwargs... - ) + kwargs..., +) check_reactive_power_limits = get(kwargs, :check_reactive_power_limits, false) for _ in 1:MAX_REACTIVE_POWER_ITERATIONS @@ -159,7 +158,6 @@ function _solve_powerflow!( check_reactive_power_limits; nlsolve_kwargs..., ) - for _ in 1:MAX_REACTIVE_POWER_ITERATIONS converged, x = _newton_powerflow(pf, data; nlsolve_kwargs...) if !converged || !check_reactive_power_limits || _check_q_limit_bounds!(data, x) From b700bc45c081941adde4c59401825fbaec200509 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 9 Jan 2025 16:46:52 -0700 Subject: [PATCH 17/23] WIP: multiperiod AC PF --- src/PowerFlowData.jl | 3 +- src/common.jl | 9 +-- src/newton_ac_powerflow.jl | 84 +++++++++++++++++++++++---- src/post_processing.jl | 2 +- test/test_multiperiod_ac_powerflow.jl | 53 +++++++++++++++++ 5 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 test/test_multiperiod_ac_powerflow.jl diff --git a/src/PowerFlowData.jl b/src/PowerFlowData.jl index c22ca912..db1ede00 100644 --- a/src/PowerFlowData.jl +++ b/src/PowerFlowData.jl @@ -173,6 +173,8 @@ function PowerFlowData( end end + timestep_map = Dict(zip([i for i in 1:time_steps], timestep_names)) + # get data for calculations power_network_matrix = PNM.Ybus(sys; check_connectivity = check_connectivity) @@ -200,7 +202,6 @@ function PowerFlowData( bus_reactivepower_bounds[i] = [0.0, 0.0] end _get_reactive_power_bound!(bus_reactivepower_bounds, bus_lookup, sys) - timestep_map = Dict(1 => "1") valid_ix = setdiff(1:n_buses, ref_bus_positions) neighbors = _calculate_neighbors(power_network_matrix) aux_network_matrix = nothing diff --git a/src/common.jl b/src/common.jl index 15bb6603..314b80c5 100644 --- a/src/common.jl +++ b/src/common.jl @@ -162,7 +162,7 @@ function make_powerflowdata( # TODO: bus_type might need to also be a Matrix since the type can change for a particular scenario bus_type = Vector{PSY.ACBusTypes}(undef, n_buses) bus_angles = zeros(Float64, n_buses) - bus_magnitude = zeros(Float64, n_buses) + bus_magnitude = ones(Float64, n_buses) _initialize_bus_data!( bus_type, @@ -193,15 +193,16 @@ function make_powerflowdata( ) # initialize data - init_1 = zeros(n_buses, time_steps) - init_2 = zeros(n_branches, time_steps) + init_1 = zeros(Float64, n_buses, time_steps) + init_1m = ones(Float64, n_buses, time_steps) + init_2 = zeros(Float64, n_branches, time_steps) # define fields as matrices whose number of columns is eqault to the number of time_steps bus_activepower_injection_1 = deepcopy(init_1) bus_reactivepower_injection_1 = deepcopy(init_1) bus_activepower_withdrawals_1 = deepcopy(init_1) bus_reactivepower_withdrawals_1 = deepcopy(init_1) - bus_magnitude_1 = deepcopy(init_1) + bus_magnitude_1 = deepcopy(init_1m) bus_angles_1 = deepcopy(init_1) branch_flow_values_1 = deepcopy(init_2) diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index dc22b6ff..39caacb9 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -106,16 +106,59 @@ function solve_powerflow( return df_results end +# Multiperiod power flow - work in progress +function solve_powerflow( + pf::ACPowerFlow{<:ACPowerFlowSolverType}, + data::ACPowerFlowData, + system::PSY.System; + kwargs..., +) + #Save per-unit flag + settings_unit_cache = deepcopy(system.units_settings.unit_system) + #Work in System per unit + PSY.set_units_base_system!(system, "SYSTEM_BASE") + + sorted_time_steps = sort(collect(keys(data.timestep_map))) + # preallocate results + ts_converged = zeros(Bool, 1, length(sorted_time_steps)) + ts_x = zeros(Float64, 2 * length(data.bus_type), length(sorted_time_steps)) + + for t in sorted_time_steps + converged, x = _ac_powereflow(data, pf, system; time_step = t, kwargs...) + ts_converged[1, t] = converged + ts_x[:, t] .= x + + #todo: implement write_results for multiperiod power flow + + # if converged + # @info("PowerFlow solve converged, the results are exported in DataFrames") + # df_results = write_results(pf, system, data, x) + # else + # df_results = missing + # @error("The powerflow solver returned convergence = $(converged)") + # end + end + + #Restore original per unit base + PSY.set_units_base_system!(system, settings_unit_cache) + + # todo: + # return df_results + + return ts_converged, ts_x +end + function _ac_powereflow( - data::PowerFlowData, + data::ACPowerFlowData, pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; + time_step::Int64 = 1, kwargs..., ) check_reactive_power_limits = get(kwargs, :check_reactive_power_limits, false) for _ in 1:MAX_REACTIVE_POWER_ITERATIONS - converged, x = _newton_powerflow(pf, data; kwargs...) + converged, x = _newton_powerflow(pf, data; time_step = time_step, kwargs...) if !converged || !check_reactive_power_limits || _check_q_limit_bounds!(data, x) return converged, x end @@ -125,7 +168,7 @@ function _ac_powereflow( return converged, x end -function _check_q_limit_bounds!(data::PowerFlowData, zero::Vector{Float64}) +function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) bus_names = data.power_network_matrix.axes[1] within_limits = true for (ix, b) in enumerate(data.bus_type) @@ -172,8 +215,17 @@ end function _newton_powerflow( pf::ACPowerFlow{NLSolveACPowerFlow}, data::ACPowerFlowData; + time_step::Int64 = 1, # not implemented for NLSolve and not used nlsolve_kwargs..., ) + if time_step != 1 + throw( + ArgumentError( + "Multiperiod power flow not implemented for NLSolve AC power flow", + ), + ) + end + pf = PolarPowerFlow(data) J = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) @@ -409,6 +461,7 @@ end function _newton_powerflow( pf::ACPowerFlow{KLUACPowerFlow}, data::ACPowerFlowData; + time_step::Int64 = 1, kwargs..., ) # Fetch maxIter and tol from kwargs, or use defaults if not provided @@ -430,10 +483,10 @@ function _newton_powerflow( npvpq = npv + npq n_buses = length(data.bus_type) - Vm = data.bus_magnitude[:] + Vm = data.bus_magnitude[:, time_step] # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: Vm[pq] .= clamp.(Vm[pq], 0.9, 1.1) - Va = data.bus_angles[:] + Va = data.bus_angles[:, time_step] V = zeros(Complex{Float64}, length(Vm)) V .= Vm .* exp.(1im .* Va) @@ -461,8 +514,12 @@ function _newton_powerflow( dx_Vm_pq = Vector{Int64}([(npv + npq + 1):(npv + 2 * npq)...]) Sbus = - data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + - 1im * (data.bus_reactivepower_injection[:] - data.bus_reactivepower_withdrawals[:]) + data.bus_activepower_injection[:, time_step] - + data.bus_activepower_withdrawals[:, time_step] + + 1im * ( + data.bus_reactivepower_injection[:, time_step] - + data.bus_reactivepower_withdrawals[:, time_step] + ) # Pre-allocate mis and F and create views for the respective real and imaginary sections of the arrays: mis = zeros(Complex{Float64}, length(V)) @@ -588,6 +645,7 @@ end function _newton_powerflow( pf::ACPowerFlow{LUACPowerFlow}, data::ACPowerFlowData; + time_step::Int64 = 1, kwargs..., ) # Fetch maxIter and tol from kwargs, or use defaults if not provided @@ -609,10 +667,10 @@ function _newton_powerflow( npvpq = npv + npq n_buses = length(data.bus_type) - Vm = data.bus_magnitude[:] + Vm = data.bus_magnitude[:, time_step] # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: Vm[pq] .= clamp.(Vm[pq], 0.9, 1.1) - Va = data.bus_angles[:] + Va = data.bus_angles[:, time_step] V = zeros(Complex{Float64}, length(Vm)) V .= Vm .* exp.(1im .* Va) @@ -622,8 +680,12 @@ function _newton_powerflow( Ybus = data.power_network_matrix.data Sbus = - data.bus_activepower_injection[:] - data.bus_activepower_withdrawals[:] + - 1im * (data.bus_reactivepower_injection[:] - data.bus_reactivepower_withdrawals[:]) + data.bus_activepower_injection[:, time_step] - + data.bus_activepower_withdrawals[:, time_step] + + 1im * ( + data.bus_reactivepower_injection[:, time_step] - + data.bus_reactivepower_withdrawals[:, time_step] + ) mis = V .* conj.(Ybus * V) .- Sbus F = [real(mis[pvpq]); imag(mis[pq])] diff --git a/src/post_processing.jl b/src/post_processing.jl index c8229201..8e2346e1 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -611,7 +611,7 @@ function write_results( ::ACPowerFlow{<:ACPowerFlowSolverType}, sys::PSY.System, data::ACPowerFlowData, - result::Vector{Float64}, + result::Union{Vector{Float64}, Matrix{Float64}}, #todo ) @info("Voltages are exported in pu. Powers are exported in MW/MVAr.") buses = sort!(collect(PSY.get_components(PSY.Bus, sys)); by = x -> PSY.get_number(x)) diff --git a/test/test_multiperiod_ac_powerflow.jl b/test/test_multiperiod_ac_powerflow.jl new file mode 100644 index 00000000..9a2f8add --- /dev/null +++ b/test/test_multiperiod_ac_powerflow.jl @@ -0,0 +1,53 @@ +# Work in progress +@testset "MULTI-PERIOD power flows evaluation: NR" begin + # get system + sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) + injections = CSV.read( + joinpath(MAIN_DIR, "test", "test_data", "c_sys14_injections.csv"), + DataFrame; + header = 0, + ) + withdrawals = CSV.read( + joinpath(MAIN_DIR, "test", "test_data", "c_sys14_withdrawals.csv"), + DataFrame; + header = 0, + ) + flows = CSV.read( + joinpath(MAIN_DIR, "test", "test_data", "c_sys14_flows.csv"), + DataFrame; + header = 0, + ) + angles = CSV.read( + joinpath(MAIN_DIR, "test", "test_data", "c_sys14_angles.csv"), + DataFrame; + header = 0, + ) + + ############################################################################## + + # create structure for multi-period case + pf = ACPowerFlow() + time_steps = 24 + data = PowerFlowData(pf, sys; time_steps = time_steps) + + # allocate data from csv + injs = Matrix(injections) + withs = Matrix(withdrawals) + + data.bus_activepower_injection .= deepcopy(injs) + data.bus_activepower_withdrawals .= deepcopy(withs) + + # get power flows with NR KLU method and write results + ts_converged, ts_x = solve_powerflow(pf, data, sys) + + # todo: implement write_results for multiperiod power flow + # todo: test the result values + + # check results + # for i in 1:length(data.timestep_map) + # net_flow = results[data.timestep_map[i]]["flow_results"].P_from_to + # net_flow_tf = results[data.timestep_map[i]]["flow_results"].P_to_from + # @test isapprox(net_flow, flows[:, i], atol = 1e-5) + # @test isapprox(net_flow_tf, -flows[:, i], atol = 1e-5) + # end +end From 0f1545eb7b31177665fc0d1723e4eebc9bf9d084 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Fri, 10 Jan 2025 11:40:23 -0700 Subject: [PATCH 18/23] adrressing comments --- src/PowerFlows.jl | 2 +- src/newton_ac_powerflow.jl | 192 ++++++------------------------- src/nlsolve_powerflow.jl | 27 +++++ src/powerflow_types.jl | 1 - test/runtests.jl | 3 + test/test_newton_ac_powerflow.jl | 23 ++-- test/test_utils/legacy_pf.jl | 118 +++++++++++++++++++ 7 files changed, 197 insertions(+), 169 deletions(-) create mode 100644 src/nlsolve_powerflow.jl create mode 100644 test/test_utils/legacy_pf.jl diff --git a/src/PowerFlows.jl b/src/PowerFlows.jl index eac152c5..f3a44e0f 100644 --- a/src/PowerFlows.jl +++ b/src/PowerFlows.jl @@ -6,7 +6,6 @@ export PowerFlowData export DCPowerFlow export NLSolveACPowerFlow export KLUACPowerFlow -export LUACPowerFlow export ACPowerFlow export ACPowerFlowSolverType export PTDFDCPowerFlow @@ -46,5 +45,6 @@ include("solve_dc_powerflow.jl") include("ac_power_flow.jl") include("ac_power_flow_jacobian.jl") include("newton_ac_powerflow.jl") +include("nlsolve_powerflow.jl") include("post_processing.jl") end diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index 39caacb9..731795a0 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -212,32 +212,6 @@ function _solve_powerflow!( return converged, x end -function _newton_powerflow( - pf::ACPowerFlow{NLSolveACPowerFlow}, - data::ACPowerFlowData; - time_step::Int64 = 1, # not implemented for NLSolve and not used - nlsolve_kwargs..., -) - if time_step != 1 - throw( - ArgumentError( - "Multiperiod power flow not implemented for NLSolve AC power flow", - ), - ) - end - - pf = PolarPowerFlow(data) - J = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) - - df = NLsolve.OnceDifferentiable(pf, J, pf.x0, pf.residual, J.Jv) - res = NLsolve.nlsolve(df, pf.x0; nlsolve_kwargs...) - if !res.f_converged - @error( - "The powerflow solver NLSolve did not converge (returned convergence = $(res.f_converged))" - ) - end - return res.f_converged, res.zero -end function _update_V!(dx::Vector{Float64}, V::Vector{Complex{Float64}}, Vm::Vector{Float64}, Va::Vector{Float64}, @@ -368,33 +342,6 @@ function _update_dSbus_dV!(rows::Vector{Int64}, cols::Vector{Int64}, return end -# this function is for testing purposes only -function _legacy_dSbus_dV( - V::Vector{Complex{Float64}}, - Ybus::SparseMatrixCSC{Complex{Float64}, Int64}, -) - diagV = LinearAlgebra.Diagonal(V) - diagVnorm = LinearAlgebra.Diagonal(V ./ abs.(V)) - diagIbus = LinearAlgebra.Diagonal(Ybus * V) - dSbus_dVm = diagV * conj.(Ybus * diagVnorm) + conj.(diagIbus) * diagVnorm - dSbus_dVa = 1im * diagV * conj.(diagIbus - Ybus * diagV) - return dSbus_dVa, dSbus_dVm -end - -# this function is for testing purposes only -function _legacy_J( - dSbus_dVa::SparseMatrixCSC{Complex{Float64}, Int64}, - dSbus_dVm::SparseMatrixCSC{Complex{Float64}, Int64}, - pvpq::Vector{Int64}, - pq::Vector{Int64}, -) - j11 = real(dSbus_dVa[pvpq, pvpq]) - j12 = real(dSbus_dVm[pvpq, pq]) - j21 = imag(dSbus_dVa[pq, pvpq]) - j22 = imag(dSbus_dVm[pq, pq]) - J = sparse([j11 j12; j21 j22]) - return J -end function _update_submatrix!( A::SparseMatrixCSC, @@ -458,6 +405,12 @@ function _calc_x( return x end +function _preallocate_J(rows::Vector{Int64}, cols::Vector{Int64}, pvpq::Vector{Int64}, pq::Vector{Int64}) + J_block = sparse(rows, cols, Float64(0), maximum(rows), maximum(cols)) + J = [J_block[pvpq, pvpq] J_block[pvpq, pq]; J_block[pq, pvpq] J_block[pq, pq]] + return J +end + function _newton_powerflow( pf::ACPowerFlow{KLUACPowerFlow}, data::ACPowerFlowData; @@ -558,8 +511,7 @@ function _newton_powerflow( # pq_rows = pq_lookup[rows][pq_lookup[rows] .!= 0] # pq_cols = pq_lookup[cols][pq_lookup[cols] .!= 0] - J_block = sparse(rows, cols, Float64(0), maximum(rows), maximum(cols)) - J = [J_block[pvpq, pvpq] J_block[pvpq, pq]; J_block[pq, pvpq] J_block[pq, pq]] + J = _preallocate_J(rows, cols, pvpq, pq) # preallocate the KLU factorization object - symbolic object only colptr = KLU.decrement(J.colptr) @@ -601,25 +553,35 @@ function _newton_powerflow( KLU.klu_l_factor(colptr, rowval, J.nzval, factor_J._symbolic, rf) end - # factorize the numeric object of KLU inplace, while reusing the symbolic object - KLU.klu_l_refactor( - colptr, - rowval, - J.nzval, - factor_J._symbolic, - factor_J._numeric, - rf, - ) + try + # factorize the numeric object of KLU inplace, while reusing the symbolic object + KLU.klu_l_refactor( + colptr, + rowval, + J.nzval, + factor_J._symbolic, + factor_J._numeric, + rf, + ) + + # solve inplace - the results are written to F, so that we must use F instead of dx for updating V + KLU.klu_l_solve( + factor_J._symbolic, + factor_J._numeric, + size(F, 1), + size(F, 2), + F, + rf, + ) + + catch e + @error("KLU factorization failed: $e") + converged = false + x = missing + return (converged, x) + end - # solve inplace - the results are written to F, so that we must use F instead of dx for updating V - KLU.klu_l_solve( - factor_J._symbolic, - factor_J._numeric, - size(F, 1), - size(F, 2), - F, - rf, - ) + # KLU.solve! overwrites F with the solution instead of returning it as dx, so -F is used here to update V _update_V!(F, V, Vm, Va, pv, pq, dx_Va_pv, dx_Va_pq, dx_Vm_pq) @@ -640,87 +602,3 @@ function _newton_powerflow( return (converged, x) end - -# legacy NR implementation - here we do not care about allocations, we use this function only for testing purposes -function _newton_powerflow( - pf::ACPowerFlow{LUACPowerFlow}, - data::ACPowerFlowData; - time_step::Int64 = 1, - kwargs..., -) - # Fetch maxIter and tol from kwargs, or use defaults if not provided - maxIter = get(kwargs, :maxIter, DEFAULT_NR_MAX_ITER) - tol = get(kwargs, :tol, DEFAULT_NR_TOL) - i = 0 - - Ybus = data.power_network_matrix.data - - # Find indices for each bus type - #ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) - pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) - pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) - pvpq = [pv; pq] - - #nref = length(ref) - npv = length(pv) - npq = length(pq) - npvpq = npv + npq - n_buses = length(data.bus_type) - - Vm = data.bus_magnitude[:, time_step] - # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: - Vm[pq] .= clamp.(Vm[pq], 0.9, 1.1) - Va = data.bus_angles[:, time_step] - V = zeros(Complex{Float64}, length(Vm)) - V .= Vm .* exp.(1im .* Va) - - # pre-allocate dx - dx = zeros(Float64, npv + 2 * npq) - - Ybus = data.power_network_matrix.data - - Sbus = - data.bus_activepower_injection[:, time_step] - - data.bus_activepower_withdrawals[:, time_step] + - 1im * ( - data.bus_reactivepower_injection[:, time_step] - - data.bus_reactivepower_withdrawals[:, time_step] - ) - - mis = V .* conj.(Ybus * V) .- Sbus - F = [real(mis[pvpq]); imag(mis[pq])] - - converged = npvpq == 0 - - while i < maxIter && !converged - i += 1 - dSbus_dVa, dSbus_dVm = _legacy_dSbus_dV(V, Ybus) - J = _legacy_J(dSbus_dVa, dSbus_dVm, pvpq, pq) - - # using a different factorization that KLU for testing - factor_J = LinearAlgebra.lu(J) - dx .= factor_J \ F - - Va[pv] .-= dx[1:npv] - Va[pq] .-= dx[(npv + 1):(npv + npq)] - Vm[pq] .-= dx[(npv + npq + 1):(npv + 2 * npq)] - V .= Vm .* exp.(1im .* Va) - Vm .= abs.(V) - Va .= angle.(V) - - mis = V .* conj.(Ybus * V) .- Sbus - F .= [real(mis[pvpq]); imag(mis[pq])] - - converged = LinearAlgebra.norm(F, Inf) < tol - end - - if !converged - @error("The powerflow solver with KLU did not converge after $i iterations") - else - @info("The powerflow solver with KLU converged after $i iterations") - end - - x = _calc_x(data, V, Va, Vm, Ybus, n_buses) - - return (converged, x) -end diff --git a/src/nlsolve_powerflow.jl b/src/nlsolve_powerflow.jl new file mode 100644 index 00000000..7d58e848 --- /dev/null +++ b/src/nlsolve_powerflow.jl @@ -0,0 +1,27 @@ + +function _newton_powerflow( + pf::ACPowerFlow{NLSolveACPowerFlow}, + data::ACPowerFlowData; + time_step::Int64 = 1, # not implemented for NLSolve and not used + nlsolve_kwargs..., +) + if time_step != 1 + throw( + ArgumentError( + "Multiperiod power flow not implemented for NLSolve AC power flow", + ), + ) + end + + pf = PolarPowerFlow(data) + J = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) + + df = NLsolve.OnceDifferentiable(pf, J, pf.x0, pf.residual, J.Jv) + res = NLsolve.nlsolve(df, pf.x0; nlsolve_kwargs...) + if !res.f_converged + @error( + "The powerflow solver NLSolve did not converge (returned convergence = $(res.f_converged))" + ) + end + return res.f_converged, res.zero +end \ No newline at end of file diff --git a/src/powerflow_types.jl b/src/powerflow_types.jl index 60d08c18..5f69023e 100644 --- a/src/powerflow_types.jl +++ b/src/powerflow_types.jl @@ -3,7 +3,6 @@ abstract type ACPowerFlowSolverType end struct KLUACPowerFlow <: ACPowerFlowSolverType end struct NLSolveACPowerFlow <: ACPowerFlowSolverType end -struct LUACPowerFlow <: ACPowerFlowSolverType end # Only for testing, a basic implementation using LinearAlgebra.lu, allocates a lot of memory Base.@kwdef struct ACPowerFlow{ACSolver <: ACPowerFlowSolverType} <: PowerFlowEvaluationModel diff --git a/test/runtests.jl b/test/runtests.jl index f798c10e..590459cf 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -35,6 +35,9 @@ MAIN_DIR = dirname(@__DIR__) include("test_utils/common.jl") include("test_utils/psse_results_compare.jl") +#include("test_utils/legacy_pf.jl") +#Base.eval(PF, Meta.parse("include(\"test/test_utils/legacy_pf.jl\")")) +Base.eval(PowerFlows, :(include("./test_utils/legacy_pf.jl"))) LOG_FILE = "power-systems.log" diff --git a/test/test_newton_ac_powerflow.jl b/test/test_newton_ac_powerflow.jl index dbc4912c..87f9ab44 100644 --- a/test/test_newton_ac_powerflow.jl +++ b/test/test_newton_ac_powerflow.jl @@ -1,8 +1,11 @@ +include("test_utils/legacy_pf.jl") + + @testset "AC Power Flow 14-Bus testing" for ACSolver in ( NLSolveACPowerFlow, KLUACPowerFlow, - LUACPowerFlow, + PowerFlows.LUACPowerFlow, ) result_14 = [ 2.3255081760423684 @@ -55,7 +58,7 @@ end ( NLSolveACPowerFlow, KLUACPowerFlow, - LUACPowerFlow, + PowerFlows.LUACPowerFlow, ) sys = PSB.build_system(PSB.PSITestSystems, "c_sys14"; add_forecasts = false) pf = ACPowerFlow{ACSolver}() @@ -86,7 +89,7 @@ end @testset "AC Power Flow 3-Bus Fixed FixedAdmittance testing" for ACSolver in ( NLSolveACPowerFlow, - KLUACPowerFlow, LUACPowerFlow, + KLUACPowerFlow, PowerFlows.LUACPowerFlow, ) p_gen_matpower_3bus = [20.3512373930753, 100.0, 100.0] q_gen_matpower_3bus = [45.516916781567232, 10.453799727283879, -31.992561631394636] @@ -104,7 +107,7 @@ end ( NLSolveACPowerFlow, KLUACPowerFlow, - LUACPowerFlow, + PowerFlows.LUACPowerFlow, ) pf_sys5_re = PSB.build_system(PSB.PSITestSystems, "c_sys5_re"; add_forecasts = false) remove_component!(Line, pf_sys5_re, "1") @@ -127,7 +130,7 @@ end ( NLSolveACPowerFlow, KLUACPowerFlow, - LUACPowerFlow, + PowerFlows.LUACPowerFlow, ) file = joinpath( TEST_FILES_DIR, @@ -167,7 +170,7 @@ end ( NLSolveACPowerFlow, KLUACPowerFlow, - LUACPowerFlow, + PowerFlows.LUACPowerFlow, ) sys = System(100.0) b = ACBus(; @@ -216,7 +219,7 @@ end ( NLSolveACPowerFlow, KLUACPowerFlow, - LUACPowerFlow, + PowerFlows.LUACPowerFlow, ) sys = System(100.0) b1 = ACBus(; @@ -303,7 +306,7 @@ end ( NLSolveACPowerFlow, KLUACPowerFlow, - LUACPowerFlow, + PowerFlows.LUACPowerFlow, ) sys = System(100.0) b = ACBus(; @@ -369,7 +372,7 @@ end ( NLSolveACPowerFlow, KLUACPowerFlow, - LUACPowerFlow, + PowerFlows.LUACPowerFlow, ) sys = System(100.0) b1 = ACBus(; @@ -594,7 +597,7 @@ end ) # test against legacy implementation - pf_legacy = ACPowerFlow(LUACPowerFlow) + pf_legacy = ACPowerFlow(PowerFlows.LUACPowerFlow) res_legacy = solve_powerflow(pf_legacy, sys) @test all( diff --git a/test/test_utils/legacy_pf.jl b/test/test_utils/legacy_pf.jl new file mode 100644 index 00000000..e65d1f82 --- /dev/null +++ b/test/test_utils/legacy_pf.jl @@ -0,0 +1,118 @@ + +import PowerFlows + +struct LUACPowerFlow <: ACPowerFlowSolverType end # Only for testing, a basic implementation using LinearAlgebra.lu, allocates a lot of memory + + +# this function is for testing purposes only +function _legacy_dSbus_dV( + V::Vector{Complex{Float64}}, + Ybus::SparseMatrixCSC{Complex{Float64}, Int64}, +) + diagV = LinearAlgebra.Diagonal(V) + diagVnorm = LinearAlgebra.Diagonal(V ./ abs.(V)) + diagIbus = LinearAlgebra.Diagonal(Ybus * V) + dSbus_dVm = diagV * conj.(Ybus * diagVnorm) + conj.(diagIbus) * diagVnorm + dSbus_dVa = 1im * diagV * conj.(diagIbus - Ybus * diagV) + return dSbus_dVa, dSbus_dVm +end + +# this function is for testing purposes only +function _legacy_J( + dSbus_dVa::SparseMatrixCSC{Complex{Float64}, Int64}, + dSbus_dVm::SparseMatrixCSC{Complex{Float64}, Int64}, + pvpq::Vector{Int64}, + pq::Vector{Int64}, +) + j11 = real(dSbus_dVa[pvpq, pvpq]) + j12 = real(dSbus_dVm[pvpq, pq]) + j21 = imag(dSbus_dVa[pq, pvpq]) + j22 = imag(dSbus_dVm[pq, pq]) + J = sparse([j11 j12; j21 j22]) + return J +end + + +# legacy NR implementation - here we do not care about allocations, we use this function only for testing purposes +function _newton_powerflow( + pf::ACPowerFlow{PowerFlows.LUACPowerFlow}, + data::PowerFlows.ACPowerFlowData; + time_step::Int64 = 1, + kwargs..., +) + # Fetch maxIter and tol from kwargs, or use defaults if not provided + maxIter = get(kwargs, :maxIter, DEFAULT_NR_MAX_ITER) + tol = get(kwargs, :tol, DEFAULT_NR_TOL) + i = 0 + + Ybus = data.power_network_matrix.data + + # Find indices for each bus type + #ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) + pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) + pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) + pvpq = [pv; pq] + + #nref = length(ref) + npv = length(pv) + npq = length(pq) + npvpq = npv + npq + n_buses = length(data.bus_type) + + Vm = data.bus_magnitude[:, time_step] + # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: + Vm[pq] .= clamp.(Vm[pq], 0.9, 1.1) + Va = data.bus_angles[:, time_step] + V = zeros(Complex{Float64}, length(Vm)) + V .= Vm .* exp.(1im .* Va) + + # pre-allocate dx + dx = zeros(Float64, npv + 2 * npq) + + Ybus = data.power_network_matrix.data + + Sbus = + data.bus_activepower_injection[:, time_step] - + data.bus_activepower_withdrawals[:, time_step] + + 1im * ( + data.bus_reactivepower_injection[:, time_step] - + data.bus_reactivepower_withdrawals[:, time_step] + ) + + mis = V .* conj.(Ybus * V) .- Sbus + F = [real(mis[pvpq]); imag(mis[pq])] + + converged = npvpq == 0 + + while i < maxIter && !converged + i += 1 + dSbus_dVa, dSbus_dVm = _legacy_dSbus_dV(V, Ybus) + J = _legacy_J(dSbus_dVa, dSbus_dVm, pvpq, pq) + + # using a different factorization that KLU for testing + factor_J = LinearAlgebra.lu(J) + dx .= factor_J \ F + + Va[pv] .-= dx[1:npv] + Va[pq] .-= dx[(npv + 1):(npv + npq)] + Vm[pq] .-= dx[(npv + npq + 1):(npv + 2 * npq)] + V .= Vm .* exp.(1im .* Va) + Vm .= abs.(V) + Va .= angle.(V) + + mis = V .* conj.(Ybus * V) .- Sbus + F .= [real(mis[pvpq]); imag(mis[pq])] + + converged = LinearAlgebra.norm(F, Inf) < tol + end + + if !converged + @error("The powerflow solver with KLU did not converge after $i iterations") + else + @info("The powerflow solver with KLU converged after $i iterations") + end + + x = _calc_x(data, V, Va, Vm, Ybus, n_buses) + + return (converged, x) +end From d0672753e7bca348e21ec4b67e0268596ddb1ea6 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Mon, 13 Jan 2025 11:52:08 -0700 Subject: [PATCH 19/23] wip: multiperiod ac pf --- src/newton_ac_powerflow.jl | 99 ++++++++++++++++----------- src/nlsolve_powerflow.jl | 39 ++++++++++- test/test_multiperiod_ac_powerflow.jl | 18 ++--- test/test_newton_ac_powerflow.jl | 8 +-- test/test_utils/legacy_pf.jl | 8 +-- 5 files changed, 111 insertions(+), 61 deletions(-) diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index 731795a0..5285f16d 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -46,7 +46,8 @@ function solve_powerflow!( check_connectivity = get(kwargs, :check_connectivity, true), ) - converged, x = _ac_powereflow(data, pf, system; kwargs...) + converged, V, Sbus_result = _ac_powereflow(data, pf, system; kwargs...) + x = _calc_x(data, V, Sbus_result) if converged write_powerflow_solution!(system, x, get(kwargs, :maxIter, DEFAULT_NR_MAX_ITER)) @@ -88,9 +89,9 @@ function solve_powerflow( system; check_connectivity = get(kwargs, :check_connectivity, true), ) - # @error(typeof(data)) - converged, x = _ac_powereflow(data, pf, system; kwargs...) + converged, V, Sbus_result = _ac_powereflow(data, pf, system; kwargs...) + x = _calc_x(data, V, Sbus_result) if converged @info("PowerFlow solve converged, the results are exported in DataFrames") @@ -121,12 +122,25 @@ function solve_powerflow( sorted_time_steps = sort(collect(keys(data.timestep_map))) # preallocate results ts_converged = zeros(Bool, 1, length(sorted_time_steps)) - ts_x = zeros(Float64, 2 * length(data.bus_type), length(sorted_time_steps)) + ts_V = zeros(Complex{Float64}, length(data.bus_type), length(sorted_time_steps)) + ts_S = zeros(Complex{Float64}, length(data.bus_type), length(sorted_time_steps)) + + results = Dict() for t in sorted_time_steps - converged, x = _ac_powereflow(data, pf, system; time_step = t, kwargs...) + converged, V, Sbus_result = + _ac_powereflow(data, pf, system; time_step = t, kwargs...) ts_converged[1, t] = converged - ts_x[:, t] .= x + ts_V[:, t] .= V + ts_S[:, t] .= Sbus_result + + # temporary implementation that will need to be improved: + if converged + x = _calc_x(data, V, Sbus_result) + results[data.timestep_map[t]] = write_results(pf, system, data, x) + else + results[data.timestep_map[t]] = missing + end #todo: implement write_results for multiperiod power flow @@ -145,7 +159,7 @@ function solve_powerflow( # todo: # return df_results - return ts_converged, ts_x + return results end function _ac_powereflow( @@ -158,22 +172,27 @@ function _ac_powereflow( check_reactive_power_limits = get(kwargs, :check_reactive_power_limits, false) for _ in 1:MAX_REACTIVE_POWER_ITERATIONS - converged, x = _newton_powerflow(pf, data; time_step = time_step, kwargs...) - if !converged || !check_reactive_power_limits || _check_q_limit_bounds!(data, x) - return converged, x + converged, V, Sbus_result = + _newton_powerflow(pf, data; time_step = time_step, kwargs...) + if !converged || !check_reactive_power_limits || + _check_q_limit_bounds!(data, Sbus_result) + return converged, V, Sbus_result end end @error("could not enforce reactive power limits after $MAX_REACTIVE_POWER_ITERATIONS") - return converged, x + return converged, V end -function _check_q_limit_bounds!(data::ACPowerFlowData, zero::Vector{Float64}) +function _check_q_limit_bounds!( + data::ACPowerFlowData, + Sbus_result::Vector{Complex{Float64}}, +) bus_names = data.power_network_matrix.axes[1] within_limits = true for (ix, b) in enumerate(data.bus_type) if b == PSY.ACBusTypes.PV - Q_gen = zero[2 * ix - 1] + Q_gen = imag(Sbus_result[ix]) else continue end @@ -199,20 +218,20 @@ function _solve_powerflow!( pf::ACPowerFlow{<:ACPowerFlowSolverType}, data::ACPowerFlowData, check_reactive_power_limits; - nlsolve_kwargs..., + kwargs..., ) for _ in 1:MAX_REACTIVE_POWER_ITERATIONS - converged, x = _newton_powerflow(pf, data; nlsolve_kwargs...) - if !converged || !check_reactive_power_limits || _check_q_limit_bounds!(data, x) - return converged, x + converged, V, Sbus_result = _newton_powerflow(pf, data; kwargs...) + if !converged || !check_reactive_power_limits || + _check_q_limit_bounds!(data, Sbus_result) + return converged, V, Sbus_result end end # todo: throw error? set converged to false? @error("could not enforce reactive power limits after $MAX_REACTIVE_POWER_ITERATIONS") - return converged, x + return converged, V, Sbus_result end - function _update_V!(dx::Vector{Float64}, V::Vector{Complex{Float64}}, Vm::Vector{Float64}, Va::Vector{Float64}, pv::Vector{Int64}, pq::Vector{Int64}, dx_Va_pv::Vector{Int64}, dx_Va_pq::Vector{Int64}, @@ -236,15 +255,17 @@ function _update_V!(dx::Vector{Float64}, V::Vector{Complex{Float64}}, Vm::Vector return end -function _update_F!(F::Vector{Float64}, mis::Vector{Complex{Float64}}, +function _update_F!(F::Vector{Float64}, Sbus_result::Vector{Complex{Float64}}, + mis::Vector{Complex{Float64}}, dx_Va_pv::Vector{Int64}, dx_Va_pq::Vector{Int64}, dx_Vm_pq::Vector{Int64}, V::Vector{Complex{Float64}}, Ybus::SparseMatrixCSC{Complex{Float64}, Int64}, Sbus::Vector{Complex{Float64}}, pv::Vector{Int64}, pq::Vector{Int64}) #mis .= V .* conj.(Ybus * V) .- Sbus - LinearAlgebra.mul!(mis, Ybus, V) - mis .= V .* conj.(mis) .- Sbus + LinearAlgebra.mul!(Sbus_result, Ybus, V) + Sbus_result .= V .* conj(Sbus_result) + mis .= Sbus_result .- Sbus for (i, j) in zip(dx_Va_pv, pv) F[i] = real(mis[j]) @@ -342,7 +363,6 @@ function _update_dSbus_dV!(rows::Vector{Int64}, cols::Vector{Int64}, return end - function _update_submatrix!( A::SparseMatrixCSC, B::SparseMatrixCSC, @@ -379,14 +399,13 @@ end function _calc_x( data::ACPowerFlowData, V::Vector{Complex{Float64}}, - Va::Vector{Float64}, - Vm::Vector{Float64}, - Ybus::SparseMatrixCSC{Complex{Float64}, Int64}, - n_buses::Int64, + Sbus_result::Vector{Complex{Float64}}, ) + Vm = abs.(V) + Va = angle.(V) + n_buses = length(V) # mock the expected x format, where the values depend on the type of the bus: x = zeros(Float64, 2 * n_buses) - Sbus_result = V .* conj(Ybus * V) # todo preallocate and pass as parameter for (ix, b) in enumerate(data.bus_type) if b == PSY.ACBusTypes.REF # When bustype == REFERENCE PSY.Bus, state variables are Active and Reactive Power Generated @@ -405,7 +424,12 @@ function _calc_x( return x end -function _preallocate_J(rows::Vector{Int64}, cols::Vector{Int64}, pvpq::Vector{Int64}, pq::Vector{Int64}) +function _preallocate_J( + rows::Vector{Int64}, + cols::Vector{Int64}, + pvpq::Vector{Int64}, + pq::Vector{Int64}, +) J_block = sparse(rows, cols, Float64(0), maximum(rows), maximum(cols)) J = [J_block[pvpq, pvpq] J_block[pvpq, pq]; J_block[pq, pvpq] J_block[pq, pq]] return J @@ -447,8 +471,8 @@ function _newton_powerflow( converged = npvpq == 0 if converged # if only ref buses present, we do not need to enter the power flow loop - x = _calc_x(data, V, Va, Vm, Ybus, n_buses) - return (converged, x) + Sbus_result = V .* conj(Ybus * V) + return (converged, V, Sbus_result) end # we need to define lookups for mappings of pv, pq buses onto the internal J indexing @@ -476,6 +500,7 @@ function _newton_powerflow( # Pre-allocate mis and F and create views for the respective real and imaginary sections of the arrays: mis = zeros(Complex{Float64}, length(V)) + Sbus_result = zeros(Complex{Float64}, length(V)) F = zeros(Float64, npvpq + npq) mis .= V .* conj.(Ybus * V) .- Sbus F .= [real(mis[pvpq]); imag(mis[pq])] @@ -576,18 +601,14 @@ function _newton_powerflow( catch e @error("KLU factorization failed: $e") - converged = false - x = missing - return (converged, x) + return (converged, V) end - - # KLU.solve! overwrites F with the solution instead of returning it as dx, so -F is used here to update V _update_V!(F, V, Vm, Va, pv, pq, dx_Va_pv, dx_Va_pq, dx_Vm_pq) # here F is mismatch again - _update_F!(F, mis, dx_Va_pv, dx_Va_pq, dx_Vm_pq, V, Ybus, Sbus, pv, pq) + _update_F!(F, Sbus_result, mis, dx_Va_pv, dx_Va_pq, dx_Vm_pq, V, Ybus, Sbus, pv, pq) converged = LinearAlgebra.norm(F, Inf) < tol end @@ -598,7 +619,5 @@ function _newton_powerflow( @info("The powerflow solver with KLU converged after $i iterations") end - x = _calc_x(data, V, Va, Vm, Ybus, n_buses) - - return (converged, x) + return (converged, V, Sbus_result) end diff --git a/src/nlsolve_powerflow.jl b/src/nlsolve_powerflow.jl index 7d58e848..b4537780 100644 --- a/src/nlsolve_powerflow.jl +++ b/src/nlsolve_powerflow.jl @@ -23,5 +23,40 @@ function _newton_powerflow( "The powerflow solver NLSolve did not converge (returned convergence = $(res.f_converged))" ) end - return res.f_converged, res.zero -end \ No newline at end of file + V = _calc_V(data, res.zero; time_step = time_step) + Sbus_result = V .* conj(data.power_network_matrix.data * V) + return (res.f_converged, V, Sbus_result) +end + +function _calc_V( + data::ACPowerFlowData, + x::Vector{Float64}; + time_step::Int64 = 1, +) + n_buses = length(x) ÷ 2 # Since x has 2 elements per bus (real and imaginary) + V = zeros(Complex{Float64}, n_buses) + Vm_data = data.bus_magnitude[:, time_step] + Va_data = data.bus_angles[:, time_step] + + # Extract values for Vm and Va from x + for (ix, b) in enumerate(data.bus_type) + if b == PSY.ACBusTypes.REF + # For REF bus, we have active and reactive power + Vm = Vm_data[ix] + Va = Va_data[ix] + V[ix] = Vm * exp(im * Va) + elseif b == PSY.ACBusTypes.PV + # For PV bus, we have reactive power and voltage angle + Vm = Vm_data[ix] + Va = x[2 * ix] + V[ix] = Vm * exp(im * Va) # Rebuild voltage from magnitude and angle + elseif b == PSY.ACBusTypes.PQ + # For PQ bus, we have voltage magnitude and voltage angle + Vm = x[2 * ix - 1] + Va = x[2 * ix] + V[ix] = Vm * exp(im * Va) # Rebuild voltage from magnitude and angle + end + end + + return V +end diff --git a/test/test_multiperiod_ac_powerflow.jl b/test/test_multiperiod_ac_powerflow.jl index 9a2f8add..dfef4188 100644 --- a/test/test_multiperiod_ac_powerflow.jl +++ b/test/test_multiperiod_ac_powerflow.jl @@ -38,16 +38,16 @@ data.bus_activepower_withdrawals .= deepcopy(withs) # get power flows with NR KLU method and write results - ts_converged, ts_x = solve_powerflow(pf, data, sys) - - # todo: implement write_results for multiperiod power flow - # todo: test the result values + results = solve_powerflow(pf, data, sys) # check results - # for i in 1:length(data.timestep_map) - # net_flow = results[data.timestep_map[i]]["flow_results"].P_from_to - # net_flow_tf = results[data.timestep_map[i]]["flow_results"].P_to_from - # @test isapprox(net_flow, flows[:, i], atol = 1e-5) - # @test isapprox(net_flow_tf, -flows[:, i], atol = 1e-5) + # for t in 1:length(data.timestep_map) + # res_t = solve_powerflow(pf, sys; time_step=t) # does not work - ts data not set in sys + # flow_ft = res_t["flow_results"].P_from_to + # flow_tf = res_t["flow_results"].P_to_from + # ts_flow_ft = results[data.timestep_map[t]]["flow_results"].P_from_to + # ts_flow_tf = results[data.timestep_map[t]]["flow_results"].P_to_from + # @test isapprox(ts_flow_ft, flow_ft, atol = 1e-9) + # @test isapprox(ts_flow_tf, flow_tf, atol = 1e-9) # end end diff --git a/test/test_newton_ac_powerflow.jl b/test/test_newton_ac_powerflow.jl index 87f9ab44..ff72e1a4 100644 --- a/test/test_newton_ac_powerflow.jl +++ b/test/test_newton_ac_powerflow.jl @@ -1,5 +1,3 @@ -include("test_utils/legacy_pf.jl") - @testset "AC Power Flow 14-Bus testing" for ACSolver in ( @@ -42,14 +40,16 @@ include("test_utils/legacy_pf.jl") pf = ACPowerFlow{ACSolver}() data = PowerFlows.PowerFlowData(pf, sys; check_connectivity = true) #Compare results between finite diff methods and Jacobian method - converged1, x1 = PowerFlows._solve_powerflow!(pf, data, false) + converged1, V1, S1 = PowerFlows._solve_powerflow!(pf, data, false) + x1 = PowerFlows._calc_x(data, V1, S1) @test LinearAlgebra.norm(result_14 - x1, Inf) <= 1e-6 @test solve_powerflow!(pf, sys; method = :newton) # Test enforcing the reactive power Limits set_reactive_power!(get_component(PowerLoad, sys, "Bus4"), 0.0) data = PowerFlows.PowerFlowData(pf, sys; check_connectivity = true) - converged2, x2 = PowerFlows._solve_powerflow!(pf, data, true) + converged2, V2, S2 = PowerFlows._solve_powerflow!(pf, data, true) + x2 = PowerFlows._calc_x(data, V2, S2) @test LinearAlgebra.norm(result_14 - x2, Inf) >= 1e-6 @test 1.08 <= x2[15] <= 1.09 end diff --git a/test/test_utils/legacy_pf.jl b/test/test_utils/legacy_pf.jl index e65d1f82..a6ba33df 100644 --- a/test/test_utils/legacy_pf.jl +++ b/test/test_utils/legacy_pf.jl @@ -3,7 +3,6 @@ import PowerFlows struct LUACPowerFlow <: ACPowerFlowSolverType end # Only for testing, a basic implementation using LinearAlgebra.lu, allocates a lot of memory - # this function is for testing purposes only function _legacy_dSbus_dV( V::Vector{Complex{Float64}}, @@ -32,7 +31,6 @@ function _legacy_J( return J end - # legacy NR implementation - here we do not care about allocations, we use this function only for testing purposes function _newton_powerflow( pf::ACPowerFlow{PowerFlows.LUACPowerFlow}, @@ -111,8 +109,6 @@ function _newton_powerflow( else @info("The powerflow solver with KLU converged after $i iterations") end - - x = _calc_x(data, V, Va, Vm, Ybus, n_buses) - - return (converged, x) + Sbus_result = V .* conj(Ybus * V) + return (converged, V, Sbus_result) end From d198c2e710300bec2ad1b843aa6b50455b8062e2 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Wed, 15 Jan 2025 09:30:51 -0700 Subject: [PATCH 20/23] wip: solve_powerflow! --- src/newton_ac_powerflow.jl | 82 ++++++++++++++++++++++++++++++++++---- src/post_processing.jl | 14 +++---- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index 5285f16d..bb3283cb 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -46,7 +46,7 @@ function solve_powerflow!( check_connectivity = get(kwargs, :check_connectivity, true), ) - converged, V, Sbus_result = _ac_powereflow(data, pf, system; kwargs...) + converged, V, Sbus_result = _ac_powereflow(data, pf; kwargs...) x = _calc_x(data, V, Sbus_result) if converged @@ -90,7 +90,7 @@ function solve_powerflow( check_connectivity = get(kwargs, :check_connectivity, true), ) - converged, V, Sbus_result = _ac_powereflow(data, pf, system; kwargs...) + converged, V, Sbus_result = _ac_powereflow(data, pf; kwargs...) x = _calc_x(data, V, Sbus_result) if converged @@ -129,7 +129,7 @@ function solve_powerflow( for t in sorted_time_steps converged, V, Sbus_result = - _ac_powereflow(data, pf, system; time_step = t, kwargs...) + _ac_powereflow(data, pf; time_step = t, kwargs...) ts_converged[1, t] = converged ts_V[:, t] .= V ts_S[:, t] .= Sbus_result @@ -162,10 +162,76 @@ function solve_powerflow( return results end + + +# Multiperiod power flow - work in progress +function solve_powerflow!( + data::ACPowerFlowData; + kwargs..., +) + pf = ACPowerFlow() # todo: somehow store in data which PF to use (see issue #50) + + sorted_time_steps = sort(collect(keys(data.timestep_map))) + # preallocate results + ts_converged = zeros(Bool, 1, length(sorted_time_steps)) + ts_V = zeros(Complex{Float64}, length(data.bus_type), length(sorted_time_steps)) + ts_S = zeros(Complex{Float64}, length(data.bus_type), length(sorted_time_steps)) + + Yft = data.power_network_matrix.data_ft + Ytf = data.power_network_matrix.data_tf + + fb = data.power_network_matrix.fb + tb = data.power_network_matrix.tb + + for t in sorted_time_steps + converged, V, Sbus_result = + _ac_powereflow(data, pf; time_step = t, kwargs...) + ts_converged[1, t] = converged + ts_V[:, t] .= V + ts_S[:, t] .= Sbus_result + + ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) + pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) + pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) + + # temporary implementation that will need to be improved: + if converged + # write results for REF + data.bus_activepower_injection[ref, t] .= real.(Sbus_result[ref]) .+ data.bus_activepower_withdrawals[ref, t] + data.bus_reactivepower_injection[ref, t] .= imag.(Sbus_result[ref]) .+ data.bus_reactivepower_withdrawals[ref, t] + # write Q results for PV + data.bus_reactivepower_injection[pv, t] .= imag.(Sbus_result[pv]) .+ data.bus_reactivepower_withdrawals[pv, t] + # results for PQ buses do not need to be updated -> already consistent with inputs + + # write bus bus_types + # todo + + # write voltage results + data.bus_magnitude[pq, t] .= abs.(V[pq]) + data.bus_angles[pq, t] .= angle.(V[pq]) + data.bus_angles[pv, t] .= angle.(V[pv]) + + + + else + # todo + 1+2 + end + end + + # write branch flows + data.branch_flow_values .= real.(ts_V[fb,:] .* conj.(Yft * ts_V)) + + # todo: + # return df_results + + return +end + + function _ac_powereflow( data::ACPowerFlowData, - pf::ACPowerFlow{<:ACPowerFlowSolverType}, - system::PSY.System; + pf::ACPowerFlow{<:ACPowerFlowSolverType}; time_step::Int64 = 1, kwargs..., ) @@ -176,12 +242,12 @@ function _ac_powereflow( _newton_powerflow(pf, data; time_step = time_step, kwargs...) if !converged || !check_reactive_power_limits || _check_q_limit_bounds!(data, Sbus_result) - return converged, V, Sbus_result + return (converged, V, Sbus_result) end end @error("could not enforce reactive power limits after $MAX_REACTIVE_POWER_ITERATIONS") - return converged, V + return (converged, V, Sbus_result) end function _check_q_limit_bounds!( @@ -601,7 +667,7 @@ function _newton_powerflow( catch e @error("KLU factorization failed: $e") - return (converged, V) + return (converged, V, Sbus_result) end # KLU.solve! overwrites F with the solution instead of returning it as dx, so -F is used here to update V diff --git a/src/post_processing.jl b/src/post_processing.jl index 8e2346e1..c734c154 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -699,25 +699,25 @@ if a new `PowerFlowData` is constructed from the resulting system it is the same See also `write_powerflow_solution!`. NOTE that this assumes that `data` was initialized from `sys` and then solved with no further modifications. """ -function update_system!(sys::PSY.System, data::PowerFlowData) +function update_system!(sys::PSY.System, data::PowerFlowData; time_step=1) for bus in PSY.get_components(PSY.Bus, sys) if bus.bustype == PSY.ACBusTypes.REF # For REF bus, voltage and angle are fixed; update active and reactive - P_gen = data.bus_activepower_injection[data.bus_lookup[PSY.get_number(bus)]] - Q_gen = data.bus_reactivepower_injection[data.bus_lookup[PSY.get_number(bus)]] + P_gen = data.bus_activepower_injection[data.bus_lookup[PSY.get_number(bus)], time_step] + Q_gen = data.bus_reactivepower_injection[data.bus_lookup[PSY.get_number(bus)], time_step] _power_redistribution_ref(sys, P_gen, Q_gen, bus, DEFAULT_MAX_REDISTRIBUTION_ITERATIONS) elseif bus.bustype == PSY.ACBusTypes.PV # For PV bus, active and voltage are fixed; update reactive and angle - Q_gen = data.bus_reactivepower_injection[data.bus_lookup[PSY.get_number(bus)]] + Q_gen = data.bus_reactivepower_injection[data.bus_lookup[PSY.get_number(bus)], time_step] _reactive_power_redistribution_pv(sys, Q_gen, bus, DEFAULT_MAX_REDISTRIBUTION_ITERATIONS) - PSY.set_angle!(bus, data.bus_angles[data.bus_lookup[PSY.get_number(bus)]]) + PSY.set_angle!(bus, data.bus_angles[data.bus_lookup[PSY.get_number(bus)], time_step]) elseif bus.bustype == PSY.ACBusTypes.PQ # For PQ bus, active and reactive are fixed; update voltage and angle - Vm = data.bus_magnitude[data.bus_lookup[PSY.get_number(bus)]] + Vm = data.bus_magnitude[data.bus_lookup[PSY.get_number(bus)], time_step] PSY.set_magnitude!(bus, Vm) - PSY.set_angle!(bus, data.bus_angles[data.bus_lookup[PSY.get_number(bus)]]) + PSY.set_angle!(bus, data.bus_angles[data.bus_lookup[PSY.get_number(bus)], time_step]) end end end From 994123be4390fbacd255e21235da4715a5a15bd5 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Wed, 15 Jan 2025 16:46:41 -0700 Subject: [PATCH 21/23] integrate previous changes to powersystemdata --- src/ac_power_flow.jl | 7 ++-- src/common.jl | 2 +- src/newton_ac_powerflow.jl | 55 +++++++++++++++++--------------- src/nlsolve_powerflow.jl | 6 ++-- src/post_processing.jl | 22 +++++++------ test/test_newton_ac_powerflow.jl | 16 ++++++---- test/test_utils/legacy_pf.jl | 4 +-- 7 files changed, 61 insertions(+), 51 deletions(-) diff --git a/src/ac_power_flow.jl b/src/ac_power_flow.jl index 617683cf..6daf57c7 100644 --- a/src/ac_power_flow.jl +++ b/src/ac_power_flow.jl @@ -55,16 +55,15 @@ function _calculate_x0(n::Int, return x0 end -function PolarPowerFlow(data::ACPowerFlowData) - time_step = 1 # TODO placeholder time_step +function PolarPowerFlow(data::ACPowerFlowData; time_step::Int64 = 1) n_buses = first(size(data.bus_type)) P_net = zeros(n_buses) Q_net = zeros(n_buses) for ix in 1:n_buses P_net[ix] = - data.bus_activepower_injection[ix] - data.bus_activepower_withdrawals[ix] + data.bus_activepower_injection[ix, time_step] - data.bus_activepower_withdrawals[ix, time_step] Q_net[ix] = - data.bus_reactivepower_injection[ix] - data.bus_reactivepower_withdrawals[ix] + data.bus_reactivepower_injection[ix, time_step] - data.bus_reactivepower_withdrawals[ix, time_step] end x0 = _calculate_x0(1, data.bus_type[:, time_step], diff --git a/src/common.jl b/src/common.jl index 28a095ec..cd2d1fb3 100644 --- a/src/common.jl +++ b/src/common.jl @@ -199,7 +199,7 @@ function make_powerflowdata( bus_reactivepower_injection_1 = zeros_bus_time() bus_activepower_withdrawals_1 = zeros_bus_time() bus_reactivepower_withdrawals_1 = zeros_bus_time() - bus_reactivepower_bounds_1 = Matrix{Vector{Float64}}(undef, n_buses, time_stepsm) + bus_reactivepower_bounds_1 = Matrix{Vector{Float64}}(undef, n_buses, time_steps) bus_magnitude_1 = ones_bus_time() bus_angles_1 = zeros_bus_time() diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index 1c2a1156..f2eaefc5 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -1,4 +1,3 @@ -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 @@ -34,6 +33,7 @@ solve_ac_powerflow!(sys, method=:newton) function solve_powerflow!( pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; + time_step::Int64=1, kwargs..., ) #Save per-unit flag @@ -47,8 +47,7 @@ function solve_powerflow!( check_connectivity = get(kwargs, :check_connectivity, true), ) - solver_kwargs = filter(p -> !(p.first in _SOLVE_AC_POWERFLOW_KWARGS), kwargs) - converged, V, Sbus_result = _ac_powereflow(data, pf; solver_kwargs...) + converged, V, Sbus_result = _ac_powereflow(data, pf; time_step=time_step, kwargs...) x = _calc_x(data, V, Sbus_result) if converged @@ -124,8 +123,8 @@ function solve_powerflow( sorted_time_steps = sort(collect(keys(data.timestep_map))) # preallocate results ts_converged = zeros(Bool, 1, length(sorted_time_steps)) - ts_V = zeros(Complex{Float64}, length(data.bus_type), length(sorted_time_steps)) - ts_S = zeros(Complex{Float64}, length(data.bus_type), length(sorted_time_steps)) + ts_V = zeros(Complex{Float64}, length(data.bus_type[:, 1]), length(sorted_time_steps)) + ts_S = zeros(Complex{Float64}, length(data.bus_type[:, 1]), length(sorted_time_steps)) results = Dict() @@ -176,8 +175,8 @@ function solve_powerflow!( sorted_time_steps = sort(collect(keys(data.timestep_map))) # preallocate results ts_converged = zeros(Bool, 1, length(sorted_time_steps)) - ts_V = zeros(Complex{Float64}, length(data.bus_type), length(sorted_time_steps)) - ts_S = zeros(Complex{Float64}, length(data.bus_type), length(sorted_time_steps)) + ts_V = zeros(Complex{Float64}, length(data.bus_type[:, 1]), length(sorted_time_steps)) + ts_S = zeros(Complex{Float64}, length(data.bus_type[:, 1]), length(sorted_time_steps)) Yft = data.power_network_matrix.data_ft Ytf = data.power_network_matrix.data_tf @@ -192,9 +191,9 @@ function solve_powerflow!( ts_V[:, t] .= V ts_S[:, t] .= Sbus_result - ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) - pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) - pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) + ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type[:, t]) + pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type[:, t]) + pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type[:, t]) # temporary implementation that will need to be improved: if converged @@ -222,7 +221,13 @@ function solve_powerflow!( end # write branch flows - data.branch_flow_values .= real.(ts_V[fb,:] .* conj.(Yft * ts_V)) + Sft = ts_V[fb,:] .* conj.(Yft * ts_V) + Stf = ts_V[tb,:] .* conj.(Ytf * ts_V) + + data.branch_activepower_flow_from_to .= real.(Sft) + data.branch_reactivepower_flow_from_to .= imag.(Sft) + data.branch_activepower_flow_to_from .= real.(Stf) + data.branch_reactivepower_flow_to_from .= imag.(Stf) # todo: # return df_results @@ -243,7 +248,7 @@ function _ac_powereflow( converged, V, Sbus_result = _newton_powerflow(pf, data; time_step = time_step, kwargs...) if !converged || !check_reactive_power_limits || - _check_q_limit_bounds!(data, Sbus_result) + _check_q_limit_bounds!(data, Sbus_result, time_step) return (converged, V, Sbus_result) end end @@ -259,8 +264,8 @@ function _check_q_limit_bounds!( ) bus_names = data.power_network_matrix.axes[1] within_limits = true - for (ix, b) in enumerate(data.bus_type) - if b == PSY.ACBusTypes.PV + for (ix, bt) in enumerate(data.bus_type[:, time_step]) + if bt == PSY.ACBusTypes.PV Q_gen = imag(Sbus_result[ix]) else continue @@ -271,10 +276,10 @@ function _check_q_limit_bounds!( within_limits = false data.bus_type[ix, time_step] = PSY.ACBusTypes.PQ data.bus_reactivepower_injection[ix, time_step] = data.bus_reactivepower_bounds[ix, time_step][1] - elseif Q_gen >= data.bus_reactivepower_bounds[ix][2] + elseif Q_gen >= data.bus_reactivepower_bounds[ix, time_step][2] @info "Bus $(bus_names[ix]) changed to PSY.ACBusTypes.PQ" within_limits = false - data.bus_type[ixm, time_step] = PSY.ACBusTypes.PQ + data.bus_type[ix, time_step] = PSY.ACBusTypes.PQ data.bus_reactivepower_injection[ix, time_step] = data.bus_reactivepower_bounds[ix, time_step][2] else @debug "Within Limits" @@ -469,23 +474,24 @@ end function _calc_x( data::ACPowerFlowData, V::Vector{Complex{Float64}}, - Sbus_result::Vector{Complex{Float64}}, + Sbus_result::Vector{Complex{Float64}}; + time_step::Int64 = 1, ) Vm = abs.(V) Va = angle.(V) n_buses = length(V) # mock the expected x format, where the values depend on the type of the bus: x = zeros(Float64, 2 * n_buses) - for (ix, b) in enumerate(data.bus_type) - if b == PSY.ACBusTypes.REF + for (ix, bt) in enumerate(data.bus_type[:, time_step]) + if bt == PSY.ACBusTypes.REF # When bustype == REFERENCE PSY.Bus, state variables are Active and Reactive Power Generated x[2 * ix - 1] = real(Sbus_result[ix]) + data.bus_activepower_withdrawals[ix] x[2 * ix] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] - elseif b == PSY.ACBusTypes.PV + elseif bt == PSY.ACBusTypes.PV # When bustype == PV PSY.Bus, state variables are Reactive Power Generated and Voltage Angle x[2 * ix - 1] = imag(Sbus_result[ix]) + data.bus_reactivepower_withdrawals[ix] x[2 * ix] = Va[ix] - elseif b == PSY.ACBusTypes.PQ + elseif bt == PSY.ACBusTypes.PQ # When bustype == PQ PSY.Bus, state variables are Voltage Magnitude and Voltage Angle x[2 * ix - 1] = Vm[ix] x[2 * ix] = Va[ix] @@ -519,16 +525,15 @@ function _newton_powerflow( Ybus = data.power_network_matrix.data # Find indices for each bus type - ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) - pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) - pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) + ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type[:, time_step]) + pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type[:, time_step]) + pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type[:, time_step]) pvpq = [pv; pq] # nref = length(ref) npv = length(pv) npq = length(pq) npvpq = npv + npq - n_buses = length(data.bus_type) Vm = data.bus_magnitude[:, time_step] # prevent unfeasible starting values for Vm; for pv and ref buses we cannot do this: diff --git a/src/nlsolve_powerflow.jl b/src/nlsolve_powerflow.jl index b4537780..77274ade 100644 --- a/src/nlsolve_powerflow.jl +++ b/src/nlsolve_powerflow.jl @@ -1,4 +1,4 @@ - +const _NLSOLVE_AC_POWERFLOW_KWARGS = Set([:check_reactive_power_limits, :check_connectivity]) function _newton_powerflow( pf::ACPowerFlow{NLSolveACPowerFlow}, data::ACPowerFlowData; @@ -13,11 +13,13 @@ function _newton_powerflow( ) end + nlsolve_solver_kwargs = filter(p -> !(p.first in _NLSOLVE_AC_POWERFLOW_KWARGS), nlsolve_kwargs) + pf = PolarPowerFlow(data) J = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) df = NLsolve.OnceDifferentiable(pf, J, pf.x0, pf.residual, J.Jv) - res = NLsolve.nlsolve(df, pf.x0; nlsolve_kwargs...) + res = NLsolve.nlsolve(df, pf.x0; nlsolve_solver_kwargs...) if !res.f_converged @error( "The powerflow solver NLSolve did not converge (returned convergence = $(res.f_converged))" diff --git a/src/post_processing.jl b/src/post_processing.jl index aa23c7d6..4bb88cf0 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -461,6 +461,7 @@ function write_powerflow_solution!( system_bustype = PSY.get_bustype(bus) data_bustype = data.bus_type[ix] (system_bustype == data_bustype) && continue + println("sys bustype: $system_bustype, data bustype: $data_bustype") @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" @@ -620,7 +621,8 @@ function write_results( ::ACPowerFlow{<:ACPowerFlowSolverType}, sys::PSY.System, data::ACPowerFlowData, - result::Union{Vector{Float64}, Matrix{Float64}}, #todo + result::Union{Vector{Float64}, Matrix{Float64}}; + time_step::Int64 = 1, ) @info("Voltages are exported in pu. Powers are exported in MW/MVAr.") buses = sort!(collect(PSY.get_components(PSY.Bus, sys)); by = x -> PSY.get_number(x)) @@ -634,27 +636,27 @@ function write_results( P_load_vect = fill(0.0, N_BUS) Q_load_vect = fill(0.0, N_BUS) - for (ix, bustype) in enumerate(data.bus_type) - P_load_vect[ix] = data.bus_activepower_withdrawals[ix] * sys_basepower - Q_load_vect[ix] = data.bus_reactivepower_withdrawals[ix] * sys_basepower + for (ix, bustype) in enumerate(data.bus_type[:, time_step]) + P_load_vect[ix] = data.bus_activepower_withdrawals[ix, time_step] * sys_basepower + Q_load_vect[ix] = data.bus_reactivepower_withdrawals[ix, time_step] * sys_basepower P_admittance, Q_admittance = _get_fixed_admittance_power(sys, buses[ix], result, ix) P_load_vect[ix] += P_admittance * sys_basepower Q_load_vect[ix] += Q_admittance * sys_basepower if bustype == PSY.ACBusTypes.REF - Vm_vect[ix] = data.bus_magnitude[ix] - θ_vect[ix] = data.bus_angles[ix] + Vm_vect[ix] = data.bus_magnitude[ix, time_step] + θ_vect[ix] = data.bus_angles[ix, time_step] P_gen_vect[ix] = result[2 * ix - 1] * sys_basepower Q_gen_vect[ix] = result[2 * ix] * sys_basepower elseif bustype == PSY.ACBusTypes.PV - Vm_vect[ix] = data.bus_magnitude[ix] + Vm_vect[ix] = data.bus_magnitude[ix, time_step] θ_vect[ix] = result[2 * ix] - P_gen_vect[ix] = data.bus_activepower_injection[ix] * sys_basepower + P_gen_vect[ix] = data.bus_activepower_injection[ix, time_step] * sys_basepower Q_gen_vect[ix] = result[2 * ix - 1] * sys_basepower elseif bustype == PSY.ACBusTypes.PQ Vm_vect[ix] = result[2 * ix - 1] θ_vect[ix] = result[2 * ix] - P_gen_vect[ix] = data.bus_activepower_injection[ix] * sys_basepower - Q_gen_vect[ix] = data.bus_reactivepower_injection[ix] * sys_basepower + P_gen_vect[ix] = data.bus_activepower_injection[ix, time_step] * sys_basepower + Q_gen_vect[ix] = data.bus_reactivepower_injection[ix, time_step] * sys_basepower end end diff --git a/test/test_newton_ac_powerflow.jl b/test/test_newton_ac_powerflow.jl index 9c4b6717..d029a49b 100644 --- a/test/test_newton_ac_powerflow.jl +++ b/test/test_newton_ac_powerflow.jl @@ -45,25 +45,25 @@ x1 = PowerFlows._calc_x(data, V1, S1) @test LinearAlgebra.norm(result_14 - x1, Inf) <= 1e-6 - # Test that solve_ac_powerflow! succeeds + # Test that solve_powerflow! succeeds solved1 = deepcopy(sys) @test solve_powerflow!(pf, 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 solve_powerflow!(pf, 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 solve_powerflow!(pf, 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 solve_powerflow!(pf, deepcopy(sys); method = :newton) # Test enforcing the reactive power limits in closer detail set_reactive_power!(get_component(PowerLoad, sys, "Bus4"), 0.0) @@ -505,10 +505,12 @@ end pf_default, sys; check_connectivity = true) + + time_step = 1 - ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) - pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) - pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) + ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type[:, time_step]) + pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type[:, time_step]) + pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type[:, time_step]) pvpq = [pv; pq] npvpq = length(pvpq) diff --git a/test/test_utils/legacy_pf.jl b/test/test_utils/legacy_pf.jl index a6ba33df..eafb8077 100644 --- a/test/test_utils/legacy_pf.jl +++ b/test/test_utils/legacy_pf.jl @@ -47,8 +47,8 @@ function _newton_powerflow( # Find indices for each bus type #ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) - pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type) - pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type) + pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type[:, time_step]) + pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type[:, time_step]) pvpq = [pv; pq] #nref = length(ref) From 09189c36c772c4133f2ace65d3ced55ce2e838f2 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 16 Jan 2025 09:08:58 -0700 Subject: [PATCH 22/23] remove a println --- src/post_processing.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/post_processing.jl b/src/post_processing.jl index 4bb88cf0..b771b062 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -461,7 +461,6 @@ function write_powerflow_solution!( system_bustype = PSY.get_bustype(bus) data_bustype = data.bus_type[ix] (system_bustype == data_bustype) && continue - println("sys bustype: $system_bustype, data bustype: $data_bustype") @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" From 691a73a1697489d548206b751ad04c79be229c25 Mon Sep 17 00:00:00 2001 From: Roman Bolgaryn Date: Thu, 16 Jan 2025 09:42:27 -0700 Subject: [PATCH 23/23] small change to _update_system! --- src/ac_power_flow.jl | 6 ++- src/newton_ac_powerflow.jl | 74 +++++++++++++++++++++----------- src/nlsolve_powerflow.jl | 6 ++- src/post_processing.jl | 58 ++++++++++++++++++++----- test/test_newton_ac_powerflow.jl | 17 ++++++-- test/test_utils/legacy_pf.jl | 10 ++++- 6 files changed, 126 insertions(+), 45 deletions(-) diff --git a/src/ac_power_flow.jl b/src/ac_power_flow.jl index 6daf57c7..ee7b2f2a 100644 --- a/src/ac_power_flow.jl +++ b/src/ac_power_flow.jl @@ -61,9 +61,11 @@ function PolarPowerFlow(data::ACPowerFlowData; time_step::Int64 = 1) Q_net = zeros(n_buses) for ix in 1:n_buses P_net[ix] = - data.bus_activepower_injection[ix, time_step] - data.bus_activepower_withdrawals[ix, time_step] + data.bus_activepower_injection[ix, time_step] - + data.bus_activepower_withdrawals[ix, time_step] Q_net[ix] = - data.bus_reactivepower_injection[ix, time_step] - data.bus_reactivepower_withdrawals[ix, time_step] + data.bus_reactivepower_injection[ix, time_step] - + data.bus_reactivepower_withdrawals[ix, time_step] end x0 = _calculate_x0(1, data.bus_type[:, time_step], diff --git a/src/newton_ac_powerflow.jl b/src/newton_ac_powerflow.jl index f2eaefc5..570732f8 100644 --- a/src/newton_ac_powerflow.jl +++ b/src/newton_ac_powerflow.jl @@ -33,7 +33,7 @@ solve_ac_powerflow!(sys, method=:newton) function solve_powerflow!( pf::ACPowerFlow{<:ACPowerFlowSolverType}, system::PSY.System; - time_step::Int64=1, + time_step::Int64 = 1, kwargs..., ) #Save per-unit flag @@ -47,11 +47,16 @@ function solve_powerflow!( check_connectivity = get(kwargs, :check_connectivity, true), ) - converged, V, Sbus_result = _ac_powereflow(data, pf; time_step=time_step, kwargs...) + converged, V, Sbus_result = _ac_powereflow(data, pf; time_step = time_step, kwargs...) x = _calc_x(data, V, Sbus_result) if converged - write_powerflow_solution!(system, x, data, get(kwargs, :maxIter, DEFAULT_NR_MAX_ITER)) + write_powerflow_solution!( + system, + x, + data, + get(kwargs, :maxIter, DEFAULT_NR_MAX_ITER), + ) @info("PowerFlow solve converged, the results have been stored in the system") else @error("The powerflow solver returned convergence = $(converged)") @@ -163,15 +168,13 @@ function solve_powerflow( return results end - - # Multiperiod power flow - work in progress function solve_powerflow!( data::ACPowerFlowData; kwargs..., ) pf = ACPowerFlow() # todo: somehow store in data which PF to use (see issue #50) - + sorted_time_steps = sort(collect(keys(data.timestep_map))) # preallocate results ts_converged = zeros(Bool, 1, length(sorted_time_steps)) @@ -191,38 +194,48 @@ function solve_powerflow!( ts_V[:, t] .= V ts_S[:, t] .= Sbus_result - ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type[:, t]) - pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type[:, t]) - pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type[:, t]) + ref = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, + data.bus_type[:, t], + ) + pv = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, + data.bus_type[:, t], + ) + pq = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, + data.bus_type[:, t], + ) # temporary implementation that will need to be improved: if converged # write results for REF - data.bus_activepower_injection[ref, t] .= real.(Sbus_result[ref]) .+ data.bus_activepower_withdrawals[ref, t] - data.bus_reactivepower_injection[ref, t] .= imag.(Sbus_result[ref]) .+ data.bus_reactivepower_withdrawals[ref, t] + data.bus_activepower_injection[ref, t] .= + real.(Sbus_result[ref]) .+ data.bus_activepower_withdrawals[ref, t] + data.bus_reactivepower_injection[ref, t] .= + imag.(Sbus_result[ref]) .+ data.bus_reactivepower_withdrawals[ref, t] # write Q results for PV - data.bus_reactivepower_injection[pv, t] .= imag.(Sbus_result[pv]) .+ data.bus_reactivepower_withdrawals[pv, t] + data.bus_reactivepower_injection[pv, t] .= + imag.(Sbus_result[pv]) .+ data.bus_reactivepower_withdrawals[pv, t] # results for PQ buses do not need to be updated -> already consistent with inputs # write bus bus_types # todo - + # write voltage results data.bus_magnitude[pq, t] .= abs.(V[pq]) data.bus_angles[pq, t] .= angle.(V[pq]) data.bus_angles[pv, t] .= angle.(V[pv]) - - else # todo - 1+2 + 1 + 2 end end # write branch flows - Sft = ts_V[fb,:] .* conj.(Yft * ts_V) - Stf = ts_V[tb,:] .* conj.(Ytf * ts_V) + Sft = ts_V[fb, :] .* conj.(Yft * ts_V) + Stf = ts_V[tb, :] .* conj.(Ytf * ts_V) data.branch_activepower_flow_from_to .= real.(Sft) data.branch_reactivepower_flow_from_to .= imag.(Sft) @@ -235,7 +248,6 @@ function solve_powerflow!( return end - function _ac_powereflow( data::ACPowerFlowData, pf::ACPowerFlow{<:ACPowerFlowSolverType}; @@ -275,12 +287,14 @@ function _check_q_limit_bounds!( @info "Bus $(bus_names[ix]) changed to PSY.ACBusTypes.PQ" within_limits = false data.bus_type[ix, time_step] = PSY.ACBusTypes.PQ - data.bus_reactivepower_injection[ix, time_step] = data.bus_reactivepower_bounds[ix, time_step][1] + data.bus_reactivepower_injection[ix, time_step] = + data.bus_reactivepower_bounds[ix, time_step][1] elseif Q_gen >= data.bus_reactivepower_bounds[ix, time_step][2] @info "Bus $(bus_names[ix]) changed to PSY.ACBusTypes.PQ" within_limits = false data.bus_type[ix, time_step] = PSY.ACBusTypes.PQ - data.bus_reactivepower_injection[ix, time_step] = data.bus_reactivepower_bounds[ix, time_step][2] + data.bus_reactivepower_injection[ix, time_step] = + data.bus_reactivepower_bounds[ix, time_step][2] else @debug "Within Limits" end @@ -296,7 +310,8 @@ function _solve_powerflow!( kwargs..., ) for _ in 1:MAX_REACTIVE_POWER_ITERATIONS - converged, V, Sbus_result = _newton_powerflow(pf, data; time_step=time_step, kwargs...) + converged, V, Sbus_result = + _newton_powerflow(pf, data; time_step = time_step, kwargs...) if !converged || !check_reactive_power_limits || _check_q_limit_bounds!(data, Sbus_result, time_step) return converged, V, Sbus_result @@ -525,9 +540,18 @@ function _newton_powerflow( Ybus = data.power_network_matrix.data # Find indices for each bus type - ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type[:, time_step]) - pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type[:, time_step]) - pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type[:, time_step]) + ref = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, + data.bus_type[:, time_step], + ) + pv = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, + data.bus_type[:, time_step], + ) + pq = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, + data.bus_type[:, time_step], + ) pvpq = [pv; pq] # nref = length(ref) diff --git a/src/nlsolve_powerflow.jl b/src/nlsolve_powerflow.jl index 77274ade..c04e5fca 100644 --- a/src/nlsolve_powerflow.jl +++ b/src/nlsolve_powerflow.jl @@ -1,4 +1,5 @@ -const _NLSOLVE_AC_POWERFLOW_KWARGS = Set([:check_reactive_power_limits, :check_connectivity]) +const _NLSOLVE_AC_POWERFLOW_KWARGS = + Set([:check_reactive_power_limits, :check_connectivity]) function _newton_powerflow( pf::ACPowerFlow{NLSolveACPowerFlow}, data::ACPowerFlowData; @@ -13,7 +14,8 @@ function _newton_powerflow( ) end - nlsolve_solver_kwargs = filter(p -> !(p.first in _NLSOLVE_AC_POWERFLOW_KWARGS), nlsolve_kwargs) + nlsolve_solver_kwargs = + filter(p -> !(p.first in _NLSOLVE_AC_POWERFLOW_KWARGS), nlsolve_kwargs) pf = PolarPowerFlow(data) J = PowerFlows.PolarPowerFlowJacobian(data, pf.x0) diff --git a/src/post_processing.jl b/src/post_processing.jl index b771b062..9faa256c 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -709,25 +709,63 @@ if a new `PowerFlowData` is constructed from the resulting system it is the same See also `write_powerflow_solution!`. NOTE that this assumes that `data` was initialized from `sys` and then solved with no further modifications. """ -function update_system!(sys::PSY.System, data::PowerFlowData; time_step=1) +function update_system!(sys::PSY.System, data::PowerFlowData; time_step = 1) for bus in PSY.get_components(PSY.Bus, sys) - if bus.bustype == PSY.ACBusTypes.REF + bus_number = data.bus_lookup[PSY.get_number(bus)] + bus_type = data.bus_type[bus_number, time_step] # use this instead of bus.bustype to account for PV -> PQ + if bus_type == PSY.ACBusTypes.REF # For REF bus, voltage and angle are fixed; update active and reactive - P_gen = data.bus_activepower_injection[data.bus_lookup[PSY.get_number(bus)], time_step] - Q_gen = data.bus_reactivepower_injection[data.bus_lookup[PSY.get_number(bus)], time_step] + P_gen = data.bus_activepower_injection[bus_number, time_step] + Q_gen = data.bus_reactivepower_injection[bus_number, time_step] _power_redistribution_ref(sys, P_gen, Q_gen, bus, DEFAULT_MAX_REDISTRIBUTION_ITERATIONS) - elseif bus.bustype == PSY.ACBusTypes.PV + elseif bus_type == PSY.ACBusTypes.PV # For PV bus, active and voltage are fixed; update reactive and angle - Q_gen = data.bus_reactivepower_injection[data.bus_lookup[PSY.get_number(bus)], time_step] + Q_gen = data.bus_reactivepower_injection[bus_number, time_step] _reactive_power_redistribution_pv(sys, Q_gen, bus, DEFAULT_MAX_REDISTRIBUTION_ITERATIONS) - PSY.set_angle!(bus, data.bus_angles[data.bus_lookup[PSY.get_number(bus)], time_step]) - elseif bus.bustype == PSY.ACBusTypes.PQ + PSY.set_angle!(bus, data.bus_angles[bus_number, time_step]) + elseif bus_type == PSY.ACBusTypes.PQ # For PQ bus, active and reactive are fixed; update voltage and angle - Vm = data.bus_magnitude[data.bus_lookup[PSY.get_number(bus)], time_step] + Vm = data.bus_magnitude[bus_number, time_step] PSY.set_magnitude!(bus, Vm) - PSY.set_angle!(bus, data.bus_angles[data.bus_lookup[PSY.get_number(bus)], time_step]) + PSY.set_angle!(bus, data.bus_angles[bus_number, time_step]) + # if it used to be a PV bus, also set the Q value: + if bus.bustype == PSY.ACBusTypes.PV + Q_gen = data.bus_reactivepower_injection[bus_number, time_step] + _reactive_power_redistribution_pv(sys, Q_gen, bus, + DEFAULT_MAX_REDISTRIBUTION_ITERATIONS) + # now both the Q and the Vm, Va are correct for this kind of buses + end + end + end +end + +# This cannot work because we do not know the redistribution keys from the power values of devices +# to the power values of buses. Leaving this here anyways to document this issue. +function set_system_time_step(sys::PSY.System, data::PowerFlowData; time_step = 1) + @error("This function cannot work") + for bus in PSY.get_components(PSY.Bus, sys) + bus_number = data.bus_lookup[PSY.get_number(bus)] + bus_type = data.bus_type[bus_number, time_step] + sys_bus_type = sys.bustype # here we should be using the system bus type to know which inputs to modify + + # for REF bus, we don't need to set anything + if sys_bus_type == PSY.ACBusTypes.PV + # we need to modify the P value + # TODO if we also modify time-series data for voltage set-points, also set that value. + # However, we cannot do this easily because it can happen that the PV -> PQ transformation + # caused a different Vm value as the result, while the set-point Vm is still the same. + # We need a solution for this. + P_gen = data.bus_activepower_injection[bus_number, time_step] + Q_gen = 0.0 + # TODO write time-series value of the bus back to devices (not possible) + elseif sys_bus_type == PSY.ACBusTypes.PQ + P_gen = data.bus_activepower_injection[bus_number, time_step] + Q_gen = data.bus_reactivepower_injection[bus_number, time_step] + P_load = data.bus_activepower_withdrawals[bus_number, time_step] + Q_load = data.bus_reactivepower_withdrawals[bus_number, time_step] + # TODO write time-series value of the bus back to devices (not possible) end end end diff --git a/test/test_newton_ac_powerflow.jl b/test/test_newton_ac_powerflow.jl index d029a49b..beed6970 100644 --- a/test/test_newton_ac_powerflow.jl +++ b/test/test_newton_ac_powerflow.jl @@ -505,12 +505,21 @@ end pf_default, sys; check_connectivity = true) - + time_step = 1 - ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type[:, time_step]) - pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type[:, time_step]) - pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type[:, time_step]) + ref = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, + data.bus_type[:, time_step], + ) + pv = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, + data.bus_type[:, time_step], + ) + pq = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, + data.bus_type[:, time_step], + ) pvpq = [pv; pq] npvpq = length(pvpq) diff --git a/test/test_utils/legacy_pf.jl b/test/test_utils/legacy_pf.jl index eafb8077..5436b334 100644 --- a/test/test_utils/legacy_pf.jl +++ b/test/test_utils/legacy_pf.jl @@ -47,8 +47,14 @@ function _newton_powerflow( # Find indices for each bus type #ref = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.REF, data.bus_type) - pv = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, data.bus_type[:, time_step]) - pq = findall(x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, data.bus_type[:, time_step]) + pv = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PV, + data.bus_type[:, time_step], + ) + pq = findall( + x -> x == PowerSystems.ACBusTypesModule.ACBusTypes.PQ, + data.bus_type[:, time_step], + ) pvpq = [pv; pq] #nref = length(ref)