Skip to content

Commit

Permalink
upstream features developed for MarieProject (#212)
Browse files Browse the repository at this point in the history
  • Loading branch information
hexaeder authored Mar 22, 2024
1 parent 66b05d2 commit 9dd159a
Show file tree
Hide file tree
Showing 14 changed files with 1,235 additions and 18 deletions.
31 changes: 19 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,25 @@ jobs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
with:
version: '1.6'
#- run: |
# git config --global user.name name
# git config --global user.email email
# git config --global github.user username
- name: Install dependencies
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy
version: '1'
- name: Configure doc environment
shell: julia --project=docs --color=yes {0}
run: |
using Pkg
Pkg.develop(PackageSpec(path=pwd()))
Pkg.instantiate()
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-docdeploy@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key
run: julia --project=docs/ docs/make.jl
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
- name: Run doctests
shell: julia --project=docs --color=yes {0}
run: |
using Documenter: DocMeta, doctest
using PowerDynamics
DocMeta.setdocmeta!(PowerDynamics, :DocTestSetup, :(using PowerDynamics); recursive=true)
doctest(PowerDynamics)
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ Manifest.toml

target/
.vscode/
/docs/src/generated/
6 changes: 6 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "3.1.6"

[deps]
BlockSystems = "4663d367-db1f-4bef-81e7-dc1bd7f7b428"
ControlSystems = "a6e380b2-a6ca-5380-bf3e-84a91bcd477e"
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
Expand All @@ -15,6 +16,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56"
NetworkDynamics = "22e9dc34-2a0d-11e9-0de0-8588d035468b"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Expand All @@ -28,16 +30,19 @@ SciMLNLSolve = "e9a6253c-8580-4d32-9898-8661bb511710"
Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[compat]
BlockSystems = "0.4.2"
ControlSystems = "1"
DiffEqBase = "^6.9.4"
DiffEqCallbacks = "^2"
Graphs = "1.4"
Ipopt = "0.7, 0.8, 0.9, 1"
JSON = "^0.21.0"
Lazy = "^0.14.0, 0.15"
MacroTools = "^0.5.1"
ModelingToolkit = "8"
NLsolve = "^4.1.0"
NetworkDynamics = "0.8"
OrderedCollections = "1.3"
Expand All @@ -51,6 +56,7 @@ SciMLNLSolve = "0.1"
Setfield = "1"
StaticArrays = "1"
SteadyStateDiffEq = "1, 2"
Unitful = "1"
julia = "1.6"

[extras]
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ If you use PowerDynamics.jl in your research publications, [please cite our pape
publisher={Elsevier}
}
```

## Funding

Development of `PowerDynamics.jl` was funded by the *German Federal Ministry for Economic Affairs and Climate Action*

<img src="bmwk_logo_en.svg" width="300"/>
1 change: 1 addition & 0 deletions bmwk_logo_en.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 7 additions & 6 deletions docs/localmake.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ At the end of each run the user is prompted to rerun the make process. Using rev
use the updated `*.md` and source files. This way the Julia session keeps alive and the
individual builds are much faster.
=#
using Revise
using LiveServer
using REPL.TerminalMenus

port = isempty(ARGS) ? 8000 : parse(Int, ARGS[1])
@assert 8000 port 9000 "port has to be in range 8000..9000!"

using Pkg
Pkg.activate(@__DIR__)
Pkg.develop(PackageSpec(path=dirname(@__DIR__))) # adds the package this script is called from
Pkg.instantiate()
Pkg.update()

using Revise
using LiveServer
using REPL.TerminalMenus

port = isempty(ARGS) ? 8000 : parse(Int, ARGS[1])
@assert 8000 port 9000 "port has to be in range 8000..9000!"

@info "Start server..."
@async serve(;dir=joinpath(@__DIR__, "build"), port)

Expand Down
4 changes: 4 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using Documenter
using PowerDynamics
using BlockSystems
using Literate

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

Literate.markdown(joinpath(@__DIR__, "src", "MARiE.jl"), joinpath(@__DIR__, "src", "generated"))

makedocs(
modules = [PowerDynamics],
authors = "Tim Kittel, Jan Liße, Sabine Auer and further contributors.",
Expand All @@ -16,6 +19,7 @@ makedocs(
"powergrid_model.md",
"node_types.md",
"custom_node_types.md",
"Modular Inverters" => "generated/MARiE.md",
"line_types.md",
"states_solutions.md",
"simulations.md",
Expand Down
145 changes: 145 additions & 0 deletions docs/src/MARiE.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#=
```@meta
CurrentModule = PowerDynamics.ModularInverter
```
# Modular Inverter Framework
This section describes the modular inverter framework developed in the MARiE-Project funded by the
*German Federal Ministry for Economic Affairs and Climate Action*.
## Structure
The inverter model is split in two parts: the inner loop and the outer loop.
The *inner loop* either acts as a voltage source
```
+---------+
u_d_ref -->| |
u_q_ref -->| Voltage |--> u_d
i_d_dis -->| Source |--> u_q
i_q_dis -->| |
+---------+
```
which gets a `dq` voltage reference and tries to achive this reference despite the `dq` current disturbance.
Alternatively, the inner loop can mimic a current source, which tries to realsize a given current setpoint despite a disturbance voltage at the output terminals:
```
+---------+
i_d_ref -->| |
i_q_ref -->| Current |--> i_d
u_d_dis -->| Source |--> i_q
u_q_dis -->| |
+---------+
```
The *outer loop* provides references to the inner loop blocks.
As an input, outerloops have acces to the measured currents and voltages at the point of common coupling.
They either eject a dq-reference for the inner voltage source or the inner current soruce:
```
+---------+
i_d_meas -->| |
i_q_meas -->| Outer |--> u_d_ref or i_d_ref
u_d_meas -->| Loop |--> u_q_ref or i_q_ref
u_q_meas -->| |
+---------+
```
`PowerDynamics.jl` provides predefined outer loop as well as inner loop models as `IOBlocks` provide by `BlockSystems.jl`, which can be used to create `IONodes` (see [Custom-Nodes-using-BlockSystems.jl](@ref)).
## Example
In order to construct a modular inverter we need to import the `ModularInverter` module to the namespace:
=#
using PowerDynamics
using BlockSystems
using PowerDynamics.ModularInverter
nothing #hide
#=
First, we need to define the inner loop as an `IOBlock` object.
You can either use your own or make use of the [Predefined Inner Loops](@ref).
=#
BlockSystems.WARN[] = false # hide
inner = CascadedVoltageControl()
#=
As you can see, the input/output structure of that innerloop adheres to the volten source innerloop convention.
Next you define the outer loop, either create you own based on the interface or pick from the [Predefined Outer Loops](@ref).
=#
outer = DroopControl()
#=
There are still a lot of open parameters, we need to assign specific values to create the `IONode`(@ref) later.
We can do so by using the keyword aguments of the constructor:
=#
outer = DroopControl(;
ω_ref = 2π*50, # frequency reference
V_ref = 1, # voltage reference (pu)
P_ref = 1, # active power reference (pu)
Q_ref = 0, # reactive power reference (pu)
K_P = 0.4, # active droop gain
K_Q = 0.004, # reactive droop gain
τ_P = 0.1, # active power filter constant
τ_Q = 0.1, # reactive power filter constant
)

#=
The full inverter model is created by combining outer and inner model:
=#
inv = Inverter(inner, outer)
#=
Which satisfies the interface for `IONodes`. To create the node
=#
node = IONode(inv);

#=
Which is a valid node definition which can be used in any `PowerDynamics.jl` context.
=#

#=
## Model Reduction
`PowerDynamics.jl` also implements automated model order reduction of linear models using the balance residualization technique.
Internaly, we make use of the method implementaion in `ControlSystems.jl`. For that, a linear block (such as the cascaded inner loops) can be decomposed in to its `A`, `B`, `C` and `D` matrix for regular LTI representation.
For example, we could creat a 10th order representation of the previously defined inner loop by calling
=#
reduced_inner = balresid(inner, 10; reconstruct=false)
#=
To obtain the reduced order model we can construct the inverter again:
=#
reduced_inverter = Inverter(reduced_inner, outer)
# ... and the reduced node
reduced_node = IONode(reduced_inverter);

#=
```@docs
balresid
```
=#

#=
## Predefined Inner Loops
```@docs
CascadedVoltageControl
CascadedVoltageControlCS
CascadedCurrentControl
PT1Source
IdealSource
IdealCSource
```
## Predefined Outer Loops
```@docs
FixedVoltage
DroopControl
Synchronverter
PLLCurrent
ConstantPower
FiltConstantPower
```
## Inverter Construction
```@docs
Inverter
InverterCS
```
=#
30 changes: 30 additions & 0 deletions src/IONodes/IOComponents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,34 @@ function ImpedanceConstraint(;name=gensym(:rxconstraint), renamings...)
return isempty(renamings) ? block : rename_vars(block; renamings...)
end

"""
Cart2Polar(;name=:c2p, renamings...)
(X, Y) ↦ (mag, arg) transformation
"""
function Cart2Polar(;name=:c2p, renamings...)
@variables t arg(t) mag(t)
@parameters x(t) y(t)
block = IOBlock([mag ~ (x^2 + y^2),
arg ~ atan(y, x)],
[x, y], [mag, arg]; name)

replace_vars(block; renamings...)
end

"""
Polar2Cart(;name=:p2c, renamings...)
(mag, arg) ↦ (X, Y) transformation
"""
function Polar2Cart(;name=:p2c, renamings...)
@variables t x(t) y(t)
@parameters arg(t) mag(t)
block = IOBlock([x ~ mag * cos(arg),
y ~ mag * sin(arg)],
[mag, arg], [x, y]; name)

replace_vars(block; renamings...)
end

end # module
1 change: 1 addition & 0 deletions src/IONodes/IONode.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function IONode(bp::BlockPara)
end

IONode(blk::IOBlock, para::Dict) = IONode(BlockPara(blk, para))
IONode(blk::IOBlock) = IONode(BlockPara(blk, Dict()))

# extend the necessary functions for the `AbstractNode` interface
function construct_vertex(ion::IONode)
Expand Down
Loading

0 comments on commit 9dd159a

Please sign in to comment.