diff --git a/Project.toml b/Project.toml index e1d4560..09af653 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "LITS" uuid = "86b0dc02-7903-11e9-325f-f195ca7e6c1a" -version = "0.3.1" +version = "0.4.0" [deps] DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" @@ -11,11 +11,10 @@ NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56" PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - [compat] DiffEqBase = "6" ForwardDiff = "~v0.10" -InfrastructureSystems = "~0.5" +InfrastructureSystems = "~0.6" NLsolve = "4" -PowerSystems = "~0.11" +PowerSystems = "~0.14" julia = "^1.2" diff --git a/dev_files/init_cond/Twogen2bus.m b/dev_files/init_cond/Twogen2bus.m index 03a0908..f940ecc 100644 --- a/dev_files/init_cond/Twogen2bus.m +++ b/dev_files/init_cond/Twogen2bus.m @@ -15,7 +15,7 @@ %% generator data % bus Pg Qg Qmax Qmin Vg mBase status Pmax Pmin Pc1 Pc2 Qc1min Qc1max Qc2min Qc2max ramp_agc ramp_10 ramp_30 ramp_q apf mpc.gen = [ - 1 700 70 100 -100 1.02 1000 1000 1000 0 0 0 0 0 0 0 0 0 0 0; + 1 700 70 100 -100 1.02 1000 1 1000 0 0 0 0 0 0 0 0 0 0 0; %2 50 -30 100 -100 1.0 100 1 150 37.5 0 0 0 0 0 0 0 0 0 0 0; ]; diff --git a/dev_files/init_cond/initial_conditions.ipynb b/dev_files/init_cond/initial_conditions.ipynb index 40dc4e3..854480f 100644 --- a/dev_files/init_cond/initial_conditions.ipynb +++ b/dev_files/init_cond/initial_conditions.ipynb @@ -2,37 +2,156 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "PowerSystems" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ + "using Revise\n", "using PowerModels\n", "using Ipopt\n", "using ForwardDiff\n", - "using OrdinaryDiffEq\n", - "using NLsolve" + "#using OrdinaryDiffEq\n", + "using NLsolve\n", + "using PowerSystems\n", + "const PSY = PowerSystems" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "HTML{String}(\"\")" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "display(HTML(\"\"))" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "┌ Info: Constructing System from Power Models\n", + "│ data[\"name\"] = Twogen2bus\n", + "│ data[\"source_type\"] = matpower\n", + "└ @ PowerSystems /Users/jdlara/.julia/packages/PowerSystems/wHSk9/src/parsers/power_models_data.jl:33\n", + "┌ Info: Reading bus data\n", + "└ @ PowerSystems /Users/jdlara/.julia/packages/PowerSystems/wHSk9/src/parsers/power_models_data.jl:122\n", + "┌ Info: Reading generator data\n", + "└ @ PowerSystems /Users/jdlara/.julia/packages/PowerSystems/wHSk9/src/parsers/power_models_data.jl:368\n", + "┌ Warning: Generator cost data not included for Generator: gen-1-1\n", + "└ @ PowerSystems /Users/jdlara/.julia/packages/PowerSystems/wHSk9/src/parsers/power_models_data.jl:330\n", + "┌ Info: Reading branch data\n", + "└ @ PowerSystems /Users/jdlara/.julia/packages/PowerSystems/wHSk9/src/parsers/power_models_data.jl:520\n", + "┌ Warning: Rate provided for 1 is 32.0, larger the SIL 258.94053053874836 in the range of (min = 325.0, max = 425.0).\n", + "└ @ PowerSystems /Users/jdlara/.julia/packages/PowerSystems/wHSk9/src/utils/IO/branchdata_checks.jl:150\n", + "┌ Info: Reading branch data\n", + "└ @ PowerSystems /Users/jdlara/.julia/packages/PowerSystems/wHSk9/src/parsers/power_models_data.jl:583\n", + "┌ Info: Reading DC Line data\n", + "└ @ PowerSystems /Users/jdlara/.julia/packages/PowerSystems/wHSk9/src/parsers/power_models_data.jl:554\n", + "┌ Info: PowerFlow solve converged, the results have been stored in the system\n", + "└ @ PowerSystems /Users/jdlara/.julia/packages/PowerSystems/wHSk9/src/utils/power_flow/power_flow.jl:147\n" + ] + }, + { + "data": { + "text/plain": [ + "true" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "file_dir = joinpath(pwd(), \"Twogen2bus.m\")\n", + "sys = System(PSY.PowerModelsData(file_dir))\n", + "solve_powerflow!(sys, nlsolve, method = :newton)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Vm = 1.02 Va= 0.0\n", + "Vm = 1.0 Va= -0.1279338657728345\n" + ] + } + ], + "source": [ + "for b in get_components(Bus, sys)\n", + " println(\"Vm = $(get_voltage(b))\", \" \",\"Va= $(get_angle(b))\")\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mMetadata\u001b[0m\n", + " baseMVA: 1000\n", + " per_unit: true\n", + "\n", + "\u001b[1mTable Counts\u001b[0m\n", + " bus: 2\n", + " gen: 1\n", + "\n", + "\n", + "\u001b[1mTable: bus\u001b[0m\n", + " vm, va\n", + " 1: 1.020, 0.000\n", + " 2: 1.000, -0.128\n", + "\n", + "\n", + "\u001b[1mTable: gen\u001b[0m\n", + " pg, qg\n", + " 1: 0.703, 0.051\n" + ] + } + ], "source": [ - "data = PowerModels.parse_file(\"C:/Users/Ewa/Downloads/Twogen2bus.m\")\n", - "pf_result = run_ac_pf(data ,Ipopt.Optimizer)\n", - "#PowerModels.print_summary(pf_result[\"solution\"])" + "data = PowerModels.parse_file(file_dir)\n", + "opt = optimizer_with_attributes(Ipopt.Optimizer, \"print_level\" => 0)\n", + "pf_result = run_ac_pf(data, opt)\n", + "PowerModels.print_summary(pf_result[\"solution\"])" ] }, { @@ -301,17 +420,17 @@ ], "metadata": { "kernelspec": { - "display_name": "Julia 1.3.1", + "display_name": "Julia 1.4.0", "language": "julia", - "name": "julia-1.3" + "name": "julia-1.4" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.3.1" + "version": "1.4.0" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/docs/src/Examples/example_lines.md b/docs/src/Examples/example_lines.md index 492a86b..14241e5 100644 --- a/docs/src/Examples/example_lines.md +++ b/docs/src/Examples/example_lines.md @@ -368,8 +368,8 @@ x0_guess = [ 0.0, #V1_I -0.01, #V2_I -0.01, #V3_I - 0.0, #δω_vsm - 0.2, #δθ_vsm + 0.0, #ω_oc + 0.2, #θ_oc 0.025, #qm 0.0015, #ξ_d -0.07, #ξ_q @@ -380,13 +380,13 @@ x0_guess = [ 1.004, #vpll_d 0.0, #vpll_q 0.0, #ε_pll - 0.1, #δθ_pll + 0.1, #θ_pll 0.5, #id_cv 0.0, #iq_cv - 0.95, #vod - -0.1, #voq - 0.49, #iod - -0.1, #ioq + 0.95, #Vd_filter + -0.1, #Vq_filter + 0.49, #Id_filter + -0.1, #Iq_filter 1.0, #eq_p 0.47, #ed_p 0.6, #δ diff --git a/src/LITS.jl b/src/LITS.jl index a281f03..1e604d5 100644 --- a/src/LITS.jl +++ b/src/LITS.jl @@ -4,7 +4,7 @@ module LITS ####################################### Structs Exports #################################### # Base Exports -export DynBranch +export DynamicLine export Simulation export run_simulation! export ThreePhaseFault @@ -23,10 +23,11 @@ import InfrastructureSystems import DiffEqBase import ForwardDiff import SparseArrays: SparseMatrixCSC -import LinearAlgebra: BLAS +#import LinearAlgebra: BLAS import LinearAlgebra: eigen import Base.to_index import NLsolve +import Base.ImmutableDict import PowerSystems const PSY = PowerSystems const IS = InfrastructureSystems @@ -41,7 +42,7 @@ include("base/simulation.jl") #Common Models include("models/branch.jl") include("models/device.jl") -include("models/kcl.jl") +include("models/kirchoff_laws.jl") include("models/dynline_model.jl") include("models/ref_transformations.jl") @@ -57,7 +58,7 @@ include("models/inverter_models/DCside_models.jl") include("models/inverter_models/filter_models.jl") include("models/inverter_models/frequency_estimator_models.jl") include("models/inverter_models/outer_control_models.jl") -include("models/inverter_models/voltage_source_control_models.jl") +include("models/inverter_models/inner_control_models.jl") include("models/inverter_models/converter_models.jl") #Injection Models @@ -69,6 +70,7 @@ include("models/system.jl") #Utils include("utils/plot_utils.jl") +include("utils/immutable_dicts.jl") include("utils/print.jl") include("utils/kwargs_check.jl") diff --git a/src/base/definitions.jl b/src/base/definitions.jl index cc82974..469c857 100644 --- a/src/base/definitions.jl +++ b/src/base/definitions.jl @@ -40,32 +40,32 @@ Inverter Inner Vars: md_var :: Modulation signal on the d-component mq_var :: Modulation signal on the q-component Vdc_var :: DC voltage supplied by the DC source -Vdo_var :: Voltage seen in the capacitor of the filter in the d-component -Vqo_var :: Voltage seen in the capacitor of the filter in the q-component +Vd_filter_var :: Voltage seen in the capacitor of the filter in the d-component +Vq_filter_var :: Voltage seen in the capacitor of the filter in the q-component ω_freq_estimator_var :: Frequency estimated by the frequency estimator. -v_control_var :: Control voltage supplied from the outer loop control to the inner loop -ω_control_var :: Control frequency supplied from the outer loop control the inner loop -δdqRI_var :: Variation of the angle (PLL or VSM) of the inverter +V_oc_var :: Control voltage supplied from the outer loop control to the inner loop +ω_oc_var :: Control frequency supplied from the outer loop control the inner loop +θ_oc_var :: Variation of the angle (PLL or VSM) of the inverter VR_inv_var :: Real terminal voltage on the inverter VI_inv_var :: Imaginary terminal voltage on the inverter -Vdcnv_var :: Voltage supplied from the converter in the d-component -Vqcnv_var :: Voltage supplied from the converter in the q-component +Vd_cnv_var :: Voltage supplied from the converter in the d-component +Vq_cnv_var :: Voltage supplied from the converter in the q-component """ @enum inverter_inner_vars begin md_var = 1 mq_var = 2 Vdc_var = 3 - Vdo_var = 4 - Vqo_var = 5 + Vd_filter_var = 4 + Vq_filter_var = 5 ω_freq_estimator_var = 6 - v_control_var = 7 - ω_control_var = 8 - δdqRI_var = 9 + V_oc_var = 7 + ω_oc_var = 8 + θ_oc_var = 9 VR_inv_var = 10 VI_inv_var = 11 - Vdcnv_var = 12 - Vqcnv_var = 13 + Vd_cnv_var = 12 + Vq_cnv_var = 13 end Base.to_index(ix::inverter_inner_vars) = Int(ix) @@ -84,5 +84,8 @@ const INNER_VARS = "inner_vars" const YBUS = "Ybus" const CONTROL_REFS = "control_refs" const GLOBAL_VARS = "global_vars" +const VOLTAGE_BUSES_NO = "voltage_buses_no" +const CURRENT_BUSES_NO = "current_buses_no" +const TOTAL_SHUNTS = "total_shunts" const SIMULATION_ACCEPTED_KWARGS = [:initial_guess, :initialize_simulation] diff --git a/src/base/ports.jl b/src/base/ports.jl index 7b0a5e1..a10f180 100644 --- a/src/base/ports.jl +++ b/src/base/ports.jl @@ -45,7 +45,7 @@ end #### Converter Ports #### function Ports(::PSY.Converter) state_input = Vector{Symbol}() - inner_input = [md_var, mq_var, Vdc_var, Vdcnv_var, Vqcnv_var] + inner_input = [md_var, mq_var, Vdc_var, Vd_cnv_var, Vq_cnv_var] return Ports(state_input, inner_input) end @@ -59,31 +59,38 @@ end #### Filter Ports #### function Ports(::PSY.Filter) #TODO: If converter has dynamics, need to connect state_input - state_input = [:δθ_vsm] #[:Vd_c, :Vq_c] #, :Id_c, :Iq_c] - inner_input = - [VR_inv_var, VI_inv_var, Vdcnv_var, Vqcnv_var, δdqRI_var, Vdo_var, Vqo_var] + state_input = [:θ_oc] #[:Vd_c, :Vq_c] #, :Id_c, :Iq_c] + inner_input = [ + VR_inv_var, + VI_inv_var, + Vd_cnv_var, + Vq_cnv_var, + θ_oc_var, + Vd_filter_var, + Vq_filter_var, + ] return Ports(state_input, inner_input) end #### Freq. Estimator Ports #### function Ports(::PSY.FrequencyEstimator) - state_input = [:vd_cap, :vq_cap, :δθ_vsm] + state_input = [:vd_filter, :vq_filter, :θ_oc] #TODO: Move PLL to PCC, i.e. move v_cap (D'Arco v_o), to inner inputs - inner_input = [Vdo_var, Vqo_var, δdqRI_var, ω_freq_estimator_var] + inner_input = [Vd_filter_var, Vq_filter_var, θ_oc_var, ω_freq_estimator_var] return Ports(state_input, inner_input) end #### Outer Control Ports #### function Ports(::PSY.OuterControl) - state_input = [:vpll_d, :vpll_q, :ε_pll, :vd_cap, :vq_cap, :id_o, :iq_o] - inner_input = [Vdo_var, Vqo_var, ω_freq_estimator_var] + state_input = [:vd_pll, :vq_pll, :ε_pll, :vd_filter, :vq_filter, :id_filter, :iq_filter] + inner_input = [Vd_filter_var, Vq_filter_var, ω_freq_estimator_var] return Ports(state_input, inner_input) end #### Inner Control Ports #### -function Ports(::PSY.VSControl) - state_input = [:id_o, :iq_o, :id_c, :iq_c, :vd_cap, :vq_cap] - inner_input = [Vdo_var, Vqo_var, v_control_var, ω_control_var] +function Ports(::PSY.InnerControl) + state_input = [:id_filter, :iq_filter, :id_cnv, :iq_cnv, :vd_filter, :vq_filter] + inner_input = [Vd_filter_var, Vq_filter_var, V_oc_var, ω_oc_var] return Ports(state_input, inner_input) end diff --git a/src/base/simulation.jl b/src/base/simulation.jl index 56f8cda..76ece36 100644 --- a/src/base/simulation.jl +++ b/src/base/simulation.jl @@ -134,7 +134,7 @@ end function _attach_inner_vars!( device::PSY.DynamicGenerator, - ::Type{T} = Float64, + ::Type{T} = Real, ) where {T <: Real} device.ext[INNER_VARS] = zeros(T, 8) return @@ -142,7 +142,7 @@ end function _attach_inner_vars!( device::PSY.DynamicInverter, - ::Type{T} = Float64, + ::Type{T} = Real, ) where {T <: Real} device.ext[INNER_VARS] = zeros(T, 13) return @@ -210,18 +210,58 @@ function _index_dynamic_system!(sys::PSY.System) n_buses = length(PSY.get_components(PSY.Bus, sys)) DAE_vector = collect(falses(n_buses * 2)) global_state_index = Dict{String, Dict{Symbol, Int64}}() - n_buses = length(PSY.get_components(PSY.Bus, sys)) state_space_ix = [n_buses * 2] + current_buses_no = collect(1:n_buses) + static_bus_var_count = 2 * length(current_buses_no) + voltage_buses_no = Vector{Int}() total_states = 0 first_dyn_branch_point = -1 branches_n_states = 0 - static_bus_vars = 2 * n_buses global_vars = Dict{Symbol, Number}( :ω_sys => 1.0, :ω_sys_index => -1, #To define 0 if infinite source, bus_number otherwise, ) + total_shunts = Dict{Int, Float64}() found_ref_bus = false + dyn_branches = PSY.get_components(DynamicLine, sys) + if !(isempty(dyn_branches)) + first_dyn_branch_point = state_space_ix[1] + 1 + for br in dyn_branches + arc = PSY.get_arc(br) + n_states = PSY.get_n_states(br) + from_bus_number = PSY.get_number(arc.from) + to_bus_number = PSY.get_number(arc.to) + merge!( + +, + total_shunts, + Dict( + from_bus_number => 1 / PSY.get_b(br).from, + to_bus_number => 1 / PSY.get_b(br).to, + ), + ) + push!(voltage_buses_no, from_bus_number, to_bus_number) + DAE_vector[from_bus_number] = DAE_vector[from_bus_number + n_buses] = true + DAE_vector[to_bus_number] = DAE_vector[to_bus_number + n_buses] = true + DAE_vector = push!(DAE_vector, collect(trues(n_states))...) + total_states += n_states + _add_states_to_global!(global_state_index, state_space_ix, br) + end + + for (ix, val) in enumerate(DAE_vector[1:n_buses]) + if val + global_state_index["V_$(ix)"] = Dict(:R => ix, :I => ix + n_buses) + total_states += 2 + static_bus_var_count -= 2 + push!(voltage_buses_no, ix) + @assert static_bus_var_count >= 0 + end + end + branches_n_states = state_space_ix[1] - n_buses * 2 + else + @debug("System doesn't contain Dynamic Branches") + end + unique!(voltage_buses_no) sources = PSY.get_components(PSY.Source, sys) for s in sources btype = PSY.get_bustype(PSY.get_bus(s)) @@ -232,7 +272,6 @@ function _index_dynamic_system!(sys::PSY.System) global_vars[:ω_sys_index] = 0 #To define 0 if infinite source, bus_number otherwise, found_ref_bus = true end - dynamic_injection = PSY.get_components(PSY.DynamicInjection, sys) isempty(dynamic_injection) && error("System doesn't contain any DynamicInjection devices") @@ -240,68 +279,45 @@ function _index_dynamic_system!(sys::PSY.System) if !(:states in fieldnames(typeof(d))) continue end - btype = PSY.get_bustype(PSY.get_bus(d)) + device_bus = PSY.get_bus(d) + btype = PSY.get_bustype(device_bus) if (btype == PSY.BusTypes.REF) && found_ref_bus throw(IS.ConflictingInputsError("The system can't have more than one source or generator in the REF Bus")) end _make_device_index!(d) device_n_states = PSY.get_n_states(d) - DAE_vector = vcat(DAE_vector, collect(trues(device_n_states))) + DAE_vector = push!(DAE_vector, collect(trues(device_n_states))...) total_states += device_n_states _add_states_to_global!(global_state_index, state_space_ix, d) btype != PSY.BusTypes.REF && continue global_vars[:ω_sys_index] = global_state_index[d.name][:ω] #To define 0 if infinite source, bus_number otherwise, found_ref_bus = true end - injection_n_states = state_space_ix[1] - n_buses * 2 - - dyn_branches = PSY.get_components(DynamicLine, sys) - if !(isempty(dyn_branches)) - first_dyn_branch_point = state_space_ix[1] + 1 - for br in dyn_branches - arc = PSY.get_arc(br) - n_states = PSY.get_n_states(br) - from_bus_number = PSY.get_number(arc.from) - to_bus_number = PSY.get_number(arc.to) - DAE_vector[from_bus_number] = DAE_vector[from_bus_number + n_buses] = true - DAE_vector[to_bus_number] = DAE_vector[to_bus_number + n_buses] = true - DAE_vector = vcat(DAE_vector, collect(trues(n_states))) - total_states += n_states - _add_states_to_global!(global_state_index, state_space_ix, br) - end - - for (ix, val) in enumerate(DAE_vector[1:n_buses]) - if val - global_state_index["V_$(ix)"] = Dict(:R => ix, :I => ix + n_buses) - total_states += 2 - state_space_ix[1] += 2 - static_bus_vars -= 2 - @assert static_bus_vars >= 0 - end - end - branches_n_states = state_space_ix[1] - injection_n_states - n_buses * 2 - else - @debug("System doesn't contain Dynamic Branches") - end - @assert total_states == state_space_ix[1] - n_buses * 2 - + injection_n_states = state_space_ix[1] - branches_n_states - n_buses * 2 + @assert total_states == state_space_ix[1] - static_bus_var_count + @debug total_states + setdiff!(current_buses_no, voltage_buses_no) if !isempty(PSY.get_components(PSY.ACBranch, sys)) Ybus = PSY.Ybus(sys)[:, :] else Ybus = SparseMatrixCSC{Complex{Float64}, Int64}(zeros(n_buses, n_buses)) end - sys_ext = Dict{String, Any}() #I change it to be Any - counts = Dict{Symbol, Int64}( + sys_ext = Dict{String, Any}() + counts = Base.ImmutableDict( :total_states => total_states, :injection_n_states => injection_n_states, :branches_n_states => branches_n_states, - :first_dyn_injection_pointer => 2 * n_buses + 1, + :first_dyn_injection_pointer => 2 * n_buses + branches_n_states + 1, :first_dyn_branch_point => first_dyn_branch_point, - :total_variables => total_states + static_bus_vars, + :total_variables => total_states + static_bus_var_count, ) + sys_ext[LITS_COUNTS] = counts sys_ext[GLOBAL_INDEX] = global_state_index + sys_ext[VOLTAGE_BUSES_NO] = voltage_buses_no + sys_ext[CURRENT_BUSES_NO] = current_buses_no sys_ext[YBUS] = Ybus + sys_ext[TOTAL_SHUNTS] = total_shunts sys_ext[GLOBAL_VARS] = global_vars @assert sys_ext[GLOBAL_VARS][:ω_sys_index] != -1 sys.internal.ext = sys_ext @@ -318,9 +334,11 @@ get_system_state_count(sys::PSY.System) = PSY.get_ext(sys)[LITS_COUNTS][:total_s get_variable_count(sys::PSY.System) = PSY.get_ext(sys)[LITS_COUNTS][:total_variables] get_device_index(sys::PSY.System, device::D) where {D <: PSY.DynamicInjection} = PSY.get_ext(sys)[GLOBAL_INDEX][device.name] - get_inner_vars(device::PSY.DynamicInjection) = device.ext[INNER_VARS] get_ω_sys(sys::PSY.System) = PSY.get_ext(sys)[GLOBAL_VARS][:ω_sys] +get_current_bus_no(sys::PSY.System) = PSY.get_ext(sys)[CURRENT_BUSES_NO] +get_voltage_bus_no(sys::PSY.System) = PSY.get_ext(sys)[VOLTAGE_BUSES_NO] +get_total_shunts(sys::PSY.System) = PSY.get_ext(sys)[TOTAL_SHUNTS] function _get_internal_mapping( device::PSY.DynamicInjection, @@ -380,11 +398,6 @@ function small_signal_analysis(sim::Simulation; kwargs...) @error("Reset the simulation") end - dyn_branches = PSY.get_components(DynamicLine, sim.system) - if !(isempty(dyn_branches)) - @error("Small Signal Analysis is not currently supported for models with DynamicLines") - end - _change_vector_type(sim.system) var_count = LITS.get_variable_count(sim.system) dx0 = zeros(var_count) #Define a vector of zeros for the derivative @@ -398,15 +411,19 @@ function small_signal_analysis(sim::Simulation; kwargs...) out = zeros(var_count) #Define a vector of zeros for the output x_eval = get(kwargs, :operating_point, sim.x0_init) jacobian = ForwardDiff.jacobian(sysf!, out, x_eval) - first_dyn_injection_pointer = - PSY.get_ext(sim.system)[LITS_COUNTS][:first_dyn_injection_pointer] - bus_size = length(PSY.get_components(PSY.Bus, sim.system)) - alg_states = 1:(2 * bus_size) - diff_states = first_dyn_injection_pointer:var_count - fx = jacobian[diff_states, diff_states] + n_buses = length(PSY.get_components(PSY.Bus, sim.system)) + diff_states = collect(trues(var_count)) + diff_states[1:(2 * n_buses)] .= false + for b_no in get_voltage_bus_no(sim.system) + diff_states[b_no] = true + diff_states[b_no + n_buses] = true + end + alg_states = .!diff_states + fx = @view jacobian[diff_states, diff_states] gy = jacobian[alg_states, alg_states] - fy = jacobian[diff_states, alg_states] - gx = jacobian[alg_states, diff_states] + fy = @view jacobian[diff_states, alg_states] + gx = @view jacobian[alg_states, diff_states] + # TODO: Make operation using BLAS! reduced_jacobian = fx - fy * inv(gy) * gx vals, vect = eigen(reduced_jacobian) return SmallSignalOutput( diff --git a/src/models/branch.jl b/src/models/branch.jl index 12ae184..297c640 100644 --- a/src/models/branch.jl +++ b/src/models/branch.jl @@ -54,7 +54,7 @@ end function branch!( x, dx, - output_ode::Vector{Float64}, + output_ode::Vector{T}, V_r_from, V_i_from, V_r_to, @@ -64,17 +64,14 @@ function branch!( current_r_to, current_i_to, ix_range::UnitRange{Int64}, - ix_dx::Vector{Int64}, ode_range::UnitRange{Int64}, branch::DynamicLine{PSY.Line}, sys::PSY.System, -) +) where {T <: Real} #Obtain local device states n_states = PSY.get_n_states(branch) device_states = @view x[ix_range] - dv_from = view(dx, ix_dx[1:2]) - dv_to = view(dx, ix_dx[3:4]) #Obtain references Sbase = PSY.get_basepower(sys) @@ -92,8 +89,6 @@ function branch!( current_i_from, current_r_to, current_i_to, - dv_from, - dv_to, sys_f, branch, ) diff --git a/src/models/device.jl b/src/models/device.jl index 96783fe..eaa9a48 100644 --- a/src/models/device.jl +++ b/src/models/device.jl @@ -106,14 +106,11 @@ function device!( #Update V_ref V_ref = PSY.get_ext(device)[CONTROL_REFS][V_ref_index] - get_inner_vars(device)[v_control_var] = V_ref + get_inner_vars(device)[V_oc_var] = V_ref #Obtain ODES for DC side mdl_DCside_ode!(device) - #Obtain ODEs for OuterLoop - mdl_outer_ode!(device_states, view(output_ode, ode_range), sys_f0, sys_ω, device) - #Obtain ODEs for PLL mdl_freq_estimator_ode!( device_states, @@ -123,8 +120,11 @@ function device!( device, ) + #Obtain ODEs for OuterLoop + mdl_outer_ode!(device_states, view(output_ode, ode_range), sys_f0, sys_ω, device) + #Obtain inner controller ODEs and modulation commands - mdl_VScontrol_ode!(device_states, view(output_ode, ode_range), device) + mdl_inner_ode!(device_states, view(output_ode, ode_range), device) #Obtain converter relations mdl_converter_ode!(device) diff --git a/src/models/dynline_model.jl b/src/models/dynline_model.jl index 9c2d167..84bdfd2 100644 --- a/src/models/dynline_model.jl +++ b/src/models/dynline_model.jl @@ -9,8 +9,6 @@ function mdl_line_ode!( current_i_from, current_r_to, current_i_to, - dv_from, - dv_to, sys_f, branch::DynamicLine{PSY.Line}, ) @@ -18,13 +16,6 @@ function mdl_line_ode!( L = PSY.get_x(branch) R = PSY.get_r(branch) ω_b = sys_f * 2 * π - c_from = PSY.get_b(branch).from - c_to = PSY.get_b(branch).to - - current_r_from[1] -= (1.0 / ω_b) * c_from * dv_from[1] - c_from * V_i_from[1] - current_i_from[1] -= (1.0 / ω_b) * c_from * dv_from[2] + c_from * V_r_from[1] - current_r_to[1] -= (1.0 / ω_b) * c_to * dv_to[1] - c_to * V_i_to[1] - current_i_to[1] -= (1.0 / ω_b) * c_to * dv_to[2] + c_to * V_r_to[1] Il_r = device_states[1] Il_i = device_states[2] diff --git a/src/models/inverter_models/DCside_models.jl b/src/models/inverter_models/DCside_models.jl index d0e0aae..49cae94 100644 --- a/src/models/inverter_models/DCside_models.jl +++ b/src/models/inverter_models/DCside_models.jl @@ -1,9 +1,9 @@ function mdl_DCside_ode!( - device::PSY.DynamicInverter{C, O, VC, PSY.FixedDCSource, P, F}, + device::PSY.DynamicInverter{C, O, IC, PSY.FixedDCSource, P, F}, ) where { C <: PSY.Converter, O <: PSY.OuterControl, - VC <: PSY.VSControl, + IC <: PSY.InnerControl, P <: PSY.FrequencyEstimator, F <: PSY.Filter, } diff --git a/src/models/inverter_models/converter_models.jl b/src/models/inverter_models/converter_models.jl index 31e8420..4aebba6 100644 --- a/src/models/inverter_models/converter_models.jl +++ b/src/models/inverter_models/converter_models.jl @@ -1,8 +1,8 @@ function mdl_converter_ode!( - device::PSY.DynamicInverter{PSY.AvgCnvFixedDC, O, VC, DC, P, F}, + device::PSY.DynamicInverter{PSY.AverageConverter, O, IC, DC, P, F}, ) where { O <: PSY.OuterControl, - VC <: PSY.VSControl, + IC <: PSY.InnerControl, DC <: PSY.DCSource, P <: PSY.FrequencyEstimator, F <: PSY.Filter, @@ -16,8 +16,8 @@ function mdl_converter_ode!( VDC = get_inner_vars(device)[Vdc_var] #Update inner_vars - get_inner_vars(device)[Vdcnv_var] = md * VDC - get_inner_vars(device)[Vqcnv_var] = mq * VDC + get_inner_vars(device)[Vd_cnv_var] = md * VDC + get_inner_vars(device)[Vq_cnv_var] = mq * VDC end #TODO: Same as above, but: diff --git a/src/models/inverter_models/filter_models.jl b/src/models/inverter_models/filter_models.jl index 3c92a4b..eb21d51 100644 --- a/src/models/inverter_models/filter_models.jl +++ b/src/models/inverter_models/filter_models.jl @@ -6,11 +6,11 @@ function mdl_filter_ode!( sys_Sbase, f0, ω_sys, - device::PSY.DynamicInverter{C, O, VC, DC, P, PSY.LCLFilter}, + device::PSY.DynamicInverter{C, O, IC, DC, P, PSY.LCLFilter}, ) where { C <: PSY.Converter, O <: PSY.OuterControl, - VC <: PSY.VSControl, + IC <: PSY.InnerControl, DC <: PSY.DCSource, P <: PSY.FrequencyEstimator, } @@ -18,16 +18,16 @@ function mdl_filter_ode!( #Obtain external states inputs for component #TODO: If converter has dynamics, need to reference states: #external_ix = device.input_port_mapping[device.converter] - #vcvd = device_states[external_ix[1]] - #vcvq = device_states[external_ix[2]] + #Vd_cnv = device_states[external_ix[1]] + #Vq_cnv = device_states[external_ix[2]] external_ix = get_input_port_ix(device, PSY.LCLFilter) δ = device_states[external_ix[1]] #Obtain inner variables for component V_tR = get_inner_vars(device)[VR_inv_var] V_tI = get_inner_vars(device)[VI_inv_var] - vcvd = get_inner_vars(device)[Vdcnv_var] - vcvq = get_inner_vars(device)[Vqcnv_var] + Vd_cnv = get_inner_vars(device)[Vd_cnv_var] + Vq_cnv = get_inner_vars(device)[Vq_cnv_var] #Get parameters filter = PSY.get_filter(device) @@ -48,45 +48,55 @@ function mdl_filter_ode!( #Define internal states for filter internal_states = @view device_states[local_ix] - icvd = internal_states[1] - icvq = internal_states[2] - vod = internal_states[3] - voq = internal_states[4] - iod = internal_states[5] - ioq = internal_states[6] + Id_cnv = internal_states[1] + Iq_cnv = internal_states[2] + Vd_filter = internal_states[3] + Vq_filter = internal_states[4] + Id_filter = internal_states[5] + Iq_filter = internal_states[6] #Inputs (control signals) - N/A #Compute 6 states ODEs (D'Arco EPSR122 Model) #Inverter Output Inductor (internal state) #𝜕id_c/𝜕t - output_ode[local_ix[1]] = - (ωb / lf * vcvd - ωb / lf * vod - ωb * rf / lf * icvd + ωb * ω_sys * icvq) + output_ode[local_ix[1]] = ( + ωb / lf * Vd_cnv - ωb / lf * Vd_filter - ωb * rf / lf * Id_cnv + + ωb * ω_sys * Iq_cnv + ) #𝜕iq_c/𝜕t - output_ode[local_ix[2]] = - (ωb / lf * vcvq - ωb / lf * voq - ωb * rf / lf * icvq - ωb * ω_sys * icvd) + output_ode[local_ix[2]] = ( + ωb / lf * Vq_cnv - ωb / lf * Vq_filter - ωb * rf / lf * Iq_cnv - + ωb * ω_sys * Id_cnv + ) #LCL Capacitor (internal state) #𝜕vd_o/𝜕t - output_ode[local_ix[3]] = (ωb / cf * icvd - ωb / cf * iod + ωb * ω_sys * voq) + output_ode[local_ix[3]] = + (ωb / cf * Id_cnv - ωb / cf * Id_filter + ωb * ω_sys * Vq_filter) #𝜕vq_o/𝜕t - output_ode[local_ix[4]] = (ωb / cf * icvq - ωb / cf * ioq - ωb * ω_sys * vod) + output_ode[local_ix[4]] = + (ωb / cf * Iq_cnv - ωb / cf * Iq_filter - ωb * ω_sys * Vd_filter) #Grid Inductance (internal state) #𝜕id_o/𝜕t - output_ode[local_ix[5]] = - (ωb / lg * vod - ωb / lg * V_dq[2] - ωb * rg / lg * iod + ωb * ω_sys * ioq) + output_ode[local_ix[5]] = ( + ωb / lg * Vd_filter - ωb / lg * V_dq[2] - ωb * rg / lg * Id_filter + + ωb * ω_sys * Iq_filter + ) #𝜕iq_o/𝜕t - output_ode[local_ix[6]] = - (ωb / lg * voq + ωb / lg * V_dq[1] - ωb * rg / lg * ioq - ωb * ω_sys * iod) + output_ode[local_ix[6]] = ( + ωb / lg * Vq_filter + ωb / lg * V_dq[1] - ωb * rg / lg * Iq_filter - + ωb * ω_sys * Id_filter + ) #Update inner_vars - get_inner_vars(device)[Vdo_var] = vod - get_inner_vars(device)[Vqo_var] = voq + get_inner_vars(device)[Vd_filter_var] = Vd_filter + get_inner_vars(device)[Vq_filter_var] = Vq_filter #TODO: If PLL models at PCC, need to update inner vars: - #get_inner_vars(device)[Vdo_var] = V_dq[q::dq_ref] - #get_inner_vars(device)[Vqo_var] = V_dq[q::dq_ref] + #get_inner_vars(device)[Vd_filter_var] = V_dq[q::dq_ref] + #get_inner_vars(device)[Vq_filter_var] = V_dq[q::dq_ref] - #Compute current from the generator to the grid - I_RI = (MVABase / sys_Sbase) * dq_ri(δ) * [iod; ioq] + #Compute current from the inverter to the grid + I_RI = (MVABase / sys_Sbase) * dq_ri(δ) * [Id_filter; Iq_filter] #Update current current_r[1] += I_RI[1] current_i[1] += I_RI[2] diff --git a/src/models/inverter_models/frequency_estimator_models.jl b/src/models/inverter_models/frequency_estimator_models.jl index 1c12246..e943424 100644 --- a/src/models/inverter_models/frequency_estimator_models.jl +++ b/src/models/inverter_models/frequency_estimator_models.jl @@ -3,25 +3,25 @@ function mdl_freq_estimator_ode!( output_ode, f0, ω_sys, - device::PSY.DynamicInverter{C, O, VC, DC, PSY.PLL, F}, + device::PSY.DynamicInverter{C, O, IC, DC, PSY.KauraPLL, F}, ) where { C <: PSY.Converter, O <: PSY.OuterControl, - VC <: PSY.VSControl, + IC <: PSY.InnerControl, DC <: PSY.DCSource, F <: PSY.Filter, } #Obtain external states inputs for component - external_ix = get_input_port_ix(device, PSY.PLL) - vod = device_states[external_ix[1]] - voq = device_states[external_ix[2]] - δθ_vsm = device_states[external_ix[3]] + external_ix = get_input_port_ix(device, PSY.KauraPLL) + Vd_filter = device_states[external_ix[1]] + Vq_filter = device_states[external_ix[2]] + θ_oc = device_states[external_ix[3]] #Obtain inner variables for component - #vod = device.inner_vars[Vdo_var] - #voq = device.inner_vars[Vqo_var] - #δθ_vsm = device.inner_vars[δdqRI_var] + #Vd_filter = device.inner_vars[Vd_filter_var] + #Vq_filter = device.inner_vars[Vq_filter_var] + #θ_oc = device.inner_vars[θ_oc_var] #Get parameters pll_control = PSY.get_freq_estimator(device) @@ -31,14 +31,14 @@ function mdl_freq_estimator_ode!( ωb = 2.0 * pi * f0 #Obtain indices for component w/r to device - local_ix = get_local_state_ix(device, PSY.PLL) + local_ix = get_local_state_ix(device, PSY.KauraPLL) #Define internal states for frequency estimator internal_states = @view device_states[local_ix] vpll_d = internal_states[1] vpll_q = internal_states[2] ϵ_pll = internal_states[3] - δθ_pll = internal_states[4] + θ_pll = internal_states[4] #Inputs (control signals) @@ -46,19 +46,19 @@ function mdl_freq_estimator_ode!( #Output Voltage LPF (internal state) #𝜕vpll_d/𝜕t, D'Arco ESPR122 eqn. 12 output_ode[local_ix[1]] = ( - ω_lp * vod * cos(δθ_pll - δθ_vsm) + ω_lp * voq * sin(δθ_pll - δθ_vsm) - + ω_lp * Vd_filter * cos(θ_pll - θ_oc) + ω_lp * Vq_filter * sin(θ_pll - θ_oc) - ω_lp * vpll_d ) #𝜕vpll_q/𝜕t, D'Arco ESPR122 eqn. 12 output_ode[local_ix[2]] = ( - -ω_lp * vod * sin(δθ_pll - δθ_vsm) + ω_lp * voq * cos(δθ_pll - δθ_vsm) - + -ω_lp * Vd_filter * sin(θ_pll - θ_oc) + ω_lp * Vq_filter * cos(θ_pll - θ_oc) - ω_lp * vpll_q ) #PI Integrator (internal state) #𝜕dϵ_pll/𝜕t, D'Arco ESPR122 eqn. 13 output_ode[local_ix[3]] = atan(vpll_q / vpll_d) #PLL Frequency Deviation (internal state) - #𝜕δθ_pll/𝜕t, D'Arco ESPR122 eqn. 15 + #𝜕θ_pll/𝜕t, D'Arco ESPR122 eqn. 15 output_ode[local_ix[4]] = (ωb * kp_pll * atan(vpll_q / vpll_d) + ωb * ki_pll * ϵ_pll) #Update inner_vars diff --git a/src/models/inverter_models/inner_control_models.jl b/src/models/inverter_models/inner_control_models.jl new file mode 100644 index 0000000..e7a1384 --- /dev/null +++ b/src/models/inverter_models/inner_control_models.jl @@ -0,0 +1,110 @@ +function mdl_inner_ode!( + device_states, + output_ode, + device::PSY.DynamicInverter{C, O, PSY.CurrentControl, DC, P, F}, +) where { + C <: PSY.Converter, + O <: PSY.OuterControl, + DC <: PSY.DCSource, + P <: PSY.FrequencyEstimator, + F <: PSY.Filter, +} + + #Obtain external states inputs for component + external_ix = get_input_port_ix(device, PSY.CurrentControl) + Id_filter = device_states[external_ix[1]] + Iq_filter = device_states[external_ix[2]] + Id_cnv = device_states[external_ix[3]] + Iq_cnv = device_states[external_ix[4]] + Vd_filter = device_states[external_ix[5]] #TODO: Should be inner reference after initialization + Vq_filter = device_states[external_ix[6]] #TODO: Should be inner reference after initialization + + #Obtain inner variables for component + #Vd_filter = get_inner_vars(device)[Vd_filter_var] + #Vq_filter = get_inner_vars(device)[Vq_filter_var] + ω_oc = get_inner_vars(device)[ω_oc_var] + v_refr = get_inner_vars(device)[V_oc_var] + vdc = get_inner_vars(device)[Vdc_var] + + #Get Voltage Controller parameters + inner_control = PSY.get_inner_control(device) + filter = PSY.get_filter(device) + kpv = PSY.get_kpv(inner_control) + kiv = PSY.get_kiv(inner_control) + kffi = PSY.get_kffi(inner_control) + cf = PSY.get_cf(filter) + rv = PSY.get_rv(inner_control) + lv = PSY.get_lv(inner_control) + + #Get Current Controller parameters + kpc = PSY.get_kpc(inner_control) + kic = PSY.get_kic(inner_control) + kffv = PSY.get_kffv(inner_control) + lf = PSY.get_lf(filter) + ωad = PSY.get_ωad(inner_control) + kad = PSY.get_kad(inner_control) + + #Obtain indices for component w/r to device + local_ix = get_local_state_ix(device, PSY.CurrentControl) + + #Define internal states for frequency estimator + internal_states = @view device_states[local_ix] + ξ_d = internal_states[1] + ξ_q = internal_states[2] + γ_d = internal_states[3] + γ_q = internal_states[4] + ϕ_d = internal_states[5] + ϕ_q = internal_states[6] + + #Inputs (control signals) + + ### Compute 6 states ODEs (D'Arco EPSR122 Model) ### + ## SRF Voltage Control w/ Virtual Impedance ## + #Virtual Impedance - Computation but not DAE + #v_refr = V_ref + kq*(q_ref - qm) + Vd_filter_ref = (v_refr - rv * Id_filter + ω_oc * lv * Iq_filter) + Vq_filter_ref = (-rv * Iq_filter - ω_oc * lv * Id_filter) + #Output Control Signal - Links to SRF Current Controller + Id_cnv_ref = ( + kpv * (Vd_filter_ref - Vd_filter) + kiv * ξ_d - cf * ω_oc * Vq_filter + + kffi * Id_filter + ) + + Iq_cnv_ref = ( + kpv * (Vq_filter_ref - Vq_filter) + + kiv * ξ_q + + cf * ω_oc * Vd_filter + + kffi * Iq_filter + ) + #Voltage Control ODEs + #PI Integrator (internal state) + output_ode[local_ix[1]] = (Vd_filter_ref - Vd_filter) + output_ode[local_ix[2]] = (Vq_filter_ref - Vq_filter) + + ## SRF Current Control ## + #Active Damping + #vad_d = kad*(Vd_filter-ϕ_d) + #vad_q = kad*(Vq_filter-ϕ_q) + #References for Converter Output Voltage + Vd_cnv_ref = ( + kpc * (Id_cnv_ref - Id_cnv) + kic * γ_d - ω_oc * lf * Iq_cnv + kffv * Vd_filter - kad * (Vd_filter - ϕ_d) + ) + Vq_cnv_ref = ( + kpc * (Iq_cnv_ref - Iq_cnv) + kic * γ_q + ω_oc * lf * Id_cnv + kffv * Vq_filter - kad * (Vq_filter - ϕ_q) + ) + #Modulation Commands to Converter + #md = Vd_cnv_ref/vdc + #mq = Vq_cnv_ref/vdc + #Current Control ODEs + #PI Integrator (internal state) + output_ode[local_ix[3]] = Id_cnv_ref - Id_cnv + output_ode[local_ix[4]] = Iq_cnv_ref - Iq_cnv + #Active Damping LPF (internal state) + output_ode[local_ix[5]] = ωad * Vd_filter - ωad * ϕ_d + output_ode[local_ix[6]] = ωad * Vq_filter - ωad * ϕ_q + + #Update inner_vars + get_inner_vars(device)[md_var] = Vd_cnv_ref / vdc + get_inner_vars(device)[mq_var] = Vq_cnv_ref / vdc + +end diff --git a/src/models/inverter_models/outer_control_models.jl b/src/models/inverter_models/outer_control_models.jl index 19b6104..ede1b74 100644 --- a/src/models/inverter_models/outer_control_models.jl +++ b/src/models/inverter_models/outer_control_models.jl @@ -5,15 +5,15 @@ function mdl_outer_ode!( ω_sys, device::PSY.DynamicInverter{ C, - PSY.VirtualInertiaQdroop{PSY.VirtualInertia, PSY.ReactivePowerDroop}, - VC, + PSY.OuterControl{PSY.VirtualInertia, PSY.ReactivePowerDroop}, + IC, DC, P, F, }, ) where { C <: PSY.Converter, - VC <: PSY.VSControl, + IC <: PSY.InnerControl, DC <: PSY.DCSource, P <: PSY.FrequencyEstimator, F <: PSY.Filter, @@ -22,21 +22,21 @@ function mdl_outer_ode!( #Obtain external states inputs for component external_ix = get_input_port_ix( device, - PSY.VirtualInertiaQdroop{PSY.VirtualInertia, PSY.ReactivePowerDroop}, + PSY.OuterControl{PSY.VirtualInertia, PSY.ReactivePowerDroop}, ) vpll_d = device_states[external_ix[1]] vpll_q = device_states[external_ix[2]] ϵ_pll = device_states[external_ix[3]] - vod = device_states[external_ix[4]] - voq = device_states[external_ix[5]] - iod = device_states[external_ix[6]] - ioq = device_states[external_ix[7]] + Vd_filter = device_states[external_ix[4]] #TODO: Should be inner reference after initialization + Vq_filter = device_states[external_ix[5]] #TODO: Should be inner reference after initialization + Id_filter = device_states[external_ix[6]] + Iq_filter = device_states[external_ix[7]] #Obtain inner variables for component ω_pll = get_inner_vars(device)[ω_freq_estimator_var] #Get Active Power Controller parameters - outer_control = PSY.get_outercontrol(device) + outer_control = PSY.get_outer_control(device) active_power_control = PSY.get_active_power(outer_control) Ta = PSY.get_Ta(active_power_control) #VSM Inertia constant kd = PSY.get_kd(active_power_control) #VSM damping constant @@ -60,28 +60,34 @@ function mdl_outer_ode!( #Obtain indices for component w/r to device local_ix = get_local_state_ix( device, - PSY.VirtualInertiaQdroop{PSY.VirtualInertia, PSY.ReactivePowerDroop}, + PSY.OuterControl{PSY.VirtualInertia, PSY.ReactivePowerDroop}, ) #Define internal states for frequency estimator internal_states = @view device_states[local_ix] - δω_vsm = internal_states[1] - δθ_vsm = internal_states[2] + ω_oc = internal_states[1] + θ_oc = internal_states[2] qm = internal_states[3] + #Obtain additional expressions + p_elec_out = Id_filter * Vd_filter + Iq_filter * Vq_filter + #Compute 3 states ODEs - output_ode[local_ix[1]] = ( - -iod * vod / Ta - ioq * voq / Ta + - kd * kp_pll * atan(vpll_q / vpll_d) / Ta + - kd * ki_pll * ϵ_pll / Ta - (kd + kω) * δω_vsm / Ta + - p_ref / Ta + - kω * ω_ref / Ta - kω * ω_sys / Ta - ) - output_ode[local_ix[2]] = ωb * δω_vsm - output_ode[local_ix[3]] = (-ωf * ioq * vod + ωf * iod * voq - ωf * qm) + output_ode[local_ix[1]] = + (p_ref / Ta - p_elec_out / Ta - kd * (ω_oc - ω_pll) / Ta - kω * (ω_oc - ω_ref) / Ta) + output_ode[local_ix[2]] = ωb * (ω_oc - ω_sys) + output_ode[local_ix[3]] = + (-ωf * Iq_filter * Vd_filter + ωf * Id_filter * Vq_filter - ωf * qm) #Update inner vars - get_inner_vars(device)[δdqRI_var] = δθ_vsm - get_inner_vars(device)[ω_control_var] = δω_vsm + 1.0 - get_inner_vars(device)[v_control_var] = V_ref + kq * (q_ref - qm) + get_inner_vars(device)[θ_oc_var] = θ_oc + get_inner_vars(device)[ω_oc_var] = ω_oc + get_inner_vars(device)[V_oc_var] = V_ref + kq * (q_ref - qm) end +#output_ode[local_ix[1]] = ( +# -Id_filter * Vd_filter / Ta - Iq_filter * Vq_filter / Ta + +# kd * kp_pll * atan(vpll_q / vpll_d) / Ta + +# kd * ki_pll * ϵ_pll / Ta - (kd + kω) * (ω_oc - ω_sys) / Ta + +# p_ref / Ta + +# kω * ω_ref / Ta - kω * ω_sys / Ta +#) diff --git a/src/models/inverter_models/voltage_source_control_models.jl b/src/models/inverter_models/voltage_source_control_models.jl deleted file mode 100644 index 16f1a77..0000000 --- a/src/models/inverter_models/voltage_source_control_models.jl +++ /dev/null @@ -1,104 +0,0 @@ -function mdl_VScontrol_ode!( - device_states, - output_ode, - device::PSY.DynamicInverter{C, O, PSY.CombinedVIwithVZ, DC, P, F}, -) where { - C <: PSY.Converter, - O <: PSY.OuterControl, - DC <: PSY.DCSource, - P <: PSY.FrequencyEstimator, - F <: PSY.Filter, -} - - #Obtain external states inputs for component - external_ix = get_input_port_ix(device, PSY.CombinedVIwithVZ) - iod = device_states[external_ix[1]] #TODO: Should be var referemce? - ioq = device_states[external_ix[2]] #TODO: Should be state reference? - icvd = device_states[external_ix[3]] - icvq = device_states[external_ix[4]] - vod = device_states[external_ix[5]] - voq = device_states[external_ix[6]] - - #Obtain inner variables for component - #vod = get_inner_vars(device)[Vdo_var] #TODO: Should be state reference? - #voq = get_inner_vars(device)[Vqo_var] #TODO: Should be state reference? - ω_vsm = get_inner_vars(device)[ω_control_var] - v_refr = get_inner_vars(device)[v_control_var] - vdc = get_inner_vars(device)[Vdc_var] - - #Get Voltage Controller parameters - vscontrol = PSY.get_vscontrol(device) - filter = PSY.get_filter(device) - kpv = PSY.get_kpv(vscontrol) - kiv = PSY.get_kiv(vscontrol) - kffi = PSY.get_kffi(vscontrol) - cf = PSY.get_cf(filter) - rv = PSY.get_rv(vscontrol) - lv = PSY.get_lv(vscontrol) - - #Get Current Controller parameters - kpc = PSY.get_kpc(vscontrol) - kic = PSY.get_kic(vscontrol) - kffv = PSY.get_kffv(vscontrol) - lf = PSY.get_lf(filter) - ωad = PSY.get_ωad(vscontrol) - kad = PSY.get_kad(vscontrol) - - #Obtain indices for component w/r to device - local_ix = get_local_state_ix(device, PSY.CombinedVIwithVZ) - - #Define internal states for frequency estimator - internal_states = @view device_states[local_ix] - ξ_d = internal_states[1] - ξ_q = internal_states[2] - γ_d = internal_states[3] - γ_q = internal_states[4] - ϕ_d = internal_states[5] - ϕ_q = internal_states[6] - - #Inputs (control signals) - - ### Compute 6 states ODEs (D'Arco EPSR122 Model) ### - ## SRF Voltage Control w/ Virtual Impedance ## - #Virtual Impedance - Computation but not DAE - #v_refr = V_ref + kq*(q_ref - qm) - vod_ref = (v_refr - rv * iod + ω_vsm * lv * ioq) - voq_ref = (-rv * ioq - ω_vsm * lv * iod) - #Output Control Signal - Links to SRF Current Controller - icvd_ref = (kpv * (vod_ref - vod) + kiv * ξ_d - cf * ω_vsm * voq + kffi * iod) - - icvq_ref = (kpv * (voq_ref - voq) + kiv * ξ_q + cf * ω_vsm * vod + kffi * ioq) - #Voltage Control ODEs - #PI Integrator (internal state) - output_ode[local_ix[1]] = (vod_ref - vod) - output_ode[local_ix[2]] = (voq_ref - voq) - - ## SRF Current Control ## - #Active Damping - #vad_d = kad*(vod-ϕ_d) - #vad_q = kad*(voq-ϕ_q) - #References for Converter Output Voltage - vcvd_ref = ( - kpc * (icvd_ref - icvd) + kic * γ_d - ω_vsm * lf * icvq + kffv * vod - - kad * (vod - ϕ_d) - ) - vcvq_ref = ( - kpc * (icvq_ref - icvq) + kic * γ_q + ω_vsm * lf * icvd + kffv * voq - - kad * (voq - ϕ_q) - ) - #Modulation Commands to Converter - #md = vcvd_ref/vdc - #mq = vcvq_ref/vdc - #Current Control ODEs - #PI Integrator (internal state) - output_ode[local_ix[3]] = icvd_ref - icvd - output_ode[local_ix[4]] = icvq_ref - icvq - #Active Damping LPF (internal state) - output_ode[local_ix[5]] = ωad * vod - ωad * ϕ_d - output_ode[local_ix[6]] = ωad * voq - ωad * ϕ_q - - #Update inner_vars - get_inner_vars(device)[md_var] = vcvd_ref / vdc - get_inner_vars(device)[mq_var] = vcvq_ref / vdc - -end diff --git a/src/models/kcl.jl b/src/models/kcl.jl deleted file mode 100644 index bc67f8d..0000000 --- a/src/models/kcl.jl +++ /dev/null @@ -1,13 +0,0 @@ -""" - Kirchoff current law for buses. - I_gen_r[j]: Real current injection from all generators connected at bus j. - It is zero if no generator is connected at bus j. - I_gen_i[j]: Real current injection from all generators connected at bus j. - It is zero if no generator is connected at bus j. - -""" - -function kcl(Ybus, V_r, V_i, I_injections_r, I_injections_i) - I_bus = Ybus * (V_r + V_i .* 1im) - return [I_injections_r - real(I_bus); I_injections_i - imag(I_bus)] -end diff --git a/src/models/kirchoff_laws.jl b/src/models/kirchoff_laws.jl new file mode 100644 index 0000000..22c9d3d --- /dev/null +++ b/src/models/kirchoff_laws.jl @@ -0,0 +1,32 @@ +""" + Kirchoff law for buses. + I_gen_r[j]: Real current injection from all generators connected at bus j. + It is zero if no generator is connected at bus j. + I_gen_i[j]: Real current injection from all generators connected at bus j. + It is zero if no generator is connected at bus j. + +""" +function kirchoff_laws(sys, V_r, V_i, I_injections_r, I_injections_i, dx) + Ybus = PSY.get_ext(sys)[YBUS] + # TODO: Make more efficent calculation here. Note: BLAS doesn't work because the the type of Vr and Vi is not Matrix of Complex + I_bus = Ybus * (V_r + V_i .* 1im) + I_balance = [real(I_bus) - I_injections_r; imag(I_bus) - I_injections_i] + + voltage_buses = get_voltage_bus_no(sys) + isempty(voltage_buses) && return I_balance + + sys_f = PSY.get_frequency(sys) + ω_b = 2.0 * π * sys_f + n_buses = length(PSY.get_components(PSY.Bus, sys)) + shunts = get_total_shunts(sys) + for bus_no in voltage_buses + shunt_multiplier = shunts[bus_no] + I_balance[bus_no] = + -ω_b * I_balance[bus_no] * shunt_multiplier + ω_b * V_i[bus_no] - dx[bus_no] + I_balance[bus_no + n_buses] = + -ω_b * I_balance[bus_no + n_buses] * shunt_multiplier - ω_b * V_r[bus_no] - + dx[bus_no + n_buses] + end + + return I_balance +end diff --git a/src/models/system.jl b/src/models/system.jl index cb27f88..4950147 100644 --- a/src/models/system.jl +++ b/src/models/system.jl @@ -21,6 +21,7 @@ function system!(out::Vector{T}, dx, x, sys, t) where {T <: Real} V_r = @view x[1:bus_size] V_i = @view x[(bus_size + 1):bus_vars_count] Sbase = PSY.get_basepower(sys) + # TODO: Don't create this matrices here every iteration I_injections_r = zeros(T, bus_size) I_injections_i = zeros(T, bus_size) injection_ode = zeros(T, get_n_injection_states(sys)) @@ -48,6 +49,19 @@ function system!(out::Vector{T}, dx, x, sys, t) where {T <: Real} out[ix_range] = injection_ode[ode_range] - dx[ix_range] end + for d in PSY.get_components(PSY.StaticInjection, sys) + bus_n = PSY.get_number(PSY.get_bus(d)) + + device!( + view(V_r, bus_n), + view(V_i, bus_n), + view(I_injections_r, bus_n), + view(I_injections_i, bus_n), + d, + sys, + ) + end + dyn_branches = PSY.get_components(DynamicLine, sys) if !(isempty(dyn_branches)) for br in dyn_branches @@ -55,12 +69,6 @@ function system!(out::Vector{T}, dx, x, sys, t) where {T <: Real} n_states = PSY.get_n_states(br) from_bus_number = PSY.get_number(arc.from) to_bus_number = PSY.get_number(arc.to) - ix_dx = [ - from_bus_number, - from_bus_number + bus_size, - to_bus_number, - to_bus_number + bus_size, - ] ix_range = range(branches_start, length = n_states) ode_range = range(branches_count, length = n_states) branches_count = branches_count + n_states @@ -79,7 +87,6 @@ function system!(out::Vector{T}, dx, x, sys, t) where {T <: Real} view(I_injections_r, to_bus_number), view(I_injections_i, to_bus_number), ix_range, - ix_dx, ode_range, br, sys, @@ -88,19 +95,6 @@ function system!(out::Vector{T}, dx, x, sys, t) where {T <: Real} end end - for d in PSY.get_components(PSY.StaticInjection, sys) - bus_n = PSY.get_number(PSY.get_bus(d)) - - device!( - view(V_r, bus_n), - view(V_i, bus_n), - view(I_injections_r, bus_n), - view(I_injections_i, bus_n), - d, - sys, - ) - end - - out[bus_range] = kcl(PSY.get_ext(sys)[YBUS], V_r, V_i, I_injections_r, I_injections_i) + out[bus_range] = kirchoff_laws(sys, V_r, V_i, I_injections_r, I_injections_i, dx) end diff --git a/src/utils/immutable_dicts.jl b/src/utils/immutable_dicts.jl new file mode 100644 index 0000000..fa3b08f --- /dev/null +++ b/src/utils/immutable_dicts.jl @@ -0,0 +1,7 @@ +function Base.ImmutableDict(KV::Pair{K, V}, KVs::Pair{K, V}...) where {K, V} + d = Base.ImmutableDict(KV) + for p in KVs + d = Base.ImmutableDict(d, p) + end + return d +end diff --git a/test/data_tests/dynamic_test_data.jl b/test/data_tests/dynamic_test_data.jl index 1abd815..87bb73d 100644 --- a/test/data_tests/dynamic_test_data.jl +++ b/test/data_tests/dynamic_test_data.jl @@ -444,12 +444,12 @@ end ###### Converter Data ###### -converter_DAIB() = PSY.AvgCnvFixedDC( +converter_DAIB() = PSY.AverageConverter( 690.0, #Rated Voltage 2.75, ) #Rated MVA -converter_case78() = PSY.AvgCnvFixedDC( +converter_case78() = PSY.AverageConverter( 138.0, #Rated Voltage 100.0, ) #Rated MVA @@ -472,7 +472,7 @@ filter_test() = PSY.LCLFilter( ###### PLL Data ###### -pll_test() = PSY.PLL( +pll_test() = PSY.KauraPLL( 500.0, #ω_lp: Cut-off frequency for LowPass filter of PLL filter. 0.084, #k_p: PLL proportional gain 4.69, @@ -492,12 +492,11 @@ reactive_droop_test() = PSY.ReactivePowerDroop( 1000.0, ) #ωf:: Reactive power cut-off low pass filter frequency -outer_control_test() = - PSY.VirtualInertiaQdroop(virtual_inertia_test(), reactive_droop_test()) +outer_control_test() = PSY.OuterControl(virtual_inertia_test(), reactive_droop_test()) ######## Inner Control ###### -vsc_test() = PSY.CombinedVIwithVZ( +vsc_test() = PSY.CurrentControl( 0.59, #kpv:: Voltage controller proportional gain 736.0, #kiv:: Voltage controller integral gain 0.0, #kffv:: Binary variable enabling the voltage feed-forward in output of current controllers @@ -550,6 +549,25 @@ function inv_case78(nodes) ) #filter end +function inv_case9(nodes) + return PSY.DynamicInverter( + 1, #Number + "DARCO", #name + nodes[3], #bus + 1.0, # ω_ref, + 0.8, #V_ref + 0.5, #P_ref + -0.3, #Q_ref + 100.0, #MVABase + converter_case78(), #converter + outer_control_test(), #outer control + vsc_test(), #inner control voltage source + dc_source_case78(), #dc source + pll_test(), #pll + filter_test(), + ) #filter +end + ###################################### ######## System Constructors ######### ###################################### diff --git a/test/test_case06_DAIB.jl b/test/test_case06_DAIB.jl index 8b6f2d1..fa4e763 100644 --- a/test/test_case06_DAIB.jl +++ b/test/test_case06_DAIB.jl @@ -40,8 +40,8 @@ x0_guess = [ 1.0648, #V2_R 0.0, #V1_I 0.001, #V2_I - 0.0, #δω_vsm - 0.2, #δθ_vsm + 1.0, #ω_oc + 0.2, #θ_oc 0.025, #qm 0.0015, #ξ_d -0.07, #ξ_q @@ -52,14 +52,14 @@ x0_guess = [ 1.004, #vpll_d 0.0, #vpll_q 0.0, #ε_pll - 0.1, #δθ_pll + 0.1, #θ_pll 0.5, #id_cv 0.0, #iq_cv - 0.95, #vod - -0.1, #voq - 0.49, #iod + 0.95, #Vd_filter + -0.1, #Vq_filter + 0.49, #Id_filter -0.1, -] #ioq +] #Iq_filter #Define Fault using Callbacks Pref_change = LITS.ControlReferenceChange(1.0, Darco_Inverter, LITS.P_ref_index, 0.7) @@ -71,6 +71,6 @@ sim = LITS.Simulation(sys, tspan, Pref_change, initial_guess = x0_guess) run_simulation!(sim, Sundials.IDA()); #Obtain data for angles -series = get_state_series(sim, ("DARCO", :δω_vsm)) +series = get_state_series(sim, ("DARCO", :ω_oc)) @test sim.solution.retcode == :Success diff --git a/test/test_case07_4th_order_Inverter.jl b/test/test_case07_4th_order_Inverter.jl index f78d8eb..ac8c284 100644 --- a/test/test_case07_4th_order_Inverter.jl +++ b/test/test_case07_4th_order_Inverter.jl @@ -57,8 +57,8 @@ x0_guess = [ 0.0, #V1_I -0.01, #V2_I -0.01, #V3_I - 0.0, #δω_vsm - 0.2, #δθ_vsm + 1.0, #ω_oc + 0.2, #θ_oc 0.025, #qm 0.0015, #ξ_d -0.07, #ξ_q @@ -69,13 +69,13 @@ x0_guess = [ 1.004, #vpll_d 0.0, #vpll_q 0.0, #ε_pll - 0.1, #δθ_pll + 0.1, #θ_pll 0.5, #id_cv 0.0, #iq_cv - 0.95, #vod - -0.1, #voq - 0.49, #iod - -0.1, #ioq + 0.95, #Vd_filter + -0.1, #Vq_filter + 0.49, #Id_filter + -0.1, #Iq_filter 1.0, #eq_p 0.47, #ed_p 0.6, #δ diff --git a/test/test_case08_staticbranches.jl b/test/test_case08_staticbranches.jl index 35bf1d6..9198239 100644 --- a/test/test_case08_staticbranches.jl +++ b/test/test_case08_staticbranches.jl @@ -29,7 +29,7 @@ case8_gen = dyn_gen_case8(nodes_case8) ############### Inverter Data ######################## -case8_inv = inv_case78(nodes_case8) +case8_inv = inv_case9(nodes_case8) ######################### Dynamical System ######################## @@ -61,8 +61,8 @@ x0_guess = [ 0.0, #V1_I -0.01, #V2_I -0.01, #V3_I - 0.0, #δω_vsm - 0.2, #δθ_vsm + 1.0, #ω_oc + 0.2, #θ_oc 0.025, #qm 0.0015, #ξ_d -0.07, #ξ_q @@ -73,13 +73,13 @@ x0_guess = [ 1.004, #vpll_d 0.0, #vpll_q 0.0, #ε_pll - 0.1, #δθ_pll + 0.1, #θ_pll 0.5, #id_cv 0.0, #iq_cv - 0.95, #vod - -0.1, #voq - 0.49, #iod - -0.1, #ioq + 0.95, #Vd_filter + -0.1, #Vq_filter + 0.49, #Id_filter + -0.1, #Iq_filter 1.0, #eq_p 0.47, #ed_p 0.6, #δ diff --git a/test/test_case09_dynbranches.jl b/test/test_case09_dynbranches.jl index d52bfe6..cf5a06a 100644 --- a/test/test_case09_dynbranches.jl +++ b/test/test_case09_dynbranches.jl @@ -30,7 +30,7 @@ case9_gen = dyn_gen_case9(nodes_case9) ############### Inverter Data ######################## -case9_inv = inv_case78(nodes_case9) +case9_inv = inv_case9(nodes_case9) ######################### Dynamical System ######################## @@ -61,8 +61,8 @@ x0_guess = [ 0.0, #V1_I -0.01, #V2_I -0.01, #V3_I - 0.0, #δω_vsm - 0.2, #δθ_vsm + 1.0, #ω_oc + 0.2, #θ_oc 0.025, #qm 0.0015, #ξ_d -0.07, #ξ_q @@ -73,13 +73,13 @@ x0_guess = [ 1.004, #vpll_d 0.0, #vpll_q 0.0, #ε_pll - 0.1, #δθ_pll + 0.1, #θ_pll 0.5, #id_cv 0.0, #iq_cv - 0.95, #vod - -0.1, #voq - 0.49, #iod - -0.1, #ioq + 0.95, #Vd_filter + -0.1, #Vq_filter + 0.49, #Id_filter + -0.1, #Iq_filter 1.0, #eq_p 0.47, #ed_p 0.6, #δ