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

Mb/add degov3 #366

Merged
merged 6 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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))`).
6 changes: 6 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} =
Float64[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 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 @@
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})

Check warning on line 17 in src/base/jacobian.jl

View check run for this annotation

Codecov / codecov/patch

src/base/jacobian.jl#L17

Added line #L17 was not covered by tests
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 @@
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 @@
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})(

Check warning on line 62 in src/base/jacobian.jl

View check run for this annotation

Codecov / codecov/patch

src/base/jacobian.jl#L62

Added line #L62 was not covered by tests
JM::U,
x::AbstractVector{Float64},
h,
p,
t,
) where {U <: Union{Matrix{Float64}, SparseArrays.SparseMatrixCSC{Float64, Int64}}}
J.Jf(JM, x, h, t)
return

Check warning on line 70 in src/base/jacobian.jl

View check run for this annotation

Codecov / codecov/patch

src/base/jacobian.jl#L69-L70

Added lines #L69 - L70 were not covered by tests
end

function (J::JacobianFunctionWrapper{NoDelays})(
JM::U,
dx::AbstractVector{Float64},
x::AbstractVector{Float64},
Expand All @@ -53,7 +86,7 @@
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 @@
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 @@
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)

Check warning on line 194 in src/base/jacobian.jl

View check run for this annotation

Codecov / codecov/patch

src/base/jacobian.jl#L187-L194

Added lines #L187 - L194 were not covered by tests
elseif sparse_retrieve_loop == 0
Jv = jac
else
throw(IS.ConflictingInputsError("negative sparse_retrieve_loop not valid"))

Check warning on line 198 in src/base/jacobian.jl

View check run for this annotation

Codecov / codecov/patch

src/base/jacobian.jl#L198

Added line #L198 was not covered by tests
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
40 changes: 37 additions & 3 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::Simulation{MassMatrixModel})
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
19 changes: 19 additions & 0 deletions src/base/simulation_inputs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct SimulationInputs
global_vars_update_pointers::Dict{Int, Int}
global_state_map::MAPPING_DICT
global_inner_var_map::Dict{String, Dict}
delays::Vector

function SimulationInputs(
sys::PSY.System,
Expand All @@ -39,6 +40,7 @@ struct SimulationInputs

TimerOutputs.@timeit BUILD_TIMER "Wrap Dynamic Injectors" begin
wrapped_injectors = _wrap_dynamic_injector_data(sys, lookup, injection_start)
delays = get_system_delays(sys)
var_count = wrapped_injectors[end].ix_range[end]
end

Expand Down Expand Up @@ -71,6 +73,11 @@ struct SimulationInputs
break
end
end

if !isempty(delays)
@info "System has delays. Use the correct solver for delay differential equations."
end

new(
wrapped_injectors,
wrapped_static_injectors,
Expand All @@ -91,6 +98,7 @@ struct SimulationInputs
global_vars,
MAPPING_DICT(),
Dict{String, Dict}(),
delays,
)
end
end
Expand Down Expand Up @@ -191,6 +199,17 @@ function _wrap_dynamic_injector_data(sys::PSY.System, lookup, injection_start::I
return wrapped_injector
end

function get_system_delays(sys::PSY.System)
delays = []
for injector in get_injectors_with_dynamics(sys)
device_delays = get_delays(PSY.get_dynamic_injector(injector))
if !isnothing(device_delays)
delays = vcat(delays, device_delays)
end
end
return delays
end

function _wrap_dynamic_branches(sys::PSY.System, lookup::Dict{Int, Int})
branches_start = 2 * get_n_buses(sys) + 1
sys_base_power = PSY.get_base_power(sys)
Expand Down
7 changes: 6 additions & 1 deletion src/base/simulation_model.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
abstract type SimulationModel end
abstract type ModelVariations end

abstract type SimulationModel <: ModelVariations end
struct MassMatrixModel <: SimulationModel end
struct ResidualModel <: SimulationModel end

abstract type DelayModel <: ModelVariations end
struct HasDelays <: DelayModel end
struct NoDelays <: DelayModel end
13 changes: 13 additions & 0 deletions src/base/small_signal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,21 @@ function _small_signal_analysis(
return _small_signal_analysis(jacwrapper.Jv, jacwrapper.x, inputs, multimachine)
end

"""
small_signal_analysis(
sim::Simulation,
)

Returns the Small Signal Output object that contains the eigenvalues and participation factors.

# Arguments
- `sim::Simulation` : Small Signal Output object that contains the eigenvalues and participation factors
"""
function small_signal_analysis(sim::Simulation{T}; kwargs...) where {T <: SimulationModel}
inputs = get_simulation_inputs(sim)
if !(isempty(inputs.delays))
return error("Small signal analysis not compatible with system model with delays")
end
x_eval = get(kwargs, :operating_point, get_initial_conditions(sim))
return _small_signal_analysis(T, inputs, x_eval, sim.multimachine)
end
Expand Down
9 changes: 6 additions & 3 deletions src/base/system_model.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
struct SystemModel{T <: SimulationModel, C <: Cache}
struct SystemModel{T <: SimulationModel, D <: DelayModel, C <: Cache}
inputs::SimulationInputs
cache::C
end

function SystemModel{T}(inputs, cache::U) where {T <: SimulationModel, U <: Cache}
return SystemModel{T, U}(inputs, cache)
function SystemModel{T, D}(
inputs,
cache::U,
) where {T <: SimulationModel, D <: DelayModel, U <: Cache}
return SystemModel{T, D, U}(inputs, cache)
end
Loading
Loading