Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

add DEGOV #357

Merged
merged 39 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e35ddcf
add degov
m-bossart Nov 17, 2023
e9b5afe
work in progress (add delays)
m-bossart Dec 11, 2023
b51c6c2
add docstring for small_signal_analysis
m-bossart Dec 14, 2023
daa2291
add simple docs page
m-bossart Dec 14, 2023
695b146
add more docs
m-bossart Dec 15, 2023
faf0f99
more changes
m-bossart Dec 15, 2023
2e8b6f4
add bus_size to DW
m-bossart Dec 15, 2023
e2984fc
add degov
m-bossart Nov 17, 2023
2dd5f48
work in progress (add delays)
m-bossart Dec 11, 2023
27f1a5f
add docstring for small_signal_analysis
m-bossart Dec 14, 2023
65c8584
add simple docs page
m-bossart Dec 14, 2023
ac13666
add more docs
m-bossart Dec 15, 2023
fb6944c
more changes
m-bossart Dec 15, 2023
381072b
add bus_size to DW
m-bossart Dec 15, 2023
aa8e71c
Merge branch 'mb/add-degov' of https://github.com/NREL-SIIP/PowerSimu…
m-bossart Dec 15, 2023
13ef341
fix test in base
m-bossart Dec 15, 2023
376c895
update Project.toml
m-bossart Dec 15, 2023
87f2d4a
Revert "update Project.toml"
m-bossart Dec 15, 2023
f336ece
get delays as a vector
m-bossart Jan 5, 2024
01d22b3
add degov
m-bossart Nov 17, 2023
a931f7e
work in progress (add delays)
m-bossart Dec 11, 2023
4819dbf
add docstring for small_signal_analysis
m-bossart Dec 14, 2023
7379506
add simple docs page
m-bossart Dec 14, 2023
785b958
add more docs
m-bossart Dec 15, 2023
de7384b
more changes
m-bossart Dec 15, 2023
8ff8c52
add bus_size to DW
m-bossart Dec 15, 2023
dc90462
work in progress (add delays)
m-bossart Dec 11, 2023
1d1be0b
add simple docs page
m-bossart Dec 14, 2023
89f0b04
add more docs
m-bossart Dec 15, 2023
f89584b
more changes
m-bossart Dec 15, 2023
c3b43f8
add bus_size to DW
m-bossart Dec 15, 2023
9733701
fix test in base
m-bossart Dec 15, 2023
b172e63
update Project.toml
m-bossart Dec 15, 2023
26a5ba8
Revert "update Project.toml"
m-bossart Dec 15, 2023
f734691
get delays as a vector
m-bossart Jan 5, 2024
c1f8076
Merge branch 'mb/add-degov' of https://github.com/NREL-SIIP/PowerSimu…
m-bossart Jan 5, 2024
648b412
fix jacobian change
m-bossart Jan 5, 2024
8a2cff8
scimlbase 2
m-bossart Jan 5, 2024
8fdaa22
remove repeated function
m-bossart Jan 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ makedocs(;
"Small Signal" => "small.md",
"Reference Frames" => "reference_frames.md",
"Perturbations" => "perturbations.md",
"Time Delays" => "time_delays.md",
"Industrial Renewable Models" => "generic.md",
"Generator Component Library" => Any[
"Machine" => "component_models/machines.md",
Expand Down
22 changes: 22 additions & 0 deletions docs/src/time_delays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Delays

PowerSimulationsDynamics supports models with constant delays in a mass matrix formulation:

```math
\begin{align}
M\frac{dx(t)}{dt} = f(x(t), x(t-\tau_1), ... , x(t-\tau_N))
\end{align}
```

For more information on solving such models, refer to the documentation for [DelayDiffEq.jl](https://github.com/SciML/DelayDiffEq.jl) package.

The following models include time delays:

* `DEGOV`

There is currently limited support for including models with time delays. The following limitations apply:

* Only constant delays are supported (state dependent delays are not).
* System models with delays must use `MassMatrixModel` formulation (`ResidualModel` is not currently compatible).
* System models with delays are not compatible with small signal analysis tools.
* The system formulation with delays is not compatible with automatic differentiation for calculating the gradient with respect to time. The setting `autodiff=false` should be set when passing the solver (e.g. `MethodofSteps(Rodas5(autodiff=false))`).
15 changes: 15 additions & 0 deletions src/base/device_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ index(::Type{<:PSY.InnerControl}) = 4
index(::Type{<:PSY.Converter}) = 5
index(::Type{<:PSY.Filter}) = 6

get_delays(::PSY.DynamicInjection) = nothing
get_delays(
dynamic_injector::PSY.DynamicGenerator{M, S, A, PSY.DEGOV, P},
) where {M <: PSY.Machine, S <: PSY.Shaft, A <: PSY.AVR, P <: PSY.PSS} =
[PSY.get_Td(PSY.get_prime_mover(dynamic_injector))]

"""
Wraps DynamicInjection devices from PowerSystems to handle changes in controls and connection
status, and allocate the required indexes of the state space.
Expand All @@ -39,6 +45,7 @@ struct DynamicWrapper{T <: PSY.DynamicInjection}
ix_range::Vector{Int}
ode_range::Vector{Int}
bus_ix::Int
bus_size::Int
global_index::Base.ImmutableDict{Symbol, Int}
component_state_mapping::Base.ImmutableDict{Int, Vector{Int}}
input_port_mapping::Base.ImmutableDict{Int, Vector{Int}}
Expand All @@ -59,6 +66,7 @@ struct DynamicWrapper{T <: PSY.DynamicInjection}
ix_range,
ode_range,
bus_ix::Int,
bus_size::Int,
global_index::Base.ImmutableDict{Symbol, Int},
component_state_mapping::Base.ImmutableDict{Int, Vector{Int}},
input_port_mapping::Base.ImmutableDict{Int, Vector{Int}},
Expand All @@ -81,6 +89,7 @@ struct DynamicWrapper{T <: PSY.DynamicInjection}
Vector{Int}(ix_range),
Vector{Int}(ode_range),
bus_ix,
bus_size,
global_index,
component_state_mapping,
input_port_mapping,
Expand Down Expand Up @@ -113,6 +122,7 @@ function DynamicWrapper(
device::T,
dynamic_device::D,
bus_ix::Int,
bus_size::Int,
ix_range,
ode_range,
inner_var_range,
Expand Down Expand Up @@ -146,6 +156,7 @@ function DynamicWrapper(
ix_range,
ode_range,
bus_ix,
bus_size,
Base.ImmutableDict(
sort!(device_states .=> ix_range; by = x -> x.second, rev = true)...,
),
Expand All @@ -167,6 +178,7 @@ function DynamicWrapper(
device::PSY.ThermalStandard,
dynamic_device::PSY.AggregateDistributedGenerationA,
bus_ix::Int,
bus_size::Int,
ix_range,
ode_range,
inner_var_range,
Expand Down Expand Up @@ -194,6 +206,7 @@ function DynamicWrapper(
ix_range,
ode_range,
bus_ix,
bus_size,
Base.ImmutableDict(
sort!(device_states .=> ix_range; by = x -> x.second, rev = true)...,
),
Expand All @@ -215,6 +228,7 @@ function DynamicWrapper(
device::PSY.Source,
dynamic_device::D,
bus_ix::Int,
bus_size::Int,
ix_range,
ode_range,
inner_var_range,
Expand All @@ -240,6 +254,7 @@ function DynamicWrapper(
collect(ix_range),
collect(ode_range),
bus_ix,
bus_size,
Base.ImmutableDict(Dict(device_states .=> ix_range)...),
Base.ImmutableDict{Int, Vector{Int}}(),
Base.ImmutableDict{Int, Vector{Int}}(),
Expand Down
98 changes: 91 additions & 7 deletions src/base/jacobian.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# in SparseDiffTools

struct JacobianFunctionWrapper{
D <: DelayModel,
F,
T <: Union{Matrix{Float64}, SparseArrays.SparseMatrixCSC{Float64, Int64}},
} <: Function
Expand All @@ -13,12 +14,12 @@ struct JacobianFunctionWrapper{
end

# This version of the function is type unstable should only be used for non-critial ops
function (J::JacobianFunctionWrapper)(x::AbstractVector{Float64})
function (J::JacobianFunctionWrapper{NoDelays})(x::AbstractVector{Float64})
J.x .= x
return J.Jf(J.Jv, x)
end

function (J::JacobianFunctionWrapper)(
function (J::JacobianFunctionWrapper{NoDelays})(
JM::U,
x::AbstractVector{Float64},
) where {U <: Union{Matrix{Float64}, SparseArrays.SparseMatrixCSC{Float64, Int64}}}
Expand All @@ -27,7 +28,17 @@ function (J::JacobianFunctionWrapper)(
return
end

function (J::JacobianFunctionWrapper)(
function (J::JacobianFunctionWrapper{HasDelays})(
JM::U,
x::AbstractVector{Float64},
) where {U <: Union{Matrix{Float64}, SparseArrays.SparseMatrixCSC{Float64, Int64}}}
h(p, t; idxs = nothing) = typeof(idxs) <: Number ? x[idxs] : x
J.x .= x
J.Jf(JM, x, h, 0.0)
return
end

function (J::JacobianFunctionWrapper{NoDelays})(
JM::U,
x::AbstractVector{Float64},
p,
Expand All @@ -37,7 +48,29 @@ function (J::JacobianFunctionWrapper)(
return
end

function (J::JacobianFunctionWrapper)(
function (J::JacobianFunctionWrapper{HasDelays})(
JM::U,
x::AbstractVector{Float64},
h,
p,
t,
) where {U <: Union{Matrix{Float64}, SparseArrays.SparseMatrixCSC{Float64, Int64}}}
J.Jf(JM, x, h, t)
return
end

function (J::JacobianFunctionWrapper{NoDelays})(
JM::U,
x::AbstractVector{Float64},
h,
p,
t,
) where {U <: Union{Matrix{Float64}, SparseArrays.SparseMatrixCSC{Float64, Int64}}}
J.Jf(JM, x, h, t)
return
end

function (J::JacobianFunctionWrapper{NoDelays})(
JM::U,
dx::AbstractVector{Float64},
x::AbstractVector{Float64},
Expand All @@ -53,7 +86,7 @@ function (J::JacobianFunctionWrapper)(
end

function JacobianFunctionWrapper(
m!::SystemModel{MassMatrixModel},
m!::SystemModel{MassMatrixModel, NoDelays},
x0_guess::Vector{Float64};
# Improve the heuristic to do sparsity detection
sparse_retrieve_loop::Int = 0, #max(3, length(x0_guess) ÷ 100),
Expand Down Expand Up @@ -84,7 +117,12 @@ function JacobianFunctionWrapper(
end
Jf(Jv, x0)
mass_matrix = get_mass_matrix(m!.inputs)
return JacobianFunctionWrapper{typeof(Jf), typeof(Jv)}(Jf, Jv, x0, mass_matrix)
return JacobianFunctionWrapper{NoDelays, typeof(Jf), typeof(Jv)}(
Jf,
Jv,
x0,
mass_matrix,
)
end

function JacobianFunctionWrapper(
Expand Down Expand Up @@ -119,7 +157,53 @@ function JacobianFunctionWrapper(
end
Jf(Jv, x0)
mass_matrix = get_mass_matrix(m!.inputs)
return JacobianFunctionWrapper{typeof(Jf), typeof(Jv)}(Jf, Jv, x0, mass_matrix)
return JacobianFunctionWrapper{NoDelays, typeof(Jf), typeof(Jv)}(
Jf,
Jv,
x0,
mass_matrix,
)
end

function JacobianFunctionWrapper(
m!::SystemModel{MassMatrixModel, HasDelays},
x0_guess::Vector{Float64};
# Improve the heuristic to do sparsity detection
sparse_retrieve_loop::Int = 0, #max(3, length(x0_guess) ÷ 100),
)
x0 = deepcopy(x0_guess)
n = length(x0)
Jf =
(Jv, x, h, t) -> begin
@debug "Evaluating Jacobian Function"
m_ = (residual, x) -> m!(residual, x, h, nothing, t)
jconfig =
ForwardDiff.JacobianConfig(m_, similar(x0), x0, ForwardDiff.Chunk(x0))
ForwardDiff.jacobian!(Jv, m_, zeros(n), x, jconfig)
return
end
jac = zeros(n, n)
if sparse_retrieve_loop > 0
for _ in 1:sparse_retrieve_loop
temp = zeros(n, n)
rng_state = copy(Random.default_rng())
Jf(temp, x0 + Random.randn(n))
copy!(Random.default_rng(), rng_state)
jac .+= abs.(temp)
end
Jv = SparseArrays.sparse(jac)
elseif sparse_retrieve_loop == 0
Jv = jac
else
throw(IS.ConflictingInputsError("negative sparse_retrieve_loop not valid"))
end
mass_matrix = get_mass_matrix(m!.inputs)
return JacobianFunctionWrapper{HasDelays, typeof(Jf), typeof(Jv)}(
Jf,
Jv,
x0,
mass_matrix,
)
end

function get_jacobian(
Expand Down
18 changes: 16 additions & 2 deletions src/base/nlsolve_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,25 @@ NLsolveWrapper() = NLsolveWrapper(Vector{Float64}(), false, true)
converged(sol::NLsolveWrapper) = sol.converged
failed(sol::NLsolveWrapper) = sol.failed

function _get_model_closure(model::SystemModel{MassMatrixModel}, ::Vector{Float64})
function _get_model_closure(
model::SystemModel{MassMatrixModel, NoDelays},
::Vector{Float64},
)
return (residual, x) -> model(residual, x, nothing, 0.0)
end

function _get_model_closure(model::SystemModel{ResidualModel}, x0::Vector{Float64})
function _get_model_closure(
model::SystemModel{MassMatrixModel, HasDelays},
x0::Vector{Float64},
)
h(p, t; idxs = nothing) = typeof(idxs) <: Number ? x0[idxs] : x0
return (residual, x) -> model(residual, x, h, nothing, 0.0)
end

function _get_model_closure(
model::SystemModel{ResidualModel, NoDelays},
x0::Vector{Float64},
)
dx0 = zeros(length(x0))
return (residual, x) -> model(residual, dx0, x, nothing, 0.0)
end
Expand Down
42 changes: 38 additions & 4 deletions src/base/simulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,10 @@ end
function _get_jacobian(sim::Simulation{MassMatrixModel})
inputs = get_simulation_inputs(sim)
x0_init = get_initial_conditions(sim)
return JacobianFunctionWrapper(MassMatrixModel(inputs, x0_init, JacobianCache), x0_init)
return JacobianFunctionWrapper(
MassMatrixModel(inputs, x0_init, JacobianCache),
x0_init,
)
end

function _build_perturbations!(sim::Simulation)
Expand Down Expand Up @@ -329,7 +332,7 @@ end

function _get_diffeq_problem(
sim::Simulation,
model::SystemModel{ResidualModel},
model::SystemModel{ResidualModel, NoDelays},
jacobian::JacobianFunctionWrapper,
)
x0 = get_initial_conditions(sim)
Expand All @@ -354,7 +357,7 @@ end

function _get_diffeq_problem(
sim::Simulation,
model::SystemModel{MassMatrixModel},
model::SystemModel{MassMatrixModel, NoDelays},
jacobian::JacobianFunctionWrapper,
)
simulation_inputs = get_simulation_inputs(sim)
Expand All @@ -375,6 +378,37 @@ function _get_diffeq_problem(
return
end

function get_history_function(simulation_inputs)
x0 = get_initial_conditions(simulation_inputs)
h(p, t; idxs = nothing) = typeof(idxs) <: Number ? x0[idxs] : x0
return h
end

function _get_diffeq_problem(
sim::Simulation,
model::SystemModel{MassMatrixModel, HasDelays},
jacobian::JacobianFunctionWrapper,
)
simulation_inputs = get_simulation_inputs(sim)
h = get_history_function(sim)
sim.problem = SciMLBase.DDEProblem(
SciMLBase.DDEFunction{true}(
model;
mass_matrix = get_mass_matrix(simulation_inputs),
jac = jacobian,
jac_prototype = jacobian.Jv,
),
sim.x0_init,
h,
get_tspan(sim),
simulation_inputs;
constant_lags = filter!(x -> x != 0, unique(simulation_inputs.delays)),
)
sim.status = BUILT

return
end

function _build!(sim::Simulation{T}; kwargs...) where {T <: SimulationModel}
check_kwargs(kwargs, SIMULATION_ACCEPTED_KWARGS, "Simulation")
# Branches are a super set of Lines. Passing both kwargs will
Expand Down Expand Up @@ -417,7 +451,7 @@ function _build!(sim::Simulation{T}; kwargs...) where {T <: SimulationModel}
model = T(simulation_inputs, get_initial_conditions(sim), SimCache)
end
TimerOutputs.@timeit BUILD_TIMER "Initial Condition NLsolve refinement" begin
refine_initial_condition!(sim, model, jacobian)
refine_initial_condition!(sim, model, jacobian) #Jacobian errors
end
TimerOutputs.@timeit BUILD_TIMER "Build Perturbations" begin
_build_perturbations!(sim)
Expand Down
Loading
Loading