Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanc/Potential in links #47

Merged
merged 10 commits into from
Nov 6, 2024
17 changes: 8 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
# Since EnergyModelsBase doesn't have binary dependencies,
# Since EnergyModelsBase doesn't have binary dependencies,
# only test on a subset of possible platforms.
include:
- version: '1' # The latest point-release (Linux)
Expand All @@ -22,18 +22,18 @@ jobs:
- version: '1' # The latest point-release (Windows)
os: windows-latest
arch: x64
- version: '1.10' # 1.10
- version: 'lts' # lts
os: ubuntu-latest
arch: x64
- version: '1.10' # 1.10
os: ubuntu-latest
arch: x86
- version: 'lts' # lts
os: windows-latest
arch: x64
# - version: 'nightly'
# os: ubuntu-latest
# arch: x64
steps:
- uses: actions/checkout@v3
- uses: julia-actions/setup-julia@v1
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
Expand All @@ -50,5 +50,4 @@ jobs:
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
with:
depwarn: error
- uses: julia-actions/julia-processcoverage@v1
depwarn: error
21 changes: 21 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Release notes

## Unversioned

### Incorporation of bidirectional flow

* Allow (in theory) for nodes and links with bidirectional flow through avoiding hard-coding a lower bound on flow variables.
* No existing links and nodes allow for bidirectional flow.
* Bidirectional flow requires new links and nodes with new methods for the function `is_unidirectional`.

### Rework of links

* Extended the functionality of links significantly.
* Allow for
* differing input and output resources of links as well as specifying these directly,
* emissions of links,
* OPEX of links (with both fixed and variable OPEX created at the same time),
* capacity of links,
* inclusion of specific link variables, and
* investments in links if the links have a capacity.
* The majority of changes are incorporated through filter functions and require the user to define new methods for the included functions (*i.e.*, `has_opex`, `has_emissions`, and `has_capacity`)
* Inclusion of variables follows principle of additional node variables.

## Version 0.8.1 (2024-10-16)

### Bugfixes
Expand Down
10 changes: 5 additions & 5 deletions docs/src/how-to/contribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Contributing to `EnergyModelsBase` can be achieved in several different ways.
The main focus of `EnergyModelsBase` is to provide an easily extensible energy system optimization modelling framework.
Hence, a first approach to contributing to `EnergyModelsBase` is to create a new package with, *e.g.*, the introduction of new node descriptions.

This is explained in [_How to create a new node_](@ref how_to-create_node).
This is explained in *[How to create a new node](@ref how_to-create_node)*.

!!! tip
If you are uncertain how you could incorporate new nodal descriptions, take a look at [`EnergyModelsRenewableProducers`](https://github.com/EnergyModelsX/EnergyModelsRenewableProducers.jl).
Expand Down Expand Up @@ -90,8 +90,8 @@ It is in general preferable to work on a separate branch when developing new com

Incorporate your changes in your new branch.
The changes should be commented to understand the thought process behind them.
In addition, please provide new tests for the added functionality and be certain that the tests run.
The tests should be based on a minimum working example.
In addition, please provide new tests for the added functionality and be certain that the existing tests run.
New tests should be based on a minimum working example in which the new concept is evaluated.

Some existing tests may potentially require changes when incorporating new features (especially within the test set `General tests`).
In this case, it is ok that they are failing and we will comment on the required changes in the pull request.
Expand All @@ -103,7 +103,7 @@ It is not necessary to provide changes directly in the documentation.
It can be easier to include these changes after the pull request is accepted in principal.
It is however a requirement to update the [`NEWS.md`](https://github.com/EnergyModelsX/EnergyModelsBase.jl/blob/main/NEWS.md) file under a new subheading titled "Unversioned".

!!! note
!!! note "Used style in EnergyModelsBase"
Currently, we have not written a style guide for the framework.
We follow in general the conventions of the *[Blue style guide](https://github.com/JuliaDiff/BlueStyle)* with minor modifications.

Expand All @@ -116,4 +116,4 @@ Once you are satisified with your changes, create a pull request towards the mai
We will internally assign the relevant person to the pull request.

You may receive quite a few comments with respect to the incorporation and how it may potentially affect other parts of the code.
Please remaing patient as it may take potentially some time before we can respond to the changes, although we try to answer as fast as possible.
Please remain patient as it may take potentially some time before we can respond to the changes, although we try to answer as fast as possible.
8 changes: 6 additions & 2 deletions docs/src/library/internals/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ CurrentModule = EnergyModelsBase

```@docs
create_link
objective(m, 𝒩, 𝒯, 𝒫, modeltype::EnergyModel)
objective(m, 𝒩, 𝒯, 𝒫, ℒ, modeltype::EnergyModel)
```

## Constraint functions
Expand All @@ -33,11 +33,15 @@ constraints_level_bounds

```@docs
variables_capacity
variables_capex(m, 𝒩, 𝒯, 𝒫, modeltype::EnergyModel)
variables_capex(m, 𝒩, 𝒯, modeltype::EnergyModel)
variables_emission
variables_flow
variables_opex
variables_nodes
variables_links
variables_links_capacity
variables_links_opex
variables_links_capex(m, ℒ, 𝒯, modeltype::EnergyModel)
```

## Check functions
Expand Down
5 changes: 3 additions & 2 deletions docs/src/library/internals/reference_EMIExt.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ check_inv_data
### Methods

```@docs
EMB.variables_capex(m, 𝒩, 𝒯, 𝒫, modeltype::AbstractInvestmentModel)
EMB.variables_capex(m, 𝒩, 𝒯, modeltype::AbstractInvestmentModel)
EMB.variables_links_capex(m, ℒ, 𝒯, modeltype::AbstractInvestmentModel)
EMB.constraints_capacity_installed(m, n::EMB.Node, 𝒯::TimeStructure, modeltype::AbstractInvestmentModel)
EMB.objective(m, 𝒩, 𝒯, 𝒫, modeltype::AbstractInvestmentModel)
EMB.objective(m, 𝒩, 𝒯, 𝒫, ℒ, modeltype::AbstractInvestmentModel)
EMB.check_node_data(n::EMB.Node, data::InvestmentData, 𝒯, modeltype::AbstractInvestmentModel, check_timeprofiles::Bool)
```

Expand Down
1 change: 1 addition & 0 deletions docs/src/library/public/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ See the page *[Creating a new node](@ref how_to-create_node)* for a detailed exp
```@docs
variables_node
create_node
variables_link
```

## [Constraint functions](@id lib-pub-fun-con)
Expand Down
18 changes: 16 additions & 2 deletions docs/src/library/public/links.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,28 @@ Direct
Linear
```

## [Functions for accessing fields of `Link` types](@id lib-pub-fun_field)
## [Functions for accessing fields of `Link` types](@id lib-pub-links-fun_field)

The following functions are declared for accessing fields from a `Link` type.

!!! warning
!!! warning "New link types"
If you want to introduce new `Link` types, it is important that the function `formulation` is either functional for your new types or you have to declare a corresponding function.
The first approach can be achieved through using the same name for the respective fields.

```@docs
inputs(n::Link)
outputs(n::Link)
formulation
link_data
```

## [Functions for identifying `Link`s](@id lib-pub-links-fun_identify)

The following functions are declared for filtering on `Link` types.

```@docs
has_capacity(l::Link)
has_emissions(l::Link)
has_opex(l::Link)
is_unidirectional(l::Link)
```
7 changes: 4 additions & 3 deletions docs/src/library/public/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ The following functions are declared for accessing fields from a `Node` type.
capacity
opex_var
opex_fixed
inputs
outputs
inputs(n::EnergyModelsBase.Node)
outputs(n::EnergyModelsBase.Node)
node_data
charge
level
Expand Down Expand Up @@ -130,7 +130,8 @@ nodes_output
nodes_emissions
has_input
has_output
has_emissions
has_emissions(n::EnergyModelsBase.Node)
has_charge
has_discharge
is_unidirectional(n::EnergyModelsBase.Node)
```
39 changes: 38 additions & 1 deletion docs/src/manual/optimization-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ Instead, it is only dependent on the installed capacity.
It is calculated using the function [`constraints_opex_fixed`](@ref).
It represents fixed costs like labour cost, maintenance, as well as insurances and taxes.

We also introduce the potential for links with operational costs.
By default, links do not introduce new variables.
Operational cost variables are only created for a link ``l`` if the function [`has_opex(n::Link)`](@ref) returns `true`.
The following link variables are then declared representing the operational costs of the links:

- ``\texttt{link\_opex\_var}[l, t_\texttt{inv}]``: Variable OPEX of link ``l`` in strategic period ``t_\texttt{inv}``.
- ``\texttt{link\_opex\_foxed}[l, t_\texttt{inv}]``: Fixed OPEX of link ``l`` in strategic period ``t_\texttt{inv}``.

!!! tip "Links with OPEX"
All links introduced in `EnergyModelsBase` do not allow for operational costs.
If you plan to introduce a link with operational costs, you have to create a new method for the function `has_opex` for your introduced link.

## [Capacity variables](@id man-opt_var-cap)

Capacity variables focus on both the capacity usage and installed capacity.
Expand Down Expand Up @@ -101,7 +113,19 @@ for t_inv ∈ 𝒯ᴵⁿᵛ, n ∈ 𝒩ˢᵘᵇ
end
```

The variables ``\texttt{cap\_inst}``, ``\texttt{stor\_charge\_inst}``, ``\texttt{stor\_level\_inst}``, and ``\texttt{stor\_discharge\_inst}`` are used in `EnergyModelsInvestment` to allow for investments in capacity of individual nodes.
We also introduce the potential for links with capacities.
By default, links do not introduce new variables.
The capacity variable is only created for a link ``l`` if the function [`has_capacity(n::Link)`](@ref) returns `true`.
The following link variable ise then declared representing the capacity of links:

- ``\texttt{link\_cap\_inst}[l, t]``: Installed capacity of link ``l`` at operational period ``t``.

!!! tip "Links with a capacity"
All links introduced in `EnergyModelsBase` do not allow for a capacity limiting the transfer.
If you plan to introduce a link with a capacity, you have to create a new method for the function `has_capacity` for your introduced link.

!!! note "Inclusions of investments"
The variables ``\texttt{cap\_inst}``, ``\texttt{stor\_charge\_inst}``, ``\texttt{stor\_level\_inst}``, ``\texttt{stor\_discharge\_inst}``, and ``\texttt{link\_cap\_inst}`` are used in `EnergyModelsInvestment` to allow for investments in capacity of individual nodes.

## [Flow variables](@id man-opt_var-flow)

Expand Down Expand Up @@ -136,6 +160,19 @@ The following node variable is then declared for all emission resource 𝒫ᵉ

- ``\texttt{emissions\_node}[n, t, p_\texttt{em}]``: Emissions of node ``n`` at operational period ``t`` of emission resource ``p_\texttt{em}``.

Similarly, it is not necessary that links have associated emission variables.
Emission variables are only created for a link ``l`` if the function [`has_emissions(n::Link)`](@ref) returns `true`.
The following link variable is then declared for all emission resource 𝒫ᵉᵐ:

- ``\texttt{emissions\_link}[n, t, p_\texttt{em}]``: Emissions of link ``l`` at operational period ``t`` of emission resource ``p_\texttt{em}``.

!!! tip "Links with emissions"
All links introduced in `EnergyModelsBase` do not allow for emissions.
If you plan to introduce a link with emissions, you have to create a new method for the function `has_emissions` for your introduced link.

We have not implemented a similar approach as for nodes.
It is however planned to allow for transmission emissions in the near future, similar to the concept employed for process emissions for nodes.

In addition, `EnergyModelsBase` declares the following variables for the global emissions:

- ``\texttt{emissions\_total}[t, p_\texttt{em}]``: Total emissions of `ResourceEmit` ``p_\texttt{em}`` in operational period ``t``, and
Expand Down
26 changes: 24 additions & 2 deletions ext/EMIExt/EMIExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ function EMI.has_investment(n::EMB.Node)
)
end

"""
EMI.has_investment(l::Link)

For a given Link `l`, checks that it contains the required investment data.
"""
function EMI.has_investment(l::Link)
(
has_capacity(l) &&
hasproperty(l, :data) &&
!isnothing(findfirst(data -> typeof(data) <: InvestmentData, link_data(l)))
)
end

"""
EMI.has_investment(n::Storage, field::Symbol)

Expand All @@ -53,19 +66,28 @@ EMI.investment_data(inv_data::SingleInvData) = inv_data.cap

"""
EMI.investment_data(n::EMB.Node)
EMI.investment_data(l::Link)
EMI.investment_data(n::EMB.Node, field::Symbol)
EMI.investment_data(l::Link, field::Symbol)

Return the `InvestmentData` of the Node `n` or Link `l`. It will return an error if the
if the Node `n` or Link `l` does not have investment data.

Return the `InvestmentData` of the Node `n` or if `field` is specified, it returns the
`InvData` for the corresponding capacity.
If `field` is specified, it returns the `InvData` for the corresponding capacity.
JulStraus marked this conversation as resolved.
Show resolved Hide resolved
"""
EMI.investment_data(n::EMB.Node) =
filter(data -> typeof(data) <: InvestmentData, node_data(n))[1]
EMI.investment_data(l::Link) =
filter(data -> typeof(data) <: InvestmentData, link_data(l))[1]
EMI.investment_data(n::EMB.Node, field::Symbol) = getproperty(investment_data(n), field)
EMI.investment_data(l::Link, field::Symbol) = getproperty(investment_data(l), field)


EMI.start_cap(n::EMB.Node, t_inv, inv_data::NoStartInvData, cap) =
capacity(n, t_inv)
EMI.start_cap(n::Storage, t_inv, inv_data::NoStartInvData, cap) =
capacity(getproperty(n, cap), t_inv)
EMI.start_cap(l::Link, t_inv, inv_data::NoStartInvData, cap) =
capacity(l, t_inv)

end
25 changes: 23 additions & 2 deletions ext/EMIExt/constraints.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""
constraints_capacity_installed(m, n::Node, 𝒯::TimeStructure, modeltype::AbstractInvestmentModel)
constraints_capacity_installed(m, n::Storage, 𝒯::TimeStructure, modeltype::AbstractInvestmentModel)
constraints_capacity_installed(m, l::Link, 𝒯::TimeStructure, modeltype::AbstractInvestmentModel)

When the modeltype is an investment model, the function introduces the related constraints
for the capacity expansion. The investment mode and lifetime mode are used for adding
constraints.

The default function only accepts nodes with [`SingleInvData`](@ref). If you have several
capacities for investments, you have to dispatch specifically on the node type. This is
implemented for `Storage` nodes where the function introduces the related constraints for
capacities for investments, you have to dispatch specifically on the node or link type. This
is implemented for `Storage` nodes where the function introduces the related constraints for
the capacity expansions for the fields `:charge`, `:level`, and `:discharge`. This requires
the utilization of the [`StorageInvData`](@ref) investment type, in which the investment
mode and lifetime mode are used for adding constraints for each capacity.
Expand Down Expand Up @@ -66,3 +67,23 @@ function EMB.constraints_capacity_installed(
end
end
end
function EMB.constraints_capacity_installed(
m,
l::Link,
𝒯::TimeStructure,
modeltype::AbstractInvestmentModel,
)
if has_investment(l)
# Extract the investment data, the discount rate, and the strategic periods
disc_rate = discount_rate(modeltype)
inv_data = investment_data(l, :cap)
𝒯ᴵⁿᵛ = strategic_periods(𝒯)

# Add the investment constraints
EMI.add_investment_constraints(m, l, inv_data, :cap, :link_cap, 𝒯ᴵⁿᵛ, disc_rate)
else
for t ∈ 𝒯
fix(m[:link_cap_inst][l, t], EMB.capacity(l, t); force = true)
end
end
end
Loading
Loading