Skip to content

Commit

Permalink
Merge branch 'main' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
sstroemer committed Sep 6, 2024
2 parents 43a18a5 + 0b4840a commit 47e8b53
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 36 deletions.
1 change: 1 addition & 0 deletions .github/workflows/Documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
shell: julia --project=docs --color=yes {0}
run: |
using Pkg
Pkg.update()
Pkg.develop(PackageSpec(path=pwd()))
Pkg.instantiate()
- uses: julia-actions/julia-buildpkg@v1
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.4] - 2024-07-26

Migrate full core component parameter docstrings.

## [1.0.3] - 2024-06-18

Relax version requirements on IESoptLib to include all `v0.2.z` versions.

## [1.0.2] - 2024-06-10

Fix solver setup for various workflows.
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "IESopt"
uuid = "ed3f0a38-8ad9-4cf8-877e-929e8d190fe9"
version = "1.0.3"
version = "1.0.4"

[deps]
ArgCheck = "dce04be8-c92d-5529-be00-80e4d2c0e197"
Expand Down Expand Up @@ -54,7 +54,7 @@ Dates = "<0.0.1,1"
GLPK = "1.2.1"
Gurobi = "1.3.0"
HiGHS = "1.9"
IESoptLib = "0.1, 0.2"
IESoptLib = "0.3"
Ipopt = "1.6.2"
JLD2 = "0.4"
JSON = "0.21"
Expand Down
12 changes: 4 additions & 8 deletions src/IESopt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,10 @@ function _build_model!(model::JuMP.Model; callbacks::Union{Nothing, Dict})

@info "Preparing components"

# Place Decisions first, since those need to be built before everything else.
corder = Vector{_CoreComponent}(
collect(component for component in values(_iesopt(model).model.components) if component isa Decision),
)
append!(
corder,
collect(component for component in values(_iesopt(model).model.components) if !(component isa Decision)),
)
# Sort components by their build priority.
# For instance, Decisions with a default build priority of 1000 are built before all other components
# with a default build priority of 0
corder = sort(collect(values(_iesopt(model).model.components)); by=_build_priority, rev=true)

@info "Start creating JuMP model"
components_with_addons = []
Expand Down
5 changes: 5 additions & 0 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ _component_type(::Node) = :Node
_component_type(::Profile) = :Profile
_component_type(::Unit) = :Unit

_build_priority(cc::_CoreComponent) = _build_priority(cc.build_priority, 0.0)
_build_priority(::Nothing, default) = default
_build_priority(priority::Real, ::T) where {T} = convert(T, priority)
_build_priority(priority, ::Any) = @error "Unsupported build priority" priority

function Base.getproperty(cc::_CoreComponent, field::Symbol)
try
(field == :var) && (return getfield(cc, :_ccoc).variables)
Expand Down
47 changes: 43 additions & 4 deletions src/core/connection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,61 @@ A `Connection` is used to model arbitrary flows of energy between `Node`s. It al
# [Mandatory] ======================================================================================================
name::_String

raw"""```{"mandatory": "yes", "values": "string", "default": "-"}```
raw"""```{"mandatory": "yes", "values": "string", "unit": "-", "default": "-"}```
This `Connection` models a flow from `node_from` to `node_to` (both are `Node`s).
"""
node_from::Union{_String, Nothing} = nothing

raw"""```{"mandatory": "yes", "values": "string", "default": "-"}```
raw"""```{"mandatory": "yes", "values": "string", "unit": "-", "default": "-"}```
This `Connection` models a flow from `node_from` to `node_to` (both are `Node`s).
"""
node_to::Union{_String, Nothing} = nothing

carrier::Carrier

# [Optional] =======================================================================================================
config::Dict{String, Any} = Dict()
ext::Dict{String, Any} = Dict()
addon::Union{String, Nothing} = nothing
conditional::Bool = false

raw"""```{"mandatory": "no", "values": "string", "unit": "-", "default": "-"}```
`Carrier` of this `Connection`. If not given, automatically picks the `carrier` of the `Node`s it connects. This
parameter is not necessary, and only exists to allow for a more explicit definition.
"""
carrier::Carrier

raw"""```{"mandatory": "no", "values": "numeric, `col@file`, `decision:value`", "unit": "power", "default": "``+\\infty``"}```
The symmetric bound on this `Connection`'s flow. Results in `lb = -capacity` and `ub = capacity`. Must not be
specified if `lb`, `ub`, or both are explicitly stated.
"""
capacity::_OptionalExpression = nothing

raw"""```{"mandatory": "no", "values": "numeric, `col@file`, `decision:value`", "unit": "power", "default": "``-\\infty``"}```
Lower bound of this `Connection`'s flow.
"""
lb::_OptionalExpression = nothing

raw"""```{"mandatory": "no", "values": "numeric, `col@file`, `decision:value`", "unit": "power", "default": "``+\\infty``"}```
Upper bound of this `Connection`'s flow.
"""
ub::_OptionalExpression = nothing

raw"""```{"mandatory": "no", "values": "numeric", "unit": "monetary (per energy)", "default": "-"}```
Cost of every unit of energy flow over this connection that is added to the model's objective function. Keep in mind
that negative flows will induce negative costs, which can be used to model revenues. Further, a bidirectional
`Connection` (if `lb < 0`, which is the default, or if `capacity` is used) with a positive `cost` will lead to
negative costs for the reverse flow. If you do not want this, split the `Connection` into two separate ones, each
being unidirectional (with `lb: 0`). Remember, that these can share the same "capacity" (which is then set as`ub`),
even when using `decision:value` or `col@file` as value.
"""
cost::_OptionalExpression = nothing

raw"""```{"mandatory": "no", "values": "``\\in [0, 1]``", "unit": "-", "default": "0"}```
Fractional loss when transfering energy. This loss occurs "at the destination", which means that for a loss of 5%,
set as `loss: 0.05`, and considering a `Snapshot` where the `Connection` has a flow value of `100`, it will
"extract" `100` from `node_from` and "inject" `95` into `node_to`. Since the flow variable is given as power, this
would, e.g., translate to consuming 200 units of energy at `node_from` and injecting 190 units at `node_to`, if the
`Snapshot` duration is 2 hours.
"""
loss::_OptionalExpression = nothing

# Energy Transfer Distribution Factors
Expand All @@ -47,6 +79,13 @@ A `Connection` is used to model arbitrary flows of energy between `Node`s. It al
pf_R::_OptionalScalarInput = nothing
pf_B::_OptionalScalarInput = nothing

raw"""```{"mandatory": "no", "values": "numeric", "unit": "-", "default": "`0`"}```
Priority for the build order of components. Components with higher build_priority are built before.
This can be useful for addons, that connect multiple components and rely on specific components being initialized
before others.
"""
build_priority::_OptionalScalarInput = nothing

# [Internal] =======================================================================================================
# -

Expand Down
50 changes: 47 additions & 3 deletions src/core/decision.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,60 @@ component's settings, as well as have associated costs.
addon::Union{String, Nothing} = nothing
conditional::Bool = false

raw"""```{"mandatory": "no", "values": "numeric", "default": "`0`"}```
Minimum size of the decision (considered for each "unit" if count allows multiple "units").
raw"""```{"mandatory": "no", "values": "numeric", "unit": "-", "default": "`0`"}```
Minimum size of the decision value (considered for each "unit" if count allows multiple "units").
"""
lb::_OptionalScalarInput = 0

raw"""```{"mandatory": "no", "values": "numeric", "unit": "-", "default": "``+\\infty``"}```
Maximum size of the decision value (considered for each "unit" if count allows multiple "units").
"""
ub::_OptionalScalarInput = nothing
fixed_value::_OptionalScalarInput = nothing

raw"""```{"mandatory": "no", "values": "numeric", "unit": "monetary (per value)", "default": "`0`"}```
Cost that the decision value induces, given as ``cost \cdot value``.
"""
cost::_OptionalScalarInput = nothing

raw"""```{"mandatory": "no", "values": "numeric", "unit": "-", "default": "-"}```
If `mode: fixed`, this value is used as the fixed value of the decision. This can be useful if this `Decision` was
used in a previous optimization and its value should be fixed to that value in the next optimization (applying it
where ever it is used, instead of needing to find all usages). Furthermore, this allows extracting the dual value of
the constraint that fixes the value, assisting in approaches like Benders decomposition. Note that this does not
change the induced cost in any way.
"""
fixed_value::_OptionalScalarInput = nothing

raw"""```{"mandatory": "no", "values": "-", "unit": "monetary", "default": "-"}```
This setting activates a "fixed cost" component for this decision variable, which requires that the model's problem
type allows for binary variables (e.g., `MILP`). This can be used to model fixed costs that are only incurred if the
decision variable is active (e.g., a fixed cost for an investment that is only incurred if the investment is made).
If the decision is `0`, no fixed costs have to be paid; however, if the decision is greater than `0`, the fixed cost
is incurred. Note that after deciding to activate the decision, the overall value is still determined in the usual
(continuous) way, incuring the (variable) `cost` as well. More complex cost functions can be modelled by switching
to mode `sos1` or `sos2` and using the `sos` parameter.
"""
fixed_cost::_OptionalScalarInput = nothing

raw"""```{"mandatory": "no", "values": "`linear`, `binary`, `integer`, `sos1`, `sos2`, `fixed`", "unit": "-", "default": "`linear`"}```
Type of the decision variable that is constructed. `linear` results in a continuous decision, `integer` results in a
integer variable, `binary` constrains it to be either `0` or `1`. `sos1` and `sos2` can be used to activate SOS1 or
SOS2 mode (used for piecewise linear costs). See `fixed_value` if setting this to `fixed`.
"""
mode::Symbol = :linear

raw"""```{"mandatory": "no", "values": "list", "unit": "-", "default": "-"}```
TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).
"""
sos::Vector{Dict{String, Float64}} = Vector()

raw"""```{"mandatory": "no", "values": "numeric", "unit": "-", "default": "`1000`"}```
Priority for the build order of components. Components with higher build_priority are built before.
This can be useful for addons, that connect multiple components and rely on specific components being initialized
before others.
"""
build_priority::_OptionalScalarInput = nothing

# [Internal] =======================================================================================================
# -

Expand Down Expand Up @@ -118,6 +160,8 @@ function _result(decision::Decision, mode::String, field::String; result::Int=1)
return nothing
end

_build_priority(decision::Decision) = _build_priority(decision.build_priority, 1000.0)

include("decision/con_fixed.jl")
include("decision/con_sos_value.jl")
include("decision/con_sos1.jl")
Expand Down
29 changes: 18 additions & 11 deletions src/core/node.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ balance equation. This allows using `Node`s for various storage tasks (like batt

# [Mandatory] ======================================================================================================
name::_String
raw"""```{"mandatory": "yes", "values": "string", "default": "-"}```
raw"""```{"mandatory": "yes", "values": "string", "unit": "-", "default": "-"}```
`Carrier` of this `Node`. All connecting components need to respect that.
"""
carrier::Carrier
Expand All @@ -42,57 +42,57 @@ balance equation. This allows using `Node`s for various storage tasks (like batt
addon::Union{String, Nothing} = nothing
conditional::Bool = false

raw"""```{"mandatory": "no", "values": "`true` or `false`", "default": "`false`"}```
raw"""```{"mandatory": "no", "values": "`true`, `false`", "unit": "-", "default": "`false`"}```
If `true`, the `Node` is considered to have an internal state ("stateful `Node`"). This allows it to act as energy
storage. Connect `Connection`s or `Unit`s to it, acting as charger/discharger.
"""
has_state::Bool = false

raw"""```{"mandatory": "no", "values": "numeric", "default": "``-\\infty``"}```
raw"""```{"mandatory": "no", "values": "numeric, `col@file`, `decision:value`", "unit": "energy", "default": "``-\\infty``"}```
Lower bound of the internal state, requires `has_state = true`.
"""
state_lb::_OptionalExpression = nothing

raw"""```{"mandatory": "no", "values": "numeric", "default": "``+\\infty``"}```
raw"""```{"mandatory": "no", "values": "numeric, `col@file`, `decision:value`", "unit": "energy", "default": "``+\\infty``"}```
Upper bound of the internal state, requires `has_state = true`.
"""
state_ub::_OptionalExpression = nothing

raw"""```{"mandatory": "no", "values": "`eq`, `geq`, or `disabled`", "default": "`eq`"}```
raw"""```{"mandatory": "no", "values": "`eq`, `geq`, or `disabled`", "unit": "-", "default": "`eq`"}```
Controls how the state considers the boundary between last and first `Snapshot`. `disabled` disables cyclic
behaviour of the state (see also `state_initial`), `eq` leads to the state at the end of the year being the initial
state at the beginning of the year, while `geq` does the same while allowing the end-of-year state to be higher (=
"allowing to destroy energy at the end of the year").
"""
state_cyclic::Symbol = :eq

raw"""```{"mandatory": "no", "values": "numeric", "default": "-"}```
raw"""```{"mandatory": "no", "values": "numeric", "unit": "energy", "default": "-"}```
Sets the initial state. Must be used in combination with `state_cyclic = disabled`.
"""
state_initial::_OptionalScalarInput = nothing

raw"""```{"mandatory": "no", "values": "numeric", "default": "-"}```
raw"""```{"mandatory": "no", "values": "numeric", "unit": "energy", "default": "-"}```
Sets the final state. Must be used in combination with `state_cyclic = disabled`.
"""
state_final::_OptionalScalarInput = nothing

raw"""```{"mandatory": "no", "values": "numeric", "default": "`0`"}```
raw"""```{"mandatory": "no", "values": "``\\in [0, 1]``", "unit": "-", "default": "0"}```
Per `Snapshot` percentage loss of state (loosing 1% should be set as `0.01`).
"""
state_percentage_loss::_OptionalScalarInput = nothing

raw"""```{"mandatory": "no", "values": "`enforce`, `destroy`, or `create`", "default": "`enforce`"}```
raw"""```{"mandatory": "no", "values": "`enforce`, `destroy`, or `create`", "unit": "-", "default": "`enforce`"}```
Can only be used for `has_state = false`. `enforce` forces total injections to always be zero (similar to
Kirchhoff's current law), `create` allows "supply < demand", `destroy` allows "supply > demand", at this `Node`.
"""
nodal_balance::Symbol = :enforce

raw"""```{"mandatory": "no", "values": "integer", "default": "-"}```
raw"""```{"mandatory": "no", "values": "integer", "unit": "-", "default": "-"}```
TODO.
"""
sum_window_size::_OptionalScalarInput = nothing

raw"""```{"mandatory": "no", "values": "integer", "default": "`1`"}```
raw"""```{"mandatory": "no", "values": "integer", "unit": "-", "default": "`1`"}```
TODO.
"""
sum_window_step::_ScalarInput = 1
Expand All @@ -102,6 +102,13 @@ balance equation. This allows using `Node`s for various storage tasks (like batt
# Powerflow
pf_slack::Bool = false

raw"""```{"mandatory": "no", "values": "numeric", "unit": "-", "default": "`0`"}```
Priority for the build order of components. Components with higher build_priority are built before.
This can be useful for addons, that connect multiple components and rely on specific components being initialized
before others.
"""
build_priority::_OptionalScalarInput = nothing

# [Internal] =======================================================================================================
# -

Expand Down
Loading

0 comments on commit 47e8b53

Please sign in to comment.