diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index ea756b1..a254ae4 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.11.0-rc4","generation_timestamp":"2024-10-07T15:27:39","documenter_version":"1.7.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.11.1","generation_timestamp":"2024-11-21T09:58:48","documenter_version":"1.8.0"}} \ No newline at end of file diff --git a/dev/ModelingConcepts/9f7f38f1.png b/dev/ModelingConcepts/9f7f38f1.png new file mode 100644 index 0000000..70dd024 Binary files /dev/null and b/dev/ModelingConcepts/9f7f38f1.png differ diff --git a/dev/ModelingConcepts/f579f9a4.png b/dev/ModelingConcepts/f579f9a4.png deleted file mode 100644 index 071f6a7..0000000 Binary files a/dev/ModelingConcepts/f579f9a4.png and /dev/null differ diff --git a/dev/ModelingConcepts/index.html b/dev/ModelingConcepts/index.html index 28aaa74..1a5265b 100644 --- a/dev/ModelingConcepts/index.html +++ b/dev/ModelingConcepts/index.html @@ -3,7 +3,8 @@ (t) │ │ o←───┤ Injector │ │ │ - └───────────┘

The current for injectors is always in injector convention, i.e. positive currents flow out of the injector towards the terminal.

Model classes

Model "classes" are nothing formalized. In this document, a model class is just a description for some ODESystem from ModelingToolkit.jl, which satisfies certain requirements. For example, any ODESystem is considered an "Injector" if it contains a connector Terminal() called :terminal.

Code example: definition of PQ load as injector
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
+      └───────────┘

The current for injectors is always in injector convention, i.e. positive currents flow out of the injector towards the terminal.

Model classes

Model "classes" are nothing formalized. In this document, a model class is just a description for some ODESystem from ModelingToolkit.jl, which satisfies certain requirements. For example, any ODESystem is considered an "Injector" if it contains a connector Terminal() called :terminal.

Code example: definition of PQ load as injector
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
+using ModelingToolkit: D_nounits as Dt, t_nounits as t
 @mtkmodel MyPQLoad begin
     @components begin
         terminal = Terminal()
@@ -23,7 +24,7 @@
         terminal.i_r ~ (Pset*terminal.u_r + Qset*terminal.u_i)/(terminal.u_r^2 + terminal.u_i^2)
         terminal.i_i ~ (Pset*terminal.u_i - Qset*terminal.u_r)/(terminal.u_r^2 + terminal.u_i^2)
     end
-end

Model class MTKBus

A MTKBus isa class of models, which are used to describe the dynamic behavior of a full bus in a power grid. Each MTKBus musst contain a predefined model of type BusBar() (named :busbar). This busbar represents the connection point to the grid. Optionally, it may contain various injectors.

 ┌───────────────────────────────────┐
+end

Model class MTKBus

A MTKBus isa class of models, which are used to describe the dynamic behavior of a full bus in a power grid. Each MTKBus musst contain a predefined model of type BusBar() (named :busbar). This busbar represents the connection point to the grid. Optionally, it may contain various injectors.

 ┌───────────────────────────────────┐
  │ MTKBus             ┌───────────┐  │
  │  ┌──────────┐   ┌──┤ Generator │  │
  │  │          │   │  └───────────┘  │
@@ -31,7 +32,7 @@
  │  │          │   │  ┌───────────┐  │
  │  └──────────┘   └──┤ Load      │  │
  │                    └───────────┘  │
- └───────────────────────────────────┘

Sometimes it is not possible to connect all injectors directly but instead one needs or wants Branches between the busbar and injector terminal. As long as the :busbar is present at the toplevel, there are few limitations on the overall model complexity.

For simple models (direct connections of a few injectors) it is possible to use the convenience method MTKBus(injectors...) to create the composite model based on provide injector models.

Code example: definition of a Bus containing a swing equation and a load
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
+ └───────────────────────────────────┘

Sometimes it is not possible to connect all injectors directly but instead one needs or wants Branches between the busbar and injector terminal. As long as the :busbar is present at the toplevel, there are few limitations on the overall model complexity.

For simple models (direct connections of a few injectors) it is possible to use the convenience method MTKBus(injectors...) to create the composite model based on provide injector models.

Code example: definition of a Bus containing a swing equation and a load
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
 @mtkmodel MyMTKBus begin
     @components begin
         busbar = BusBar()
@@ -42,11 +43,11 @@
         connect(busbar.terminal, swing.terminal)
         connect(busbar.terminal, load.terminal)
     end
-end

Alternativly, for that system you could have just called

mybus = MTKBus(Swing(;name=:swing), PQLoad(;name=:load))

to get an instance of a model which is structually aquivalent to MyMTKBus.

Line Modeling

Model class Branch

A branch is the two-port equivalent to an injector. I needs to have two Terminal()s, one is called :src, the other :dst.

Examples for branches are: PI─Model branches, dynamic RL branches or transformers.

      ┌───────────┐
+end

Alternativly, for that system you could have just called

mybus = MTKBus(Swing(;name=:swing), PQLoad(;name=:load))

to get an instance of a model which is structually aquivalent to MyMTKBus.

Line Modeling

Model class Branch

A branch is the two-port equivalent to an injector. I needs to have two Terminal()s, one is called :src, the other :dst.

Examples for branches are: PI─Model branches, dynamic RL branches or transformers.

      ┌───────────┐
 (src) │           │ (dst)
   o←──┤  Branch   ├──→o
       │           │
-      └───────────┘

Both ends follow the injector interface, i.e. current leaving the device towards the terminals is always positive.

Code example: algebraic R-line
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
+      └───────────┘

Both ends follow the injector interface, i.e. current leaving the device towards the terminals is always positive.

Code example: algebraic R-line
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
 @mtkmodel MyRLine begin
     @components begin
         src = Terminal()
@@ -61,7 +62,7 @@
         src.i_r ~ -dst.i_r
         src.i_i ~ -dst.i_i
     end
-end

Model class: MTKLine

Similar to the MTKBus, a MTKLine is a model class which represents a transmission line in the network.

It musst contain two LineEnd() instances, one called :src, one called :dst.

 ┌────────────────────────────────────────────────┐
+end

Model class: MTKLine

Similar to the MTKBus, a MTKLine is a model class which represents a transmission line in the network.

It musst contain two LineEnd() instances, one called :src, one called :dst.

 ┌────────────────────────────────────────────────┐
  │ MTKLine          ┌──────────┐                  │
  │  ┌─────────┐  ┌──┤ Branch A │──┐  ┌─────────┐  │
  │  │ LineEnd │  │  └──────────┘  │  │ LineEnd │  │
@@ -69,7 +70,7 @@
  │  │         │  │  ┌──────────┐  │  │         │  │
  │  └─────────┘  └──┤ Branch B │──┘  └─────────┘  │
  │                  └──────────┘                  │
- └────────────────────────────────────────────────┘

Simple line models, which consist only of valid Branch models can be instantiated using the MTKLine(branches...) constructor.

More complex models can be created manually. For example if you want to chain multiple branches between the LineEnds, for example something like

LineEnd(:src) ──o── Transformer ──o── Pi─Line ──o── LineEnd(:dst)
Code example: Transmission line with two pi-branches
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
+ └────────────────────────────────────────────────┘

Simple line models, which consist only of valid Branch models can be instantiated using the MTKLine(branches...) constructor.

More complex models can be created manually. For example if you want to chain multiple branches between the LineEnds, for example something like

LineEnd(:src) ──o── Transformer ──o── Pi─Line ──o── LineEnd(:dst)
Code example: Transmission line with two pi-branches
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
 @mtkmodel MyMTKLine begin
     @components begin
         src = LineEnd()
@@ -83,26 +84,31 @@
         connect(dst.terminal, branch1.dst)
         connect(dst.terminal, branch2.dst)
     end
-end

Alternatively, an equivalent model with multiple valid branch models in parallel could be created and instantiated with the convenience constructor

line = MTKLine(DynawoPiLine(;name=:branch1), DynawoPiLine(;name=:branch2))

From MTK Models to NetworkDynamics

Valid MTKLine and MTKBus can be transformed into so called Line and Bus objects.

Line and Bus structs are no MTK models anymore, but rather containers. Currently, they mainly contain a NetworkDynamics component function (ODEVertex, StaticEdge).

Eventually, those models will contain more metadata. For example

The exact structure here is not clear yet!

The result would look something like that:

using OpPoDyn, OpPoDyn.Library, ModelingToolkit
+end

Alternatively, an equivalent model with multiple valid branch models in parallel could be created and instantiated with the convenience constructor

line = MTKLine(DynawoPiLine(;name=:branch1), DynawoPiLine(;name=:branch2))

From MTK Models to NetworkDynamics

Valid MTKLine and MTKBus can be transformed into so called Line and Bus objects.

Line and Bus structs are no MTK models anymore, but rather containers. Currently, they mainly contain a NetworkDynamics component function (VertexModel, EdgeModel).

Eventually, those models will contain more metadata. For example

The exact structure here is not clear yet!

The result would look something like that:

using OpPoDyn, OpPoDyn.Library, ModelingToolkit
 using Graphs, NetworkDynamics
 using OrdinaryDiffEqRosenbrock, OrdinaryDiffEqNonlinearSolve
 using CairoMakie

Define a swing bus with load

# define injectors
 @named swing = Swing(; Pm=1, V=1, D=0.1)
 @named load = PQLoad(; Pset=-.5, Qset=0)
 bus1mtk = MTKBus(swing, load; name=:swingbus)
-vertex1f = Bus(bus1mtk) # extract component function
NetworkDynamics.ODEVertex :swingbus with depth 2
- ├─ 4 states: [busbar₊u_r=1.0, busbar₊u_i=0.0, swing₊θ, swing₊ω]
- |    with diagonal mass matrix [0, 0, 1, 1]
- └─ 7 params: [load₊Qset=0.0, swing₊D=0.1, swing₊M=0.005, swing₊ω_ref=1.0, swing₊Pm=1.0, swing₊V=1.0, load₊Pset=-0.5]

Define a second bus as a slack

bus2mtk = SlackDifferential(; name=:slackbus)
-vertex2f = Bus(bus2mtk) # extract component function
NetworkDynamics.ODEVertex :slackbus with depth 2
- └─ 2 states: [busbar₊u_r=1.0, busbar₊u_i=0.0]

Define the powerline connecting both nodes

@named branch1 = DynawoPiLine()
+vertex1f = Bus(bus1mtk) # extract component function
NetworkDynamics.VertexModel :swingbus NetworkDynamics.NoFeedForward()
+ ├─ 2 inputs:  [busbar₊i_r, busbar₊i_i]
+ ├─ 2 states:  [swing₊θ≈0, swing₊ω≈0]
+ ├─ 2 outputs: [busbar₊u_r=1, busbar₊u_i=0]
+ └─ 7 params:  [load₊Qset=0, swing₊D=0.1, swing₊M=0.005, swing₊ω_ref=1, swing₊Pm=1, swing₊V=1, load₊Pset=-0.5]

Define a second bus as a slack

bus2mtk = SlackDifferential(; name=:slackbus)
+vertex2f = Bus(bus2mtk) # extract component function
NetworkDynamics.VertexModel :slackbus NetworkDynamics.PureStateMap()
+ ├─ 2 inputs:  [busbar₊i_r, busbar₊i_i]
+ ├─ 2 states:  [busbar₊u_r=1, busbar₊u_i=0]
+ └─ 2 outputs: [busbar₊u_r=1, busbar₊u_i=0]

Define the powerline connecting both nodes

@named branch1 = DynawoPiLine()
 @named branch2 = DynawoPiLine()
 linemtk = MTKLine(branch1, branch2; name=:powerline)
-edgef = Line(linemtk) # extract component function
NetworkDynamics.StaticEdge :powerline with Fiducial coupling of depth 2
- ├─ 4 states: [dst₊i_r, dst₊i_i, src₊i_r, src₊i_i]
- └─ 8 params: [branch1₊GPu=0.0, branch2₊BPu=0.0, branch2₊GPu=0.0, branch1₊RPu=0.0, branch2₊XPu=0.022522, branch1₊XPu=0.022522, branch2₊RPu=0.0, branch1₊BPu=0.0]

Define the graph, the network and extract initial conditions

g = complete_graph(2)
+edgef = Line(linemtk) # extract component function
NetworkDynamics.EdgeModel :powerline NetworkDynamics.PureFeedForward()
+ ├─ 2/2 inputs:  src=[src₊u_r, src₊u_i] dst=[dst₊u_r, dst₊u_i]
+ ├─   0 states:  []  
+ ├─ 2/2 outputs: src=[src₊i_r, src₊i_i] dst=[dst₊i_r, dst₊i_i]
+ └─   8 params:  [branch1₊GPu=0, branch2₊BPu=0, branch2₊GPu=0, branch1₊RPu=0, branch2₊XPu=0.022522, branch1₊XPu=0.022522, branch2₊RPu=0, branch1₊BPu=0]

Define the graph, the network and extract initial conditions

g = complete_graph(2)
 nw = Network(g, [vertex1f, vertex2f], edgef)
-u0 = NWState(nw) # extract parameters and state from modesl
+u0 = NWState(nw) # extract parameters and state from models
 u0.v[1, :swing₊θ] = 0 # set missing initial conditions
 u0.v[1, :swing₊ω] = 1
1

Then we can solve the problem

prob = ODEProblem(nw, uflat(u0), (0,1), pflat(u0))
 sol = solve(prob, Rodas5P())

And finally we can plot the solution:

fig = Figure();
@@ -115,7 +121,7 @@
 ax = Axis(fig[2,1])
 lines!(ax, sol; idxs=VIndex(1,:busbar₊u_arg), label="swing bus voltage angle", color=Cycled(1))
 lines!(ax, sol; idxs=VIndex(2,:busbar₊u_arg), label="slack bus voltage angle", color=Cycled(2))
-axislegend(ax)
Example block output

Internals

Internally, we use different input/output conventions for bus and line models. The predefined models BusBar() and LineEnd() are defined in the following way:

Model: BusBar()

A busbar is a concrete model used in bus modeling. It represents the physical connection within a bus, the thing where all injectors and lines attach.

           ┌──────────┐
+axislegend(ax)
Example block output

Internals

Internally, we use different input/output conventions for bus and line models. The predefined models BusBar() and LineEnd() are defined in the following way:

Model: BusBar()

A busbar is a concrete model used in bus modeling. It represents the physical connection within a bus, the thing where all injectors and lines attach.

           ┌──────────┐
 i_lines ──→│          │  (t)
            │  Busbar  ├───o
   u_bus ←──│          │
@@ -123,4 +129,4 @@
  u_bus ──→│           │  (t)
           │  LineEnd  ├───o
 i_line ←──│           │
-          └───────────┘

It has special input/output connectors which handle the network interconnection. The main difference beeing the different input/output conventions for the network interface.

+ └───────────┘

It has special input/output connectors which handle the network interconnection. The main difference beeing the different input/output conventions for the network interface.

diff --git a/dev/assets/documenter.js b/dev/assets/documenter.js index 82252a1..7d68cd8 100644 --- a/dev/assets/documenter.js +++ b/dev/assets/documenter.js @@ -612,176 +612,194 @@ function worker_function(documenterSearchIndex, documenterBaseURL, filters) { }; } -// `worker = Threads.@spawn worker_function(documenterSearchIndex)`, but in JavaScript! -const filters = [ - ...new Set(documenterSearchIndex["docs"].map((x) => x.category)), -]; -const worker_str = - "(" + - worker_function.toString() + - ")(" + - JSON.stringify(documenterSearchIndex["docs"]) + - "," + - JSON.stringify(documenterBaseURL) + - "," + - JSON.stringify(filters) + - ")"; -const worker_blob = new Blob([worker_str], { type: "text/javascript" }); -const worker = new Worker(URL.createObjectURL(worker_blob)); - /////// SEARCH MAIN /////// -// Whether the worker is currently handling a search. This is a boolean -// as the worker only ever handles 1 or 0 searches at a time. -var worker_is_running = false; - -// The last search text that was sent to the worker. This is used to determine -// if the worker should be launched again when it reports back results. -var last_search_text = ""; - -// The results of the last search. This, in combination with the state of the filters -// in the DOM, is used compute the results to display on calls to update_search. -var unfiltered_results = []; - -// Which filter is currently selected -var selected_filter = ""; - -$(document).on("input", ".documenter-search-input", function (event) { - if (!worker_is_running) { - launch_search(); - } -}); - -function launch_search() { - worker_is_running = true; - last_search_text = $(".documenter-search-input").val(); - worker.postMessage(last_search_text); -} - -worker.onmessage = function (e) { - if (last_search_text !== $(".documenter-search-input").val()) { - launch_search(); - } else { - worker_is_running = false; - } - - unfiltered_results = e.data; - update_search(); -}; +function runSearchMainCode() { + // `worker = Threads.@spawn worker_function(documenterSearchIndex)`, but in JavaScript! + const filters = [ + ...new Set(documenterSearchIndex["docs"].map((x) => x.category)), + ]; + const worker_str = + "(" + + worker_function.toString() + + ")(" + + JSON.stringify(documenterSearchIndex["docs"]) + + "," + + JSON.stringify(documenterBaseURL) + + "," + + JSON.stringify(filters) + + ")"; + const worker_blob = new Blob([worker_str], { type: "text/javascript" }); + const worker = new Worker(URL.createObjectURL(worker_blob)); + + // Whether the worker is currently handling a search. This is a boolean + // as the worker only ever handles 1 or 0 searches at a time. + var worker_is_running = false; + + // The last search text that was sent to the worker. This is used to determine + // if the worker should be launched again when it reports back results. + var last_search_text = ""; + + // The results of the last search. This, in combination with the state of the filters + // in the DOM, is used compute the results to display on calls to update_search. + var unfiltered_results = []; + + // Which filter is currently selected + var selected_filter = ""; + + $(document).on("input", ".documenter-search-input", function (event) { + if (!worker_is_running) { + launch_search(); + } + }); -$(document).on("click", ".search-filter", function () { - if ($(this).hasClass("search-filter-selected")) { - selected_filter = ""; - } else { - selected_filter = $(this).text().toLowerCase(); + function launch_search() { + worker_is_running = true; + last_search_text = $(".documenter-search-input").val(); + worker.postMessage(last_search_text); } - // This updates search results and toggles classes for UI: - update_search(); -}); + worker.onmessage = function (e) { + if (last_search_text !== $(".documenter-search-input").val()) { + launch_search(); + } else { + worker_is_running = false; + } -/** - * Make/Update the search component - */ -function update_search() { - let querystring = $(".documenter-search-input").val(); + unfiltered_results = e.data; + update_search(); + }; - if (querystring.trim()) { - if (selected_filter == "") { - results = unfiltered_results; + $(document).on("click", ".search-filter", function () { + if ($(this).hasClass("search-filter-selected")) { + selected_filter = ""; } else { - results = unfiltered_results.filter((result) => { - return selected_filter == result.category.toLowerCase(); - }); + selected_filter = $(this).text().toLowerCase(); } - let search_result_container = ``; - let modal_filters = make_modal_body_filters(); - let search_divider = `
`; + // This updates search results and toggles classes for UI: + update_search(); + }); - if (results.length) { - let links = []; - let count = 0; - let search_results = ""; - - for (var i = 0, n = results.length; i < n && count < 200; ++i) { - let result = results[i]; - if (result.location && !links.includes(result.location)) { - search_results += result.div; - count++; - links.push(result.location); - } - } + /** + * Make/Update the search component + */ + function update_search() { + let querystring = $(".documenter-search-input").val(); - if (count == 1) { - count_str = "1 result"; - } else if (count == 200) { - count_str = "200+ results"; + if (querystring.trim()) { + if (selected_filter == "") { + results = unfiltered_results; } else { - count_str = count + " results"; + results = unfiltered_results.filter((result) => { + return selected_filter == result.category.toLowerCase(); + }); } - let result_count = `
${count_str}
`; - search_result_container = ` + let search_result_container = ``; + let modal_filters = make_modal_body_filters(); + let search_divider = `
`; + + if (results.length) { + let links = []; + let count = 0; + let search_results = ""; + + for (var i = 0, n = results.length; i < n && count < 200; ++i) { + let result = results[i]; + if (result.location && !links.includes(result.location)) { + search_results += result.div; + count++; + links.push(result.location); + } + } + + if (count == 1) { + count_str = "1 result"; + } else if (count == 200) { + count_str = "200+ results"; + } else { + count_str = count + " results"; + } + let result_count = `
${count_str}
`; + + search_result_container = ` +
+ ${modal_filters} + ${search_divider} + ${result_count} +
+ ${search_results} +
+
+ `; + } else { + search_result_container = `
${modal_filters} ${search_divider} - ${result_count} -
- ${search_results} -
-
+
0 result(s)
+ +
No result found!
`; - } else { - search_result_container = ` -
- ${modal_filters} - ${search_divider} -
0 result(s)
-
-
No result found!
- `; - } + } - if ($(".search-modal-card-body").hasClass("is-justify-content-center")) { - $(".search-modal-card-body").removeClass("is-justify-content-center"); - } + if ($(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").removeClass("is-justify-content-center"); + } - $(".search-modal-card-body").html(search_result_container); - } else { - if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { - $(".search-modal-card-body").addClass("is-justify-content-center"); + $(".search-modal-card-body").html(search_result_container); + } else { + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(` +
Type something to get started!
+ `); } + } - $(".search-modal-card-body").html(` -
Type something to get started!
- `); + /** + * Make the modal filter html + * + * @returns string + */ + function make_modal_body_filters() { + let str = filters + .map((val) => { + if (selected_filter == val.toLowerCase()) { + return `${val}`; + } else { + return `${val}`; + } + }) + .join(""); + + return ` +
+ Filters: + ${str} +
`; } } -/** - * Make the modal filter html - * - * @returns string - */ -function make_modal_body_filters() { - let str = filters - .map((val) => { - if (selected_filter == val.toLowerCase()) { - return `${val}`; - } else { - return `${val}`; - } - }) - .join(""); - - return ` -
- Filters: - ${str} -
`; +function waitUntilSearchIndexAvailable() { + // It is possible that the documenter.js script runs before the page + // has finished loading and documenterSearchIndex gets defined. + // So we need to wait until the search index actually loads before setting + // up all the search-related stuff. + if (typeof documenterSearchIndex !== "undefined") { + runSearchMainCode(); + } else { + console.warn("Search Index not available, waiting"); + setTimeout(waitUntilSearchIndexAvailable, 1000); + } } +// The actual entry point to the search code +waitUntilSearchIndexAvailable(); + }) //////////////////////////////////////////////////////////////////////////////// require(['jquery'], function($) { diff --git a/dev/generated/ieee9bus.jl b/dev/generated/ieee9bus.jl index e542bd7..e3ea73d 100644 --- a/dev/generated/ieee9bus.jl +++ b/dev/generated/ieee9bus.jl @@ -94,7 +94,7 @@ nothing #hide # After this, we can build the NetworkDynamic components using the `Bus`-constructor. # -# The `Bus` constructor is essentially a thin wrapper around the `ODEVertex` constructor which, +# 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. @@ -117,7 +117,7 @@ nothing #hide # into the line parameters according to the base voltage on both ends. # # For the lines we again make use of the `src` and `dst` metadata of the -# `StaticEdge` objects for automatic graph construction. +# `EdgeModel` objects for automatic graph construction. function piline(; R, X, B) @named pibranch = PiLine(;R, X, B_src=B/2, B_dst=B/2, G_src=0, G_dst=0) diff --git a/dev/generated/ieee9bus/923ea2b8.png b/dev/generated/ieee9bus/923ea2b8.png new file mode 100644 index 0000000..91c9006 Binary files /dev/null and b/dev/generated/ieee9bus/923ea2b8.png differ diff --git a/dev/generated/ieee9bus/9a4c1535.png b/dev/generated/ieee9bus/9a4c1535.png deleted file mode 100644 index db684cb..0000000 Binary files a/dev/generated/ieee9bus/9a4c1535.png and /dev/null differ diff --git a/dev/generated/ieee9bus/index.html b/dev/generated/ieee9bus/index.html index 90e1c3f..5feaa76 100644 --- a/dev/generated/ieee9bus/index.html +++ b/dev/generated/ieee9bus/index.html @@ -49,7 +49,7 @@ @named mtkbus6 = LoadBus(;load__Pset=-0.90, load__Qset=-0.3) @named mtkbus7 = MTKBus() @named mtkbus8 = LoadBus(;load__Pset=-1.0, load__Qset=-0.35) -@named mtkbus9 = MTKBus()

After this, we can build the NetworkDynamic components using the Bus-constructor.

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

@named bus1 = Bus(mtkbus1; vidx=1)
+@named mtkbus9 = MTKBus()

After this, we can build the NetworkDynamic 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.

@named bus1 = Bus(mtkbus1; vidx=1)
 @named bus2 = Bus(mtkbus2; vidx=2)
 @named bus3 = Bus(mtkbus3; vidx=3)
 @named bus4 = Bus(mtkbus4; vidx=4)
@@ -57,7 +57,7 @@
 @named bus6 = Bus(mtkbus6; vidx=6)
 @named bus7 = Bus(mtkbus7; vidx=7)
 @named bus8 = Bus(mtkbus8; vidx=8)
-@named bus9 = Bus(mtkbus9; vidx=9)

Branches

Branches and Transformers are build from the same PILine model with optional transformer on both ends. However, the data is provided in a way that the actual transformer values are 1.0. Apparently, the transforming action has been absorbed into the line parameters according to the base voltage on both ends.

For the lines we again make use of the src and dst metadata of the StaticEdge objects for automatic graph construction.

function piline(; R, X, B)
+@named bus9 = Bus(mtkbus9; vidx=9)

Branches

Branches and Transformers are build from the same PILine model with optional transformer on both ends. However, the data is provided in a way that the actual transformer values are 1.0. Apparently, the transforming action has been absorbed into the line parameters according to the base voltage on both ends.

For the lines we again make use of the src and dst metadata of the EdgeModel objects for automatic graph construction.

function piline(; R, X, B)
     @named pibranch = PiLine(;R, X, B_src=B/2, B_dst=B/2, G_src=0, G_dst=0)
     MTKLine(pibranch)
 end
@@ -86,19 +86,19 @@
 set_current!(bus2; P=1.630, Q= 0.067)
 set_current!(bus3; P=0.850, Q=-0.109)

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.

Concretely, here we're solving for all internal machien states and the reference values for voltage and power of the AVR and govenor models.

initialize_component!(bus1)
 initialize_component!(bus2)
-initialize_component!(bus3)
[ Info: Initialization problem is fully constrained. Created NonlinearLeastSquaresProblem for [:machine₊δ, :machine₊ω, :machine₊E′_q, :machine₊E′_d, :machine₊ψ″_d, :machine₊ψ″_q, :avr₊vf₊u, :avr₊vr1, :avr₊vr2, :gov₊xg1, :gov₊xg2, :avr₊vref, :gov₊p_ref]
-[ Info: Initialization successful with residual 5.222796427994071e-14
-[ Info: Initialization problem is fully constrained. Created NonlinearLeastSquaresProblem for [:machine₊δ, :machine₊ω, :machine₊E′_q, :machine₊E′_d, :machine₊ψ″_d, :machine₊ψ″_q, :avr₊vf₊u, :avr₊vr1, :avr₊vr2, :gov₊xg1, :gov₊xg2, :avr₊vref, :gov₊p_ref]
-[ Info: Initialization successful with residual 3.262766089943112e-14
-[ Info: Initialization problem is fully constrained. Created NonlinearLeastSquaresProblem for [:machine₊δ, :machine₊ω, :machine₊E′_q, :machine₊E′_d, :machine₊ψ″_d, :machine₊ψ″_q, :avr₊vf₊u, :avr₊vr1, :avr₊vr2, :gov₊xg1, :gov₊xg2, :avr₊vref, :gov₊p_ref]
-[ Info: Initialization successful with residual 4.797353606947269e-14

Build Network

Finally, we can build the network by providing the vertices and edges.

vertexfs = [bus1, bus2, bus3, bus4, bus5, bus6, bus7, bus8, bus9];
+initialize_component!(bus3)
[ Info: Initialization problem is overconstrained (13 vars for 15 equations). Create NonlinearLeastSquaresProblem for [:machine₊δ, :machine₊ω, :machine₊E′_q, :machine₊E′_d, :machine₊ψ″_d, :machine₊ψ″_q, :avr₊vf₊u, :avr₊vr1, :avr₊vr2, :gov₊xg1, :gov₊xg2, :avr₊vref, :gov₊p_ref].
+[ Info: Initialization successful with residual 5.223255456073215e-14
+[ Info: Initialization problem is overconstrained (13 vars for 15 equations). Create NonlinearLeastSquaresProblem for [:machine₊δ, :machine₊ω, :machine₊E′_q, :machine₊E′_d, :machine₊ψ″_d, :machine₊ψ″_q, :avr₊vf₊u, :avr₊vr1, :avr₊vr2, :gov₊xg1, :gov₊xg2, :avr₊vref, :gov₊p_ref].
+[ Info: Initialization successful with residual 1.3990123609786352e-14
+[ Info: Initialization problem is overconstrained (13 vars for 15 equations). Create NonlinearLeastSquaresProblem for [:machine₊δ, :machine₊ω, :machine₊E′_q, :machine₊E′_d, :machine₊ψ″_d, :machine₊ψ″_q, :avr₊vf₊u, :avr₊vr1, :avr₊vr2, :gov₊xg1, :gov₊xg2, :avr₊vref, :gov₊p_ref].
+[ Info: Initialization successful with residual 4.7064820018157155e-14

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))
 sol = solve(prob, Rodas5P())
-nothing
┌ Warning: At t=8.632492209547111, dt was forced below floating point epsilon 1.7763568394002505e-15, and step error estimate = 600.6622525010758. Aborting. There is either an error in your model specification or the true solution is unstable (or the true solution can not be represented in the precision of Float64).
-@ SciMLBase ~/.julia/packages/SciMLBase/PTTHz/src/integrator_interface.jl:609

Plotting the Solution

fig = Figure(size=(1000,2000));
+nothing
┌ Warning: At t=8.632490304498285, dt was forced below floating point epsilon 1.7763568394002505e-15, and step error estimate = 600.6603785544058. Aborting. There is either an error in your model specification or the true solution is unstable (or the true solution can not be represented in the precision of Float64).
+@ SciMLBase ~/.julia/packages/SciMLBase/NtgCQ/src/integrator_interface.jl:623

Plotting the Solution

fig = Figure(size=(1000,2000));
 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))
@@ -118,4 +118,4 @@
 for i in 1:3
     lines!(ax, sol; idxs=VIndex(i,:machine₊ω), label="Bus $i", color=Cycled(i))
 end
-axislegend(ax)
Example block output

This page was generated using Literate.jl.

+axislegend(ax)Example block output

This page was generated using Literate.jl.

diff --git a/dev/index.html b/dev/index.html index a71bdb2..568ce4e 100644 --- a/dev/index.html +++ b/dev/index.html @@ -16,4 +16,4 @@ (devenv) pkg> dev ./OpPoDyntesting -(devenv) pkg> dev .
OpPoDyn.ModelMetadataConstructorType

This type wraps the default constructor of a ModelingToolkit.Model and allows to attach metadata to it. Two types of metadata is possible:

  • a "name" field which will become the "name" field of the system
  • a named tuple with aribrary fields which will become the "metadata" field of the system
source
OpPoDyn.separate_differential_constraint_eqsFunction
separate_differential_constraint_eqs(M, p=nothing)

Returns the constraint equations and differential equations indices from an ODEFunction h(x) used in DifferentialEquations.jl. The ODE h must be in Mass Matrix form meaning: M ẋ = h(x), with M diagonal. h should be inplace.

source
OpPoDyn.small_signal_stability_analysisFunction
small_signal_stability_analysis(h::ODEFunction, eq_point, p = nothing)

Performs a small signal stability analysis according to: [1] "Power System Modelling and Scripting", F. Milano, Chapter 7.2. We find the reduced Jacobian (or State Matrix A_s) and calculate its eigenvalues. If the eigenvalues have positive real parts we classify the grid as unstable.

  • h: Full DAE system
  • eq_point: Equilibrium point / fixed point of h. h(eq_point) = 0.0
source
OpPoDyn.@attach_metadata!Macro
@attach_metadata! Model metadata

Allows you to attach additonal metadata to a Model which was previously defined using @mtkmodel. The metadata needs to be in the form of a named tuple (; name=..., field1=..., field2=...).

If name is present in the metadata, it will be used as the default name of the system and stripped from the metadata. The rest of the named tuple will be attachde to the ODESystems metadata.

source

Funding

Development of this project was in part funded by the German Federal Ministry for Economic Affairs and Climate Action as part of the OpPoDyn-Project (Project ID 01258425/1, 2024-2027).

+(devenv) pkg> dev .
OpPoDyn.ModelMetadataConstructorType

This type wraps the default constructor of a ModelingToolkit.Model and allows to attach metadata to it. Two types of metadata is possible:

  • a "name" field which will become the "name" field of the system
  • a named tuple with aribrary fields which will become the "metadata" field of the system
source
OpPoDyn.separate_differential_constraint_eqsFunction
separate_differential_constraint_eqs(M, p=nothing)

Returns the constraint equations and differential equations indices from an ODEFunction h(x) used in DifferentialEquations.jl. The ODE h must be in Mass Matrix form meaning: M ẋ = h(x), with M diagonal. h should be inplace.

source
OpPoDyn.small_signal_stability_analysisFunction
small_signal_stability_analysis(h::ODEFunction, eq_point, p = nothing)

Performs a small signal stability analysis according to: [1] "Power System Modelling and Scripting", F. Milano, Chapter 7.2. We find the reduced Jacobian (or State Matrix A_s) and calculate its eigenvalues. If the eigenvalues have positive real parts we classify the grid as unstable.

  • h: Full DAE system
  • eq_point: Equilibrium point / fixed point of h. h(eq_point) = 0.0
source
OpPoDyn.@attach_metadata!Macro
@attach_metadata! Model metadata

Allows you to attach additonal metadata to a Model which was previously defined using @mtkmodel. The metadata needs to be in the form of a named tuple (; name=..., field1=..., field2=...).

If name is present in the metadata, it will be used as the default name of the system and stripped from the metadata. The rest of the named tuple will be attachde to the ODESystems metadata.

source

Funding

Development of this project was in part funded by the German Federal Ministry for Economic Affairs and Climate Action as part of the OpPoDyn-Project (Project ID 01258425/1, 2024-2027).

diff --git a/dev/search_index.js b/dev/search_index.js index 8b1d5a2..aa39581 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"ModelingConcepts/#Modeling-Concepts","page":"Modeling Concepts","title":"Modeling Concepts","text":"","category":"section"},{"location":"ModelingConcepts/#Terminal","page":"Modeling Concepts","title":"Terminal","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"The Terminal─Connector is an important building block for every model. It represents a connection point with constant voltage in dq─cordinates u_r and u_i and enforces the kirchoff constraints sum(i_r)=0 and sum(i_i)=0.","category":"page"},{"location":"ModelingConcepts/#Modeling-of-Buses","page":"Modeling Concepts","title":"Modeling of Buses","text":"","category":"section"},{"location":"ModelingConcepts/#Model-class-Injector","page":"Modeling Concepts","title":"Model class Injector","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"An injector is a class of components with a single Terminal() (called :terminal). Examples for injectors might be Generators, Shunts, Loads.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌───────────┐\n(t) │ │\n o←───┤ Injector │\n │ │\n └───────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"The current for injectors is always in injector convention, i.e. positive currents flow out of the injector towards the terminal.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"note: Model classes\nModel \"classes\" are nothing formalized. In this document, a model class is just a description for some ODESystem from ModelingToolkit.jl, which satisfies certain requirements. For example, any ODESystem is considered an \"Injector\" if it contains a connector Terminal() called :terminal.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"details: Code example: definition of PQ load as injector\nusing OpPoDyn, OpPoDyn.Library, ModelingToolkit\n@mtkmodel MyPQLoad begin\n @components begin\n terminal = Terminal()\n end\n @parameters begin\n Pset, [description=\"Active Power demand\"]\n Qset, [description=\"Reactive Power demand\"]\n end\n @variables begin\n P(t), [description=\"Active Power\"]\n Q(t), [description=\"Reactive Power\"]\n end\n @equations begin\n P ~ terminal.u_r*terminal.i_r + terminal.u_i*terminal.i_i\n Q ~ terminal.u_i*terminal.i_r - terminal.u_r*terminal.i_i\n # if possible, its better for the solver to explicitly provide algebraic equations for the current\n terminal.i_r ~ (Pset*terminal.u_r + Qset*terminal.u_i)/(terminal.u_r^2 + terminal.u_i^2)\n terminal.i_i ~ (Pset*terminal.u_i - Qset*terminal.u_r)/(terminal.u_r^2 + terminal.u_i^2)\n end\nend\nMyPQLoad(name=:pqload) #hide\nnothing #hide","category":"page"},{"location":"ModelingConcepts/#Model-class-MTKBus","page":"Modeling Concepts","title":"Model class MTKBus","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"A MTKBus isa class of models, which are used to describe the dynamic behavior of a full bus in a power grid. Each MTKBus musst contain a predefined model of type BusBar() (named :busbar). This busbar represents the connection point to the grid. Optionally, it may contain various injectors.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌───────────────────────────────────┐\n │ MTKBus ┌───────────┐ │\n │ ┌──────────┐ ┌──┤ Generator │ │\n │ │ │ │ └───────────┘ │\n │ │ BusBar ├───o │\n │ │ │ │ ┌───────────┐ │\n │ └──────────┘ └──┤ Load │ │\n │ └───────────┘ │\n └───────────────────────────────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Sometimes it is not possible to connect all injectors directly but instead one needs or wants Branches between the busbar and injector terminal. As long as the :busbar is present at the toplevel, there are few limitations on the overall model complexity.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"For simple models (direct connections of a few injectors) it is possible to use the convenience method MTKBus(injectors...) to create the composite model based on provide injector models.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"details: Code example: definition of a Bus containing a swing equation and a load\nusing OpPoDyn, OpPoDyn.Library, ModelingToolkit\n@mtkmodel MyMTKBus begin\n @components begin\n busbar = BusBar()\n swing = Swing()\n load = PQLoad()\n end\n @equations begin\n connect(busbar.terminal, swing.terminal)\n connect(busbar.terminal, load.terminal)\n end\nend\nMyMTKBus(name=:bus) #hide\nnothing #hideAlternativly, for that system you could have just calledmybus = MTKBus(Swing(;name=:swing), PQLoad(;name=:load))\nnothing #hideto get an instance of a model which is structually aquivalent to MyMTKBus.","category":"page"},{"location":"ModelingConcepts/#Line-Modeling","page":"Modeling Concepts","title":"Line Modeling","text":"","category":"section"},{"location":"ModelingConcepts/#Model-class-Branch","page":"Modeling Concepts","title":"Model class Branch","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"A branch is the two-port equivalent to an injector. I needs to have two Terminal()s, one is called :src, the other :dst.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Examples for branches are: PI─Model branches, dynamic RL branches or transformers.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌───────────┐\n(src) │ │ (dst)\n o←──┤ Branch ├──→o\n │ │\n └───────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Both ends follow the injector interface, i.e. current leaving the device towards the terminals is always positive.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"details: Code example: algebraic R-line\nusing OpPoDyn, OpPoDyn.Library, ModelingToolkit\n@mtkmodel MyRLine begin\n @components begin\n src = Terminal()\n dst = Terminal()\n end\n @parameters begin\n R=0, [description=\"Resistance\"]\n end\n @equations begin\n dst.i_r ~ (dst.u_r - src.u_r)/R\n dst.i_i ~ (dst.u_i - src.u_i)/R\n src.i_r ~ -dst.i_r\n src.i_i ~ -dst.i_i\n end\nend\nMyRLine(name=:rline) #hide\nnothing #hide","category":"page"},{"location":"ModelingConcepts/#Model-class:-MTKLine","page":"Modeling Concepts","title":"Model class: MTKLine","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Similar to the MTKBus, a MTKLine is a model class which represents a transmission line in the network.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"It musst contain two LineEnd() instances, one called :src, one called :dst.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌────────────────────────────────────────────────┐\n │ MTKLine ┌──────────┐ │\n │ ┌─────────┐ ┌──┤ Branch A │──┐ ┌─────────┐ │\n │ │ LineEnd │ │ └──────────┘ │ │ LineEnd │ │\n │ │ :src ├──o o──┤ :dst │ │\n │ │ │ │ ┌──────────┐ │ │ │ │\n │ └─────────┘ └──┤ Branch B │──┘ └─────────┘ │\n │ └──────────┘ │\n └────────────────────────────────────────────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Simple line models, which consist only of valid Branch models can be instantiated using the MTKLine(branches...) constructor.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"More complex models can be created manually. For example if you want to chain multiple branches between the LineEnds, for example something like","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"LineEnd(:src) ──o── Transformer ──o── Pi─Line ──o── LineEnd(:dst)","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"details: Code example: Transmission line with two pi-branches\nusing OpPoDyn, OpPoDyn.Library, ModelingToolkit\n@mtkmodel MyMTKLine begin\n @components begin\n src = LineEnd()\n dst = LineEnd()\n branch1 = DynawoPiLine()\n branch2 = DynawoPiLine()\n end\n @equations begin\n connect(src.terminal, branch1.src)\n connect(src.terminal, branch2.src)\n connect(dst.terminal, branch1.dst)\n connect(dst.terminal, branch2.dst)\n end\nend\nMyMTKLine(name=:mtkline) #hide\nnothing #hideAlternatively, an equivalent model with multiple valid branch models in parallel could be created and instantiated with the convenience constructorline = MTKLine(DynawoPiLine(;name=:branch1), DynawoPiLine(;name=:branch2))\nnothing #hide","category":"page"},{"location":"ModelingConcepts/#From-MTK-Models-to-NetworkDynamics","page":"Modeling Concepts","title":"From MTK Models to NetworkDynamics","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Valid MTKLine and MTKBus can be transformed into so called Line and Bus objects.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Line and Bus structs are no MTK models anymore, but rather containers. Currently, they mainly contain a NetworkDynamics component function (ODEVertex, StaticEdge).","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Eventually, those models will contain more metadata. For example","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"static representation for powerflow,\npossibly local information about PU system (for transforming parameters between SI/PU),\nmeta information for initialization, for example initialization model or the information which parameters are considered \"tunable\" in order to initialize the dynamical model","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"The exact structure here is not clear yet!","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"The result would look something like that:","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"using OpPoDyn, OpPoDyn.Library, ModelingToolkit\nusing Graphs, NetworkDynamics\nusing OrdinaryDiffEqRosenbrock, OrdinaryDiffEqNonlinearSolve\nusing CairoMakie\nnothing #hide","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Define a swing bus with load","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"# define injectors\n@named swing = Swing(; Pm=1, V=1, D=0.1)\n@named load = PQLoad(; Pset=-.5, Qset=0)\nbus1mtk = MTKBus(swing, load; name=:swingbus)\nvertex1f = Bus(bus1mtk) # extract component function","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Define a second bus as a slack","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"bus2mtk = SlackDifferential(; name=:slackbus)\nvertex2f = Bus(bus2mtk) # extract component function","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Define the powerline connecting both nodes","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"@named branch1 = DynawoPiLine()\n@named branch2 = DynawoPiLine()\nlinemtk = MTKLine(branch1, branch2; name=:powerline)\nedgef = Line(linemtk) # extract component function","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Define the graph, the network and extract initial conditions","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"g = complete_graph(2)\nnw = Network(g, [vertex1f, vertex2f], edgef)\nu0 = NWState(nw) # extract parameters and state from modesl\nu0.v[1, :swing₊θ] = 0 # set missing initial conditions\nu0.v[1, :swing₊ω] = 1","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Then we can solve the problem","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"prob = ODEProblem(nw, uflat(u0), (0,1), pflat(u0))\nsol = solve(prob, Rodas5P())\n@assert OrdinaryDiffEqRosenbrock.SciMLBase.successful_retcode(sol) # hide\nnothing #hide","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"And finally we can plot the solution:","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"fig = Figure();\nax = Axis(fig[1,1])\nlines!(ax, sol; idxs=VIndex(1,:busbar₊P), label=\"Power injection Bus\", color=Cycled(1))\nlines!(ax, sol; idxs=VIndex(1,:swing₊Pel), label=\"Power injection Swing\", color=Cycled(2))\nlines!(ax, sol; idxs=VIndex(1,:load₊P), label=\"Power injection load\", color=Cycled(3))\naxislegend(ax)\n\nax = Axis(fig[2,1])\nlines!(ax, sol; idxs=VIndex(1,:busbar₊u_arg), label=\"swing bus voltage angle\", color=Cycled(1))\nlines!(ax, sol; idxs=VIndex(2,:busbar₊u_arg), label=\"slack bus voltage angle\", color=Cycled(2))\naxislegend(ax)\nfig #hide","category":"page"},{"location":"ModelingConcepts/#Internals","page":"Modeling Concepts","title":"Internals","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Internally, we use different input/output conventions for bus and line models. The predefined models BusBar() and LineEnd() are defined in the following way:","category":"page"},{"location":"ModelingConcepts/#Model:-BusBar()","page":"Modeling Concepts","title":"Model: BusBar()","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"A busbar is a concrete model used in bus modeling. It represents the physical connection within a bus, the thing where all injectors and lines attach.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌──────────┐\ni_lines ──→│ │ (t)\n │ Busbar ├───o\n u_bus ←──│ │\n └──────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"It receives the sum of all line currents as an input and equals that to the currents flowing into the terminal. As an output, it gives forwards the terminal voltage to the backend.","category":"page"},{"location":"ModelingConcepts/#Model:-LineEnd()","page":"Modeling Concepts","title":"Model: LineEnd()","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"A LineEnd model is very similar to the BusBar model. It represents one end of a transmission line.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌───────────┐\n u_bus ──→│ │ (t)\n │ LineEnd ├───o\ni_line ←──│ │\n └───────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"It has special input/output connectors which handle the network interconnection. The main difference beeing the different input/output conventions for the network interface.","category":"page"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = OpPoDyn","category":"page"},{"location":"#OpPoDyn","page":"Home","title":"OpPoDyn","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Documentation for OpPoDyn.","category":"page"},{"location":"#Project-Structure","page":"Home","title":"Project Structure","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The project is structured as follows","category":"page"},{"location":"","page":"Home","title":"Home","text":"OpPoDyn/\n├── assets: contains asses for reference tests\n├── docs: contains this documentation\n├── OpPoDynTesting: helper package for testing, defines test utilities like reference tests\n├── Project.toml\n├── src: source code\n│ ├── Library: submodule for library, all the models live here\n│ └── ...\n└── test: test code","category":"page"},{"location":"","page":"Home","title":"Home","text":"At this stage, this project is meant to be used with the main branch from NetworkDynamics. Unfortunately, it also depends on the unregistered subpackage OpPoDynTesting which makes instantiating the environment a bit tricky (because you can neither add NetworkDynamics#main nor OpPoDyntesting#../OpPoDynTesting without it complaining about the other dependency. Thanks to the [sources] block in Project.toml in Julia v1.11, this shouldn'te be a problem anymore.","category":"page"},{"location":"","page":"Home","title":"Home","text":"If you want to use the realse version of Julia v1.10 I suggest to create a new development environment:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> pwd() # make sure you're in the right folder\n\".../.julia/dev/OpPoDyn\"\n\n(v1.10) pkg> activate devenv\n\n(devenv) pkg> dev NetworkDynamics\n\n(devenv) pkg> dev ./OpPoDyntesting\n\n(devenv) pkg> dev .","category":"page"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"","page":"Home","title":"Home","text":"Modules = [OpPoDyn]","category":"page"},{"location":"#OpPoDyn.ModelMetadataConstructor","page":"Home","title":"OpPoDyn.ModelMetadataConstructor","text":"This type wraps the default constructor of a ModelingToolkit.Model and allows to attach metadata to it. Two types of metadata is possible:\n\na \"name\" field which will become the \"name\" field of the system\na named tuple with aribrary fields which will become the \"metadata\" field of the system\n\n\n\n\n\n","category":"type"},{"location":"#OpPoDyn.separate_differential_constraint_eqs","page":"Home","title":"OpPoDyn.separate_differential_constraint_eqs","text":"separate_differential_constraint_eqs(M, p=nothing)\n\nReturns the constraint equations and differential equations indices from an ODEFunction h(x) used in DifferentialEquations.jl. The ODE h must be in Mass Matrix form meaning: M ẋ = h(x), with M diagonal. h should be inplace.\n\n\n\n\n\n","category":"function"},{"location":"#OpPoDyn.small_signal_stability_analysis","page":"Home","title":"OpPoDyn.small_signal_stability_analysis","text":"small_signal_stability_analysis(h::ODEFunction, eq_point, p = nothing)\n\nPerforms a small signal stability analysis according to: [1] \"Power System Modelling and Scripting\", F. Milano, Chapter 7.2. We find the reduced Jacobian (or State Matrix A_s) and calculate its eigenvalues. If the eigenvalues have positive real parts we classify the grid as unstable.\n\nh: Full DAE system\neq_point: Equilibrium point / fixed point of h. h(eq_point) = 0.0\n\n\n\n\n\n","category":"function"},{"location":"#OpPoDyn.@attach_metadata!-Tuple{Any, Any}","page":"Home","title":"OpPoDyn.@attach_metadata!","text":"@attach_metadata! Model metadata\n\nAllows you to attach additonal metadata to a Model which was previously defined using @mtkmodel. The metadata needs to be in the form of a named tuple (; name=..., field1=..., field2=...).\n\nIf name is present in the metadata, it will be used as the default name of the system and stripped from the metadata. The rest of the named tuple will be attachde to the ODESystems metadata.\n\n\n\n\n\n","category":"macro"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Development of this project was in part funded by the German Federal Ministry for Economic Affairs and Climate Action as part of the OpPoDyn-Project (Project ID 01258425/1, 2024-2027).","category":"page"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"EditURL = \"../../examples/ieee9bus.jl\"","category":"page"},{"location":"generated/ieee9bus/#IEEE-9Bus-Example","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"In this example, we're going to model the IEEE 9 bus system.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"The parameters are mainly adopted from the RTDS data.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"using OpPoDyn\nusing OpPoDyn.Library\nusing ModelingToolkit\nusing NetworkDynamics\nusing Graphs\nusing OrdinaryDiffEqRosenbrock\nusing OrdinaryDiffEqNonlinearSolve\nusing CairoMakie","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"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.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"Ae, Be = Library.solve_ceilf(3.3 => 0.6602, 4.5 => 4.2662)","category":"page"},{"location":"generated/ieee9bus/#Generator-Busses","page":"IEEE 9Bus Example","title":"Generator Busses","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"The 3 generator buses are modeld using a SauerPai 6th order machine model with variable field voltage and mechanical torque input. The field voltage is provided by an AVRTypeI, the torque is provide by a TGOV1 model.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"@mtkmodel GenBus begin\n @components begin\n machine = SauerPaiMachine(;\n vf_input=true,\n τ_m_input=true,\n S_b=100,\n V_b=1,\n ω_b=2π*60,\n R_s=0.000125,\n T″_d0=0.01,\n T″_q0=0.01,\n X_d, X′_d, X″_d, X_q, X′_q, X″_q, X_ls, T′_d0, T′_q0, H # free per machine parameter\n )\n avr = AVRTypeI(vr_min=-5, vr_max=5, Ka=20, Ta=0.2, Kf=0.063,\n Tf=0.35, Ke=1, Te=0.314, Ae, Be, tmeas_lag=false)\n gov = TGOV1(R=0.05, T1=0.05, T2=2.1, T3=7.0, DT=0, V_max=5, V_min=-5)\n busbar = BusBar()\n end\n @equations begin\n connect(machine.terminal, busbar.terminal)\n connect(machine.v_mag_out, avr.vh)\n connect(avr.vf, machine.vf_in)\n connect(gov.τ_m, machine.τ_m_in)\n connect(machine.ωout, gov.ω_meas)\n end\nend\nnothing # hide","category":"page"},{"location":"generated/ieee9bus/#Load-Busses","page":"IEEE 9Bus Example","title":"Load Busses","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"The loard buses are modeled as static PQ loads. This means they allways draw exactly Pset and Qset.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"@mtkmodel LoadBus begin\n @components begin\n busbar = BusBar()\n load = PQLoad(Pset, Qset)\n end\n @equations begin\n connect(load.terminal, busbar.terminal)\n end\nend\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/#Generate-Dynamical-models","page":"IEEE 9Bus Example","title":"Generate Dynamical models","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"The parameters of the machines are obtaind from the data table from the RTDS datasheet.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"gen1p = (;machine__X_ls=0.01460, machine__X_d=0.1460, machine__X′_d=0.0608, machine__X″_d=0.06, machine__X_q=0.1000, machine__X′_q=0.0969, machine__X″_q=0.06, machine__T′_d0=8.96, machine__T′_q0=0.310, machine__H=23.64)\ngen2p = (;machine__X_ls=0.08958, machine__X_d=0.8958, machine__X′_d=0.1198, machine__X″_d=0.11, machine__X_q=0.8645, machine__X′_q=0.1969, machine__X″_q=0.11, machine__T′_d0=6.00, machine__T′_q0=0.535, machine__H= 6.40)\ngen3p = (;machine__X_ls=0.13125, machine__X_d=1.3125, machine__X′_d=0.1813, machine__X″_d=0.18, machine__X_q=1.2578, machine__X′_q=0.2500, machine__X″_q=0.18, machine__T′_d0=5.89, machine__T′_q0=0.600, machine__H= 3.01)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"We instantiate all models as modeling toolkit models.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"@named mtkbus1 = GenBus(; gen1p...)\n@named mtkbus2 = GenBus(; gen2p...)\n@named mtkbus3 = GenBus(; gen3p...)\n@named mtkbus4 = MTKBus()\n@named mtkbus5 = LoadBus(;load__Pset=-1.25, load__Qset=-0.5)\n@named mtkbus6 = LoadBus(;load__Pset=-0.90, load__Qset=-0.3)\n@named mtkbus7 = MTKBus()\n@named mtkbus8 = LoadBus(;load__Pset=-1.0, load__Qset=-0.35)\n@named mtkbus9 = MTKBus()\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"After this, we can build the NetworkDynamic components using the Bus-constructor.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"The Bus constructor is essentially a thin wrapper around the ODEVertex constructor which, per default, adds some metadata. For example the vidx property which later on allows for \"graph free\" network dynamics instantiation.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"@named bus1 = Bus(mtkbus1; vidx=1)\n@named bus2 = Bus(mtkbus2; vidx=2)\n@named bus3 = Bus(mtkbus3; vidx=3)\n@named bus4 = Bus(mtkbus4; vidx=4)\n@named bus5 = Bus(mtkbus5; vidx=5)\n@named bus6 = Bus(mtkbus6; vidx=6)\n@named bus7 = Bus(mtkbus7; vidx=7)\n@named bus8 = Bus(mtkbus8; vidx=8)\n@named bus9 = Bus(mtkbus9; vidx=9)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/#Branches","page":"IEEE 9Bus Example","title":"Branches","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"Branches and Transformers are build from the same PILine model with optional transformer on both ends. However, the data is provided in a way that the actual transformer values are 1.0. Apparently, the transforming action has been absorbed into the line parameters according to the base voltage on both ends.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"For the lines we again make use of the src and dst metadata of the StaticEdge objects for automatic graph construction.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"function piline(; R, X, B)\n @named pibranch = PiLine(;R, X, B_src=B/2, B_dst=B/2, G_src=0, G_dst=0)\n MTKLine(pibranch)\nend\nfunction transformer(; R, X)\n @named transformer = PiLine(;R, X, B_src=0, B_dst=0, G_src=0, G_dst=0)\n MTKLine(transformer)\nend\n\n@named l45 = Line(piline(; R=0.0100, X=0.0850, B=0.1760), src=4, dst=5)\n@named l46 = Line(piline(; R=0.0170, X=0.0920, B=0.1580), src=4, dst=6)\n@named l57 = Line(piline(; R=0.0320, X=0.1610, B=0.3060), src=5, dst=7)\n@named l69 = Line(piline(; R=0.0390, X=0.1700, B=0.3580), src=6, dst=9)\n@named l78 = Line(piline(; R=0.0085, X=0.0720, B=0.1490), src=7, dst=8)\n@named l89 = Line(piline(; R=0.0119, X=0.1008, B=0.2090), src=8, dst=9)\n@named t14 = Line(transformer(; R=0, X=0.0576), src=1, dst=4)\n@named t27 = Line(transformer(; R=0, X=0.0625), src=2, dst=7)\n@named t39 = Line(transformer(; R=0, X=0.0586), src=3, dst=9)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/#Initialization","page":"IEEE 9Bus Example","title":"Initialization","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"To initialize the system, we first set the \"default\" values for the bus voltages according to the provide power flow solution from the document.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"set_voltage!(bus1; mag=1.040, arg=deg2rad( 0.0))\nset_voltage!(bus2; mag=1.025, arg=deg2rad( 9.3))\nset_voltage!(bus3; mag=1.025, arg=deg2rad( 4.7))\nset_voltage!(bus4; mag=1.026, arg=deg2rad(-2.2))\nset_voltage!(bus5; mag=0.996, arg=deg2rad(-4.0))\nset_voltage!(bus6; mag=1.013, arg=deg2rad(-3.7))\nset_voltage!(bus7; mag=1.026, arg=deg2rad( 3.7))\nset_voltage!(bus8; mag=1.016, arg=deg2rad( 0.7))\nset_voltage!(bus9; mag=1.032, arg=deg2rad( 2.0))\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"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.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"set_current!(bus1; P=0.716, Q= 0.270)\nset_current!(bus2; P=1.630, Q= 0.067)\nset_current!(bus3; P=0.850, Q=-0.109)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"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.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"Concretely, here we're solving for all internal machien states and the reference values for voltage and power of the AVR and govenor models.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"initialize_component!(bus1)\ninitialize_component!(bus2)\ninitialize_component!(bus3)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/#Build-Network","page":"IEEE 9Bus Example","title":"Build Network","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"Finally, we can build the network by providing the vertices and edges.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"vertexfs = [bus1, bus2, bus3, bus4, bus5, bus6, bus7, bus8, bus9];\nedgefs = [l45, l46, l57, l69, l78, l89, t14, t27, t39];\nnw = Network(vertexfs, edgefs)\nu0 = NWState(nw)\nprob = ODEProblem(nw, uflat(u0), (0,100), pflat(u0))\nsol = solve(prob, Rodas5P())\nnothing","category":"page"},{"location":"generated/ieee9bus/#Plotting-the-Solution","page":"IEEE 9Bus Example","title":"Plotting the Solution","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"fig = Figure(size=(1000,2000));\nax = Axis(fig[1, 1]; title=\"Active power\")\nfor i in [1,2,3,5,6,8]\n lines!(ax, sol; idxs=VIndex(i,:busbar₊P), label=\"Bus $i\", color=Cycled(i))\nend\naxislegend(ax)\nax = Axis(fig[2, 1]; title=\"Voltage magnitude\")\nfor i in 1:9\n lines!(ax, sol; idxs=VIndex(i,:busbar₊u_mag), label=\"Bus $i\", color=Cycled(i))\nend\naxislegend(ax)\nax = Axis(fig[3, 1]; title=\"Voltag angel\")\nfor i in 1:9\n lines!(ax, sol; idxs=VIndex(i,:busbar₊u_arg), label=\"Bus $i\", color=Cycled(i))\nend\naxislegend(ax)\nax = Axis(fig[4, 1]; title=\"Frequency\")\nfor i in 1:3\n lines!(ax, sol; idxs=VIndex(i,:machine₊ω), label=\"Bus $i\", color=Cycled(i))\nend\naxislegend(ax)\nfig #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"This page was generated using Literate.jl.","category":"page"}] +[{"location":"ModelingConcepts/#Modeling-Concepts","page":"Modeling Concepts","title":"Modeling Concepts","text":"","category":"section"},{"location":"ModelingConcepts/#Terminal","page":"Modeling Concepts","title":"Terminal","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"The Terminal─Connector is an important building block for every model. It represents a connection point with constant voltage in dq─cordinates u_r and u_i and enforces the kirchoff constraints sum(i_r)=0 and sum(i_i)=0.","category":"page"},{"location":"ModelingConcepts/#Modeling-of-Buses","page":"Modeling Concepts","title":"Modeling of Buses","text":"","category":"section"},{"location":"ModelingConcepts/#Model-class-Injector","page":"Modeling Concepts","title":"Model class Injector","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"An injector is a class of components with a single Terminal() (called :terminal). Examples for injectors might be Generators, Shunts, Loads.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌───────────┐\n(t) │ │\n o←───┤ Injector │\n │ │\n └───────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"The current for injectors is always in injector convention, i.e. positive currents flow out of the injector towards the terminal.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"note: Model classes\nModel \"classes\" are nothing formalized. In this document, a model class is just a description for some ODESystem from ModelingToolkit.jl, which satisfies certain requirements. For example, any ODESystem is considered an \"Injector\" if it contains a connector Terminal() called :terminal.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"details: Code example: definition of PQ load as injector\n","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"using OpPoDyn, OpPoDyn.Library, ModelingToolkit\nusing ModelingToolkit: D_nounits as Dt, t_nounits as t\n@mtkmodel MyPQLoad begin\n @components begin\n terminal = Terminal()\n end\n @parameters begin\n Pset, [description=\"Active Power demand\"]\n Qset, [description=\"Reactive Power demand\"]\n end\n @variables begin\n P(t), [description=\"Active Power\"]\n Q(t), [description=\"Reactive Power\"]\n end\n @equations begin\n P ~ terminal.u_r*terminal.i_r + terminal.u_i*terminal.i_i\n Q ~ terminal.u_i*terminal.i_r - terminal.u_r*terminal.i_i\n # if possible, its better for the solver to explicitly provide algebraic equations for the current\n terminal.i_r ~ (Pset*terminal.u_r + Qset*terminal.u_i)/(terminal.u_r^2 + terminal.u_i^2)\n terminal.i_i ~ (Pset*terminal.u_i - Qset*terminal.u_r)/(terminal.u_r^2 + terminal.u_i^2)\n end\nend\nMyPQLoad(name=:pqload) #hide\nnothing #hide","category":"page"},{"location":"ModelingConcepts/#Model-class-MTKBus","page":"Modeling Concepts","title":"Model class MTKBus","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"A MTKBus isa class of models, which are used to describe the dynamic behavior of a full bus in a power grid. Each MTKBus musst contain a predefined model of type BusBar() (named :busbar). This busbar represents the connection point to the grid. Optionally, it may contain various injectors.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌───────────────────────────────────┐\n │ MTKBus ┌───────────┐ │\n │ ┌──────────┐ ┌──┤ Generator │ │\n │ │ │ │ └───────────┘ │\n │ │ BusBar ├───o │\n │ │ │ │ ┌───────────┐ │\n │ └──────────┘ └──┤ Load │ │\n │ └───────────┘ │\n └───────────────────────────────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Sometimes it is not possible to connect all injectors directly but instead one needs or wants Branches between the busbar and injector terminal. As long as the :busbar is present at the toplevel, there are few limitations on the overall model complexity.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"For simple models (direct connections of a few injectors) it is possible to use the convenience method MTKBus(injectors...) to create the composite model based on provide injector models.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"details: Code example: definition of a Bus containing a swing equation and a load\n","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"using OpPoDyn, OpPoDyn.Library, ModelingToolkit\n@mtkmodel MyMTKBus begin\n @components begin\n busbar = BusBar()\n swing = Swing()\n load = PQLoad()\n end\n @equations begin\n connect(busbar.terminal, swing.terminal)\n connect(busbar.terminal, load.terminal)\n end\nend\nMyMTKBus(name=:bus) #hide\nnothing #hide","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Alternativly, for that system you could have just called","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"mybus = MTKBus(Swing(;name=:swing), PQLoad(;name=:load))\nnothing #hide","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"to get an instance of a model which is structually aquivalent to MyMTKBus.","category":"page"},{"location":"ModelingConcepts/#Line-Modeling","page":"Modeling Concepts","title":"Line Modeling","text":"","category":"section"},{"location":"ModelingConcepts/#Model-class-Branch","page":"Modeling Concepts","title":"Model class Branch","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"A branch is the two-port equivalent to an injector. I needs to have two Terminal()s, one is called :src, the other :dst.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Examples for branches are: PI─Model branches, dynamic RL branches or transformers.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌───────────┐\n(src) │ │ (dst)\n o←──┤ Branch ├──→o\n │ │\n └───────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Both ends follow the injector interface, i.e. current leaving the device towards the terminals is always positive.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"details: Code example: algebraic R-line\n","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"using OpPoDyn, OpPoDyn.Library, ModelingToolkit\n@mtkmodel MyRLine begin\n @components begin\n src = Terminal()\n dst = Terminal()\n end\n @parameters begin\n R=0, [description=\"Resistance\"]\n end\n @equations begin\n dst.i_r ~ (dst.u_r - src.u_r)/R\n dst.i_i ~ (dst.u_i - src.u_i)/R\n src.i_r ~ -dst.i_r\n src.i_i ~ -dst.i_i\n end\nend\nMyRLine(name=:rline) #hide\nnothing #hide","category":"page"},{"location":"ModelingConcepts/#Model-class:-MTKLine","page":"Modeling Concepts","title":"Model class: MTKLine","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Similar to the MTKBus, a MTKLine is a model class which represents a transmission line in the network.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"It musst contain two LineEnd() instances, one called :src, one called :dst.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌────────────────────────────────────────────────┐\n │ MTKLine ┌──────────┐ │\n │ ┌─────────┐ ┌──┤ Branch A │──┐ ┌─────────┐ │\n │ │ LineEnd │ │ └──────────┘ │ │ LineEnd │ │\n │ │ :src ├──o o──┤ :dst │ │\n │ │ │ │ ┌──────────┐ │ │ │ │\n │ └─────────┘ └──┤ Branch B │──┘ └─────────┘ │\n │ └──────────┘ │\n └────────────────────────────────────────────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Simple line models, which consist only of valid Branch models can be instantiated using the MTKLine(branches...) constructor.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"More complex models can be created manually. For example if you want to chain multiple branches between the LineEnds, for example something like","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"LineEnd(:src) ──o── Transformer ──o── Pi─Line ──o── LineEnd(:dst)","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"details: Code example: Transmission line with two pi-branches\n","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"using OpPoDyn, OpPoDyn.Library, ModelingToolkit\n@mtkmodel MyMTKLine begin\n @components begin\n src = LineEnd()\n dst = LineEnd()\n branch1 = DynawoPiLine()\n branch2 = DynawoPiLine()\n end\n @equations begin\n connect(src.terminal, branch1.src)\n connect(src.terminal, branch2.src)\n connect(dst.terminal, branch1.dst)\n connect(dst.terminal, branch2.dst)\n end\nend\nMyMTKLine(name=:mtkline) #hide\nnothing #hide","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Alternatively, an equivalent model with multiple valid branch models in parallel could be created and instantiated with the convenience constructor","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"line = MTKLine(DynawoPiLine(;name=:branch1), DynawoPiLine(;name=:branch2))\nnothing #hide","category":"page"},{"location":"ModelingConcepts/#From-MTK-Models-to-NetworkDynamics","page":"Modeling Concepts","title":"From MTK Models to NetworkDynamics","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Valid MTKLine and MTKBus can be transformed into so called Line and Bus objects.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Line and Bus structs are no MTK models anymore, but rather containers. Currently, they mainly contain a NetworkDynamics component function (VertexModel, EdgeModel).","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Eventually, those models will contain more metadata. For example","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"static representation for powerflow,\npossibly local information about PU system (for transforming parameters between SI/PU),\nmeta information for initialization, for example initialization model or the information which parameters are considered \"tunable\" in order to initialize the dynamical model","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"The exact structure here is not clear yet!","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"The result would look something like that:","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"using OpPoDyn, OpPoDyn.Library, ModelingToolkit\nusing Graphs, NetworkDynamics\nusing OrdinaryDiffEqRosenbrock, OrdinaryDiffEqNonlinearSolve\nusing CairoMakie\nnothing #hide","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Define a swing bus with load","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"# define injectors\n@named swing = Swing(; Pm=1, V=1, D=0.1)\n@named load = PQLoad(; Pset=-.5, Qset=0)\nbus1mtk = MTKBus(swing, load; name=:swingbus)\nvertex1f = Bus(bus1mtk) # extract component function","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Define a second bus as a slack","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"bus2mtk = SlackDifferential(; name=:slackbus)\nvertex2f = Bus(bus2mtk) # extract component function","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Define the powerline connecting both nodes","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"@named branch1 = DynawoPiLine()\n@named branch2 = DynawoPiLine()\nlinemtk = MTKLine(branch1, branch2; name=:powerline)\nedgef = Line(linemtk) # extract component function","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Define the graph, the network and extract initial conditions","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"g = complete_graph(2)\nnw = Network(g, [vertex1f, vertex2f], edgef)\nu0 = NWState(nw) # extract parameters and state from models\nu0.v[1, :swing₊θ] = 0 # set missing initial conditions\nu0.v[1, :swing₊ω] = 1","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Then we can solve the problem","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"prob = ODEProblem(nw, uflat(u0), (0,1), pflat(u0))\nsol = solve(prob, Rodas5P())\n@assert OrdinaryDiffEqRosenbrock.SciMLBase.successful_retcode(sol) # hide\nnothing #hide","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"And finally we can plot the solution:","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"fig = Figure();\nax = Axis(fig[1,1])\nlines!(ax, sol; idxs=VIndex(1,:busbar₊P), label=\"Power injection Bus\", color=Cycled(1))\nlines!(ax, sol; idxs=VIndex(1,:swing₊Pel), label=\"Power injection Swing\", color=Cycled(2))\nlines!(ax, sol; idxs=VIndex(1,:load₊P), label=\"Power injection load\", color=Cycled(3))\naxislegend(ax)\n\nax = Axis(fig[2,1])\nlines!(ax, sol; idxs=VIndex(1,:busbar₊u_arg), label=\"swing bus voltage angle\", color=Cycled(1))\nlines!(ax, sol; idxs=VIndex(2,:busbar₊u_arg), label=\"slack bus voltage angle\", color=Cycled(2))\naxislegend(ax)\nfig #hide","category":"page"},{"location":"ModelingConcepts/#Internals","page":"Modeling Concepts","title":"Internals","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"Internally, we use different input/output conventions for bus and line models. The predefined models BusBar() and LineEnd() are defined in the following way:","category":"page"},{"location":"ModelingConcepts/#Model:-BusBar()","page":"Modeling Concepts","title":"Model: BusBar()","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"A busbar is a concrete model used in bus modeling. It represents the physical connection within a bus, the thing where all injectors and lines attach.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌──────────┐\ni_lines ──→│ │ (t)\n │ Busbar ├───o\n u_bus ←──│ │\n └──────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"It receives the sum of all line currents as an input and equals that to the currents flowing into the terminal. As an output, it gives forwards the terminal voltage to the backend.","category":"page"},{"location":"ModelingConcepts/#Model:-LineEnd()","page":"Modeling Concepts","title":"Model: LineEnd()","text":"","category":"section"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"A LineEnd model is very similar to the BusBar model. It represents one end of a transmission line.","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":" ┌───────────┐\n u_bus ──→│ │ (t)\n │ LineEnd ├───o\ni_line ←──│ │\n └───────────┘","category":"page"},{"location":"ModelingConcepts/","page":"Modeling Concepts","title":"Modeling Concepts","text":"It has special input/output connectors which handle the network interconnection. The main difference beeing the different input/output conventions for the network interface.","category":"page"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = OpPoDyn","category":"page"},{"location":"#OpPoDyn","page":"Home","title":"OpPoDyn","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Documentation for OpPoDyn.","category":"page"},{"location":"#Project-Structure","page":"Home","title":"Project Structure","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The project is structured as follows","category":"page"},{"location":"","page":"Home","title":"Home","text":"OpPoDyn/\n├── assets: contains asses for reference tests\n├── docs: contains this documentation\n├── OpPoDynTesting: helper package for testing, defines test utilities like reference tests\n├── Project.toml\n├── src: source code\n│ ├── Library: submodule for library, all the models live here\n│ └── ...\n└── test: test code","category":"page"},{"location":"","page":"Home","title":"Home","text":"At this stage, this project is meant to be used with the main branch from NetworkDynamics. Unfortunately, it also depends on the unregistered subpackage OpPoDynTesting which makes instantiating the environment a bit tricky (because you can neither add NetworkDynamics#main nor OpPoDyntesting#../OpPoDynTesting without it complaining about the other dependency. Thanks to the [sources] block in Project.toml in Julia v1.11, this shouldn'te be a problem anymore.","category":"page"},{"location":"","page":"Home","title":"Home","text":"If you want to use the realse version of Julia v1.10 I suggest to create a new development environment:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> pwd() # make sure you're in the right folder\n\".../.julia/dev/OpPoDyn\"\n\n(v1.10) pkg> activate devenv\n\n(devenv) pkg> dev NetworkDynamics\n\n(devenv) pkg> dev ./OpPoDyntesting\n\n(devenv) pkg> dev .","category":"page"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"","page":"Home","title":"Home","text":"Modules = [OpPoDyn]","category":"page"},{"location":"#OpPoDyn.ModelMetadataConstructor","page":"Home","title":"OpPoDyn.ModelMetadataConstructor","text":"This type wraps the default constructor of a ModelingToolkit.Model and allows to attach metadata to it. Two types of metadata is possible:\n\na \"name\" field which will become the \"name\" field of the system\na named tuple with aribrary fields which will become the \"metadata\" field of the system\n\n\n\n\n\n","category":"type"},{"location":"#OpPoDyn.separate_differential_constraint_eqs","page":"Home","title":"OpPoDyn.separate_differential_constraint_eqs","text":"separate_differential_constraint_eqs(M, p=nothing)\n\nReturns the constraint equations and differential equations indices from an ODEFunction h(x) used in DifferentialEquations.jl. The ODE h must be in Mass Matrix form meaning: M ẋ = h(x), with M diagonal. h should be inplace.\n\n\n\n\n\n","category":"function"},{"location":"#OpPoDyn.small_signal_stability_analysis","page":"Home","title":"OpPoDyn.small_signal_stability_analysis","text":"small_signal_stability_analysis(h::ODEFunction, eq_point, p = nothing)\n\nPerforms a small signal stability analysis according to: [1] \"Power System Modelling and Scripting\", F. Milano, Chapter 7.2. We find the reduced Jacobian (or State Matrix A_s) and calculate its eigenvalues. If the eigenvalues have positive real parts we classify the grid as unstable.\n\nh: Full DAE system\neq_point: Equilibrium point / fixed point of h. h(eq_point) = 0.0\n\n\n\n\n\n","category":"function"},{"location":"#OpPoDyn.@attach_metadata!-Tuple{Any, Any}","page":"Home","title":"OpPoDyn.@attach_metadata!","text":"@attach_metadata! Model metadata\n\nAllows you to attach additonal metadata to a Model which was previously defined using @mtkmodel. The metadata needs to be in the form of a named tuple (; name=..., field1=..., field2=...).\n\nIf name is present in the metadata, it will be used as the default name of the system and stripped from the metadata. The rest of the named tuple will be attachde to the ODESystems metadata.\n\n\n\n\n\n","category":"macro"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Development of this project was in part funded by the German Federal Ministry for Economic Affairs and Climate Action as part of the OpPoDyn-Project (Project ID 01258425/1, 2024-2027).","category":"page"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"EditURL = \"../../examples/ieee9bus.jl\"","category":"page"},{"location":"generated/ieee9bus/#IEEE-9Bus-Example","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"In this example, we're going to model the IEEE 9 bus system.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"The parameters are mainly adopted from the RTDS data.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"using OpPoDyn\nusing OpPoDyn.Library\nusing ModelingToolkit\nusing NetworkDynamics\nusing Graphs\nusing OrdinaryDiffEqRosenbrock\nusing OrdinaryDiffEqNonlinearSolve\nusing CairoMakie","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"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.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"Ae, Be = Library.solve_ceilf(3.3 => 0.6602, 4.5 => 4.2662)","category":"page"},{"location":"generated/ieee9bus/#Generator-Busses","page":"IEEE 9Bus Example","title":"Generator Busses","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"The 3 generator buses are modeld using a SauerPai 6th order machine model with variable field voltage and mechanical torque input. The field voltage is provided by an AVRTypeI, the torque is provide by a TGOV1 model.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"@mtkmodel GenBus begin\n @components begin\n machine = SauerPaiMachine(;\n vf_input=true,\n τ_m_input=true,\n S_b=100,\n V_b=1,\n ω_b=2π*60,\n R_s=0.000125,\n T″_d0=0.01,\n T″_q0=0.01,\n X_d, X′_d, X″_d, X_q, X′_q, X″_q, X_ls, T′_d0, T′_q0, H # free per machine parameter\n )\n avr = AVRTypeI(vr_min=-5, vr_max=5, Ka=20, Ta=0.2, Kf=0.063,\n Tf=0.35, Ke=1, Te=0.314, Ae, Be, tmeas_lag=false)\n gov = TGOV1(R=0.05, T1=0.05, T2=2.1, T3=7.0, DT=0, V_max=5, V_min=-5)\n busbar = BusBar()\n end\n @equations begin\n connect(machine.terminal, busbar.terminal)\n connect(machine.v_mag_out, avr.vh)\n connect(avr.vf, machine.vf_in)\n connect(gov.τ_m, machine.τ_m_in)\n connect(machine.ωout, gov.ω_meas)\n end\nend\nnothing # hide","category":"page"},{"location":"generated/ieee9bus/#Load-Busses","page":"IEEE 9Bus Example","title":"Load Busses","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"The loard buses are modeled as static PQ loads. This means they allways draw exactly Pset and Qset.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"@mtkmodel LoadBus begin\n @components begin\n busbar = BusBar()\n load = PQLoad(Pset, Qset)\n end\n @equations begin\n connect(load.terminal, busbar.terminal)\n end\nend\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/#Generate-Dynamical-models","page":"IEEE 9Bus Example","title":"Generate Dynamical models","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"The parameters of the machines are obtaind from the data table from the RTDS datasheet.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"gen1p = (;machine__X_ls=0.01460, machine__X_d=0.1460, machine__X′_d=0.0608, machine__X″_d=0.06, machine__X_q=0.1000, machine__X′_q=0.0969, machine__X″_q=0.06, machine__T′_d0=8.96, machine__T′_q0=0.310, machine__H=23.64)\ngen2p = (;machine__X_ls=0.08958, machine__X_d=0.8958, machine__X′_d=0.1198, machine__X″_d=0.11, machine__X_q=0.8645, machine__X′_q=0.1969, machine__X″_q=0.11, machine__T′_d0=6.00, machine__T′_q0=0.535, machine__H= 6.40)\ngen3p = (;machine__X_ls=0.13125, machine__X_d=1.3125, machine__X′_d=0.1813, machine__X″_d=0.18, machine__X_q=1.2578, machine__X′_q=0.2500, machine__X″_q=0.18, machine__T′_d0=5.89, machine__T′_q0=0.600, machine__H= 3.01)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"We instantiate all models as modeling toolkit models.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"@named mtkbus1 = GenBus(; gen1p...)\n@named mtkbus2 = GenBus(; gen2p...)\n@named mtkbus3 = GenBus(; gen3p...)\n@named mtkbus4 = MTKBus()\n@named mtkbus5 = LoadBus(;load__Pset=-1.25, load__Qset=-0.5)\n@named mtkbus6 = LoadBus(;load__Pset=-0.90, load__Qset=-0.3)\n@named mtkbus7 = MTKBus()\n@named mtkbus8 = LoadBus(;load__Pset=-1.0, load__Qset=-0.35)\n@named mtkbus9 = MTKBus()\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"After this, we can build the NetworkDynamic components using the Bus-constructor.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"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.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"@named bus1 = Bus(mtkbus1; vidx=1)\n@named bus2 = Bus(mtkbus2; vidx=2)\n@named bus3 = Bus(mtkbus3; vidx=3)\n@named bus4 = Bus(mtkbus4; vidx=4)\n@named bus5 = Bus(mtkbus5; vidx=5)\n@named bus6 = Bus(mtkbus6; vidx=6)\n@named bus7 = Bus(mtkbus7; vidx=7)\n@named bus8 = Bus(mtkbus8; vidx=8)\n@named bus9 = Bus(mtkbus9; vidx=9)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/#Branches","page":"IEEE 9Bus Example","title":"Branches","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"Branches and Transformers are build from the same PILine model with optional transformer on both ends. However, the data is provided in a way that the actual transformer values are 1.0. Apparently, the transforming action has been absorbed into the line parameters according to the base voltage on both ends.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"For the lines we again make use of the src and dst metadata of the EdgeModel objects for automatic graph construction.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"function piline(; R, X, B)\n @named pibranch = PiLine(;R, X, B_src=B/2, B_dst=B/2, G_src=0, G_dst=0)\n MTKLine(pibranch)\nend\nfunction transformer(; R, X)\n @named transformer = PiLine(;R, X, B_src=0, B_dst=0, G_src=0, G_dst=0)\n MTKLine(transformer)\nend\n\n@named l45 = Line(piline(; R=0.0100, X=0.0850, B=0.1760), src=4, dst=5)\n@named l46 = Line(piline(; R=0.0170, X=0.0920, B=0.1580), src=4, dst=6)\n@named l57 = Line(piline(; R=0.0320, X=0.1610, B=0.3060), src=5, dst=7)\n@named l69 = Line(piline(; R=0.0390, X=0.1700, B=0.3580), src=6, dst=9)\n@named l78 = Line(piline(; R=0.0085, X=0.0720, B=0.1490), src=7, dst=8)\n@named l89 = Line(piline(; R=0.0119, X=0.1008, B=0.2090), src=8, dst=9)\n@named t14 = Line(transformer(; R=0, X=0.0576), src=1, dst=4)\n@named t27 = Line(transformer(; R=0, X=0.0625), src=2, dst=7)\n@named t39 = Line(transformer(; R=0, X=0.0586), src=3, dst=9)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/#Initialization","page":"IEEE 9Bus Example","title":"Initialization","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"To initialize the system, we first set the \"default\" values for the bus voltages according to the provide power flow solution from the document.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"set_voltage!(bus1; mag=1.040, arg=deg2rad( 0.0))\nset_voltage!(bus2; mag=1.025, arg=deg2rad( 9.3))\nset_voltage!(bus3; mag=1.025, arg=deg2rad( 4.7))\nset_voltage!(bus4; mag=1.026, arg=deg2rad(-2.2))\nset_voltage!(bus5; mag=0.996, arg=deg2rad(-4.0))\nset_voltage!(bus6; mag=1.013, arg=deg2rad(-3.7))\nset_voltage!(bus7; mag=1.026, arg=deg2rad( 3.7))\nset_voltage!(bus8; mag=1.016, arg=deg2rad( 0.7))\nset_voltage!(bus9; mag=1.032, arg=deg2rad( 2.0))\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"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.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"set_current!(bus1; P=0.716, Q= 0.270)\nset_current!(bus2; P=1.630, Q= 0.067)\nset_current!(bus3; P=0.850, Q=-0.109)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"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.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"Concretely, here we're solving for all internal machien states and the reference values for voltage and power of the AVR and govenor models.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"initialize_component!(bus1)\ninitialize_component!(bus2)\ninitialize_component!(bus3)\nnothing #hide","category":"page"},{"location":"generated/ieee9bus/#Build-Network","page":"IEEE 9Bus Example","title":"Build Network","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"Finally, we can build the network by providing the vertices and edges.","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"vertexfs = [bus1, bus2, bus3, bus4, bus5, bus6, bus7, bus8, bus9];\nedgefs = [l45, l46, l57, l69, l78, l89, t14, t27, t39];\nnw = Network(vertexfs, edgefs)\nu0 = NWState(nw)\nprob = ODEProblem(nw, uflat(u0), (0,100), pflat(u0))\nsol = solve(prob, Rodas5P())\nnothing","category":"page"},{"location":"generated/ieee9bus/#Plotting-the-Solution","page":"IEEE 9Bus Example","title":"Plotting the Solution","text":"","category":"section"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"fig = Figure(size=(1000,2000));\nax = Axis(fig[1, 1]; title=\"Active power\")\nfor i in [1,2,3,5,6,8]\n lines!(ax, sol; idxs=VIndex(i,:busbar₊P), label=\"Bus $i\", color=Cycled(i))\nend\naxislegend(ax)\nax = Axis(fig[2, 1]; title=\"Voltage magnitude\")\nfor i in 1:9\n lines!(ax, sol; idxs=VIndex(i,:busbar₊u_mag), label=\"Bus $i\", color=Cycled(i))\nend\naxislegend(ax)\nax = Axis(fig[3, 1]; title=\"Voltag angel\")\nfor i in 1:9\n lines!(ax, sol; idxs=VIndex(i,:busbar₊u_arg), label=\"Bus $i\", color=Cycled(i))\nend\naxislegend(ax)\nax = Axis(fig[4, 1]; title=\"Frequency\")\nfor i in 1:3\n lines!(ax, sol; idxs=VIndex(i,:machine₊ω), label=\"Bus $i\", color=Cycled(i))\nend\naxislegend(ax)\nfig #hide","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"","category":"page"},{"location":"generated/ieee9bus/","page":"IEEE 9Bus Example","title":"IEEE 9Bus Example","text":"This page was generated using Literate.jl.","category":"page"}] }