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 comparison with new england system from RMSPowerSyms #7

Merged
merged 37 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
52f0832
implement ZIP load
hexaeder Jan 6, 2025
343ab76
improve powerflow calculation
hexaeder Jan 8, 2025
a8c07bf
fix dq trafo
hexaeder Jan 8, 2025
54404a8
add additional models
hexaeder Jan 8, 2025
41dd2d0
fix tgov1
hexaeder Jan 8, 2025
6333635
add quadratic avr ceiling function
hexaeder Jan 8, 2025
ed6c927
add newengland script
hexaeder Jan 8, 2025
02a27c4
wip, still unstable
hexaeder Jan 9, 2025
31cb44c
minor fixes of sauer pai model
hexaeder Jan 10, 2025
cec1ee6
some generators are sane now
hexaeder Jan 10, 2025
fc6009f
better handling of limits
hexaeder Jan 10, 2025
b94b688
fix models to match RMSPowerSims
hexaeder Jan 13, 2025
023e883
fix stability problems in 39bus system
hexaeder Jan 13, 2025
d643983
add single machine example script
hexaeder Jan 14, 2025
ba4ed40
moved dump_state functionality to network dynamics
hexaeder Jan 15, 2025
d5d260d
add bounds
hexaeder Jan 15, 2025
3ff05ff
add separate output state to enforce bounds
hexaeder Jan 15, 2025
e0b6a12
fix trafo
hexaeder Jan 15, 2025
27a6e3b
clean up script a bit
hexaeder Jan 15, 2025
a39ad25
automaticially initialize Ae/Be
hexaeder Jan 16, 2025
42b4ec1
clean up script
hexaeder Jan 17, 2025
9ef35c4
add data
hexaeder Jan 17, 2025
09b268b
fix 9bus example
hexaeder Feb 12, 2025
f6a3f57
indent details blocks
hexaeder Feb 12, 2025
01c4b5d
mv to test folder
hexaeder Feb 12, 2025
769ed21
include tests
hexaeder Feb 12, 2025
8c38184
rename file
hexaeder Feb 12, 2025
8d164c8
mv pin parameters test
hexaeder Feb 12, 2025
00248a7
add interlinks
hexaeder Feb 12, 2025
9798c0b
fix some test problems
hexaeder Feb 12, 2025
8e3627a
fix docs
hexaeder Feb 13, 2025
8649a07
add doc cleanup workflow
hexaeder Feb 13, 2025
2b072ae
fix makedocs
hexaeder Feb 13, 2025
78394c8
ignore unused prams in initialisation
hexaeder Feb 13, 2025
373795e
require new features from [email protected]
hexaeder Feb 13, 2025
f9a7e8b
remove broken link
hexaeder Feb 13, 2025
148db2b
update ci test versions
hexaeder Feb 13, 2025
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
3 changes: 2 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ jobs:
fail-fast: false
matrix:
version:
- '1.10'
- 'lts'
- '1'
- 'pre'
os:
- ubuntu-latest
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/docpreviewcleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Doc Preview Cleanup

on:
pull_request:
types: [closed]

# Ensure that only one "Doc Preview Cleanup" workflow is force pushing at a time
concurrency:
group: doc-preview-cleanup
cancel-in-progress: false

jobs:
doc-preview-cleanup:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout gh-pages branch
uses: actions/checkout@v4
with:
ref: gh-pages
- name: Delete preview and history + push changes
run: |
if [ -d "${preview_dir}" ]; then
git config user.name "Documenter.jl"
git config user.email "[email protected]"
git rm -rf "${preview_dir}"
git commit -m "delete preview"
git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree})
git push --force origin gh-pages-new:gh-pages
fi
env:
preview_dir: previews/PR${{ github.event.number }}
12 changes: 6 additions & 6 deletions OpPoDynTesting/src/scenarios.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function line_between_slacks(edgef)
nw = Network(g, [src, dst], edgef)
u0 = NWState(nw)
any(isnan.(uflat(u0))) && error("Initial conditions contain NaNs")
any(isnan.(pflat(u0))) && error("Parameters contain NaNs")
any(isnan.(pflat(u0))) && @warn "Parameters contain NaNs"

affect = function(integrator)
u = NWState(integrator)
Expand Down Expand Up @@ -65,14 +65,14 @@ function bus_on_slack(busf; tmax=6, toilength=1000, argscale=1, magscale=1)
nw = Network(g, [slack, busf], edgef)
u0 = NWState(nw)
if any(isnan.(uflat(u0)))
show(stderr, MIME"text/plain"(), u0)
println(stderr)
# show(stderr, MIME"text/plain"(), u0)
# println(stderr)
error("Initial conditions contain NaNs")
end
if any(isnan.(pflat(u0)))
show(stderr, MIME"text/plain"(), u0.p)
println(stderr)
error("Parameters contain NaNs")
# show(stderr, MIME"text/plain"(), u0.p)
# println(stderr)
@warn "Parameters contain NaNs"
end

tstops = collect(range(0,tmax, length=7))[2:end-1]
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Graphs = "1.9.0"
LinearAlgebra = "1.10.0"
ModelingToolkit = "9.33.0"
ModelingToolkitStandardLibrary = "2.11.0"
NetworkDynamics = "0.9.3"
NetworkDynamics = "0.9.10"
NonlinearSolve = "4"
OrderedCollections = "1.6.3"
SciMLBase = "2.48.1"
Expand Down
5 changes: 5 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
NetworkDynamics = "22e9dc34-2a0d-11e9-0de0-8588d035468b"
OpPoDyn = "88be6d57-9a19-43dd-942c-8912817e6f84"
OpPoDynTesting = "97638210-66c4-447f-990e-d00c2b05d7ca"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
OrdinaryDiffEqNonlinearSolve = "127b3ac7-2247-4354-8eb6-78cf4e7c58e8"
OrdinaryDiffEqRosenbrock = "43230ef6-c299-4910-a778-202eb28ce4ce"
OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
Expand Down
126 changes: 65 additions & 61 deletions docs/examples/ieee9bus.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,6 @@ using OrdinaryDiffEqNonlinearSolve
using CairoMakie

#=
The AVRTypeI model contains a ceiling function which needs to parameters.
Often, the parameters are not given explicitly but istead their are two
datapoints provided. This is the case in the RTDS data, thus we need to solve
the nonlinear system to find parameters `Ae` and `Be`.
Turns out, `Ae` and `Be` are quite common default values.
=#
Ae, Be = Library.solve_ceilf(3.3 => 0.6602, 4.5 => 4.2662)

#=

## Generator Busses

The 3 generator buses are modeld using a SauerPai 6th order machine model with
Expand All @@ -44,8 +34,12 @@ The field voltage is provided by an `AVRTypeI`, the torque is provide by a `TGOV
T″_q0=0.01,
X_d, X′_d, X″_d, X_q, X′_q, X″_q, X_ls, T′_d0, T′_q0, H # free per machine parameter
)
avr = AVRTypeI(vr_min=-5, vr_max=5, Ka=20, Ta=0.2, Kf=0.063,
Tf=0.35, Ke=1, Te=0.314, Ae, Be, tmeas_lag=false)
avr = AVRTypeI(vr_min=-5, vr_max=5,
Ka=20, Ta=0.2,
Kf=0.063, Tf=0.35,
Ke=1, Te=0.314,
E1=3.3, Se1=0.6602, E2=4.5, Se2=4.2662,
tmeas_lag=false)
gov = TGOV1(R=0.05, T1=0.05, T2=2.1, T3=7.0, DT=0, V_max=5, V_min=-5)
busbar = BusBar()
end
Expand All @@ -63,12 +57,14 @@ nothing # hide
#=
## Load Busses

The loard buses are modeled as static PQ loads. This means they allways draw exactly `Pset` and `Qset`.
The dynamic loads are modeld as static Y-loads. Those have 3 parameters: `Pset`, `Qset` and `Vset`.
The `Vset` parameter is left free for now. Later on it is automaticially determined to match the
behavior of the static power flow load model.
=#
@mtkmodel LoadBus begin
@components begin
busbar = BusBar()
load = PQLoad(Pset, Qset)
load = ConstantYLoad(Pset, Qset)
end
@equations begin
connect(load.terminal, busbar.terminal)
Expand Down Expand Up @@ -102,20 +98,24 @@ We instantiate all models as modeling toolkit models.
nothing #hide

#=
After this, we can build the NetworkDynamic components using the `Bus`-constructor.
After this, we can build the `NetworkDynamics` components using the `Bus`-constructor.

The `Bus` constructor is essentially a thin wrapper around the `VertexModel` constructor which,
per default, adds some metadata. For example the `vidx` property which later on allows for
"graph free" network dynamics instantiation.

We use the `pf` keyword to specify the models which should be used in the powerflow calculation.
Here, generator 1 is modeld as a slack bus while the other two generators are modeled as a PV bus.
The loads are modeled as PQ buses.
=#
@named bus1 = Bus(mtkbus1; vidx=1)
@named bus2 = Bus(mtkbus2; vidx=2)
@named bus3 = Bus(mtkbus3; vidx=3)
@named bus1 = Bus(mtkbus1; vidx=1, pf=pfSlack(V=1.04))
@named bus2 = Bus(mtkbus2; vidx=2, pf=pfPV(V=1.025, P=1.63))
@named bus3 = Bus(mtkbus3; vidx=3, pf=pfPV(V=1.025, P=0.85))
@named bus4 = Bus(mtkbus4; vidx=4)
@named bus5 = Bus(mtkbus5; vidx=5)
@named bus6 = Bus(mtkbus6; vidx=6)
@named bus5 = Bus(mtkbus5; vidx=5, pf=pfPQ(P=-1.25, Q=-0.5))
@named bus6 = Bus(mtkbus6; vidx=6, pf=pfPQ(P=-0.9, Q=-0.3))
@named bus7 = Bus(mtkbus7; vidx=7)
@named bus8 = Bus(mtkbus8; vidx=8)
@named bus8 = Bus(mtkbus8; vidx=8, pf=pfPQ(P=-1.0, Q=-0.35))
@named bus9 = Bus(mtkbus9; vidx=9)
nothing #hide

Expand Down Expand Up @@ -151,64 +151,73 @@ end
nothing #hide

#=
## Initialization
## Build Network

To initialize the system, we first set the "default" values for the bus voltages
according to the provide power flow solution from the document.
Finally, we can build the network by providing the vertices and edges.
=#
set_voltage!(bus1; mag=1.040, arg=deg2rad( 0.0))
set_voltage!(bus2; mag=1.025, arg=deg2rad( 9.3))
set_voltage!(bus3; mag=1.025, arg=deg2rad( 4.7))
set_voltage!(bus4; mag=1.026, arg=deg2rad(-2.2))
set_voltage!(bus5; mag=0.996, arg=deg2rad(-4.0))
set_voltage!(bus6; mag=1.013, arg=deg2rad(-3.7))
set_voltage!(bus7; mag=1.026, arg=deg2rad( 3.7))
set_voltage!(bus8; mag=1.016, arg=deg2rad( 0.7))
set_voltage!(bus9; mag=1.032, arg=deg2rad( 2.0))
nothing #hide
vertexfs = [bus1, bus2, bus3, bus4, bus5, bus6, bus7, bus8, bus9];
edgefs = [l45, l46, l57, l69, l78, l89, t14, t27, t39];
nw = Network(vertexfs, edgefs)

#=
The generator buses have lots of internal states and parameters which need to be
initialized.
To do so, we need to set the current in addition to the voltage on those buses.
The current can be set by provide `P` and `Q` instead, which will read out the voltages
and define the current accordingly.
## Powerflow

To initialize the system, we first solve the static powerflow problem.
Internally, `OpPoDyn.jl` builds an equivalent network but replaces each dynamic model
with the given static power flow model.
The static powerflow problem is solved, after which the power flow solution (bus voltages
and currents) are stored as `default` values in the vertex models.
=#
set_current!(bus1; P=0.716, Q= 0.270)
set_current!(bus2; P=1.630, Q= 0.067)
set_current!(bus3; P=0.850, Q=-0.109)
solve_powerflow!(nw)
nothing #hide

#=
To initialize the internal states, we just need to call `initialize_component!`, which will
create a nonlinear initialization problem automaticially, which solves for "free" states and parameters.
## Component initialization

The power flow solution provided all the "interface states" (i.e. voltages and currents).
With that information, we can initialize the free states and parameters of the dynamic models,
such that the dynamic steady state matches the static power flow solution.

When calling `initialize!`, `OpPoDyn.jl` will loop through all the dynamic models
in the system, automaticially creating and solving a nonlinear initialization problem for each of them.

Concretely, here we're solving for all internal machien states and the reference
values for voltage and power of the AVR and govenor models.
Concretly, here were solving for the following things:
- unknown `Vset` for load busses,
- unknown internal machine and controller states as well as the free govenor and
avr references (parameters) of the generator busses.
=#
initialize_component!(bus1)
initialize_component!(bus2)
initialize_component!(bus3)
OpPoDyn.initialize!(nw)
nothing #hide

#=
## Disturbance

To see some dynamics, we need to introduce some disturbance.
For that we use a [`PresetTimeComponentCallback`](@extref NetworkDynamics.PresetTimeComponentCallback)
to deactivate a line at a certain time.
=#
deactivate_line = ComponentAffect([], [:pibranch₊active]) do u, p, ctx
@info "Deactivate line $(ctx.src)=>$(ctx.dst) at t=$(ctx.t)"
p[:pibranch₊active] = 0
end
cb = PresetTimeComponentCallback([1.0], deactivate_line)
set_callback!(l46, cb)
nothing # hide

#=
## Build Network

Finally, we can build the network by providing the vertices and edges.
=#
vertexfs = [bus1, bus2, bus3, bus4, bus5, bus6, bus7, bus8, bus9];
edgefs = [l45, l46, l57, l69, l78, l89, t14, t27, t39];
nw = Network(vertexfs, edgefs)
u0 = NWState(nw)
prob = ODEProblem(nw, uflat(u0), (0,100), pflat(u0))
prob = ODEProblem(nw, uflat(u0), (0,15), pflat(u0); callback=get_callbacks(nw))
sol = solve(prob, Rodas5P())
nothing


#=
## Plotting the Solution
=#
fig = Figure(size=(1000,2000));
fig = Figure(size=(600,800));
ax = Axis(fig[1, 1]; title="Active power")
for i in [1,2,3,5,6,8]
lines!(ax, sol; idxs=VIndex(i,:busbar₊P), label="Bus $i", color=Cycled(i))
Expand All @@ -219,12 +228,7 @@ for i in 1:9
lines!(ax, sol; idxs=VIndex(i,:busbar₊u_mag), label="Bus $i", color=Cycled(i))
end
axislegend(ax)
ax = Axis(fig[3, 1]; title="Voltag angel")
for i in 1:9
lines!(ax, sol; idxs=VIndex(i,:busbar₊u_arg), label="Bus $i", color=Cycled(i))
end
axislegend(ax)
ax = Axis(fig[4, 1]; title="Frequency")
ax = Axis(fig[3, 1]; title="Frequency")
for i in 1:3
lines!(ax, sol; idxs=VIndex(i,:machine₊ω), label="Bus $i", color=Cycled(i))
end
Expand Down
35 changes: 28 additions & 7 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
using OpPoDyn
using Documenter
using Literate
using DocumenterInterLinks

links = InterLinks(
"NetworkDynamics" => "https://juliadynamics.github.io/NetworkDynamics.jl/stable/",
)

DocMeta.setdocmeta!(OpPoDyn, :DocTestSetup, :(using OpPoDyn); recursive=true)

Expand All @@ -16,12 +21,13 @@ for example in filter(contains(r".jl$"), readdir(example_dir, join=true))
end


makedocs(;
kwargs = (;
modules=[OpPoDyn],
authors="Hans Würfel <[email protected]> and contributors",
sitename="OpPoDyn.jl",
linkcheck=true,
pagesonly=true,
plugins=[links],
format=Documenter.HTML(;
canonical="https://juliaenergy.github.io/OpPoDyn.jl",
edit_link="main",
Expand All @@ -33,11 +39,26 @@ makedocs(;
"Examples" => [
"generated/ieee9bus.md",]
],
warnonly=[:cross_references, :missing_docs, :docs_block],
warnonly=[:missing_docs],
)
kwargs_warnonly = (; kwargs..., warnonly=true)

deploydocs(;
repo="github.com/JuliaEnergy/OpPoDyn.jl",
devbranch="main",
push_preview=true,
)
if haskey(ENV,"GITHUB_ACTIONS")
success = true
thrown_ex = nothing
try
makedocs(; kwargs...)
catch e
@info "Strict doc build failed, try again with warnonly=true"
global success = false
global thrown_ex = e
makedocs(; kwargs_warnonly...)
end

deploydocs(; repo="github.com/JuliaEnergy/OpPoDyn.jl",
devbranch="main", push_preview=true)

success || throw(thrown_ex)
else # local build
makedocs(; kwargs_warnonly...)
end
Loading
Loading