Skip to content

Commit

Permalink
Adjust/EnergyModelsInvestment changes (#45)
Browse files Browse the repository at this point in the history
* Adjustment for changes in EMI
* Moved investment examples to EnergyModelsBase
* Added specific investment tests
* Updated version number and dependencies
  - Adjusted to TimeStruct 0.9
  - Switched to Julia LTS
* Changed CI to 1.10

---------

Co-authored-by: hellemo <[email protected]>
  • Loading branch information
JulStraus and hellemo authored Oct 16, 2024
1 parent b98aa77 commit e128eb8
Show file tree
Hide file tree
Showing 16 changed files with 964 additions and 70 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ jobs:
- version: '1' # The latest point-release (Windows)
os: windows-latest
arch: x64
- version: '1.9' # 1.9
- version: '1.10' # 1.10
os: ubuntu-latest
arch: x64
- version: '1.9' # 1.9
- version: '1.10' # 1.10
os: ubuntu-latest
arch: x86
# - version: 'nightly'
Expand Down
23 changes: 15 additions & 8 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
# Release notes

## Unversioned
## Version 0.8.1 (2024-10-16)

### Bugfixes

* Fixed a bug in which it was possible to have wrong profiles if it must be indexed over an operational scenario or representative period.

### Minor updates
### Adjustment to EnergyModelsInvestments changes

* Included an option to deactive the checks entirely with printing a warning.
* Introduced the variable ``\texttt{stor\_level\_Δ\_sp}`` using `SparseVariables` to simplify the extension in other `Storage` nodes.
* Replaced the function `EMB.multiple` with the function `scale_op_sp` to avoid issues with respect to a function of the same name in `TimeStruct`.
* This type is now exported, simplifying its application in other packages.
* `EMB.multiple` is still included through a deprecation notice. It is however advisable to switch to the new function.
* Adjusted the investment data checks.
* Provided legacy constructors for the previous usage of `SingleInvData`.
* Introduced the investment examples to the example sections.
* Added investment options tests.

### Rework of documentation

* The documentation received a significant rework.
The rework consists of:
* Provide webpages for the individual nodal descriptions in which the fields are described more in detail as well as a description of the math involved in the nodes.
* Providing webpages for the individual nodal descriptions in which the fields are described more in detail as well as a description of the constraints of the individual nodes.
* Restructured both the public and internal libraries

### Minor updates

* Included an option to deactive the checks entirely with printing a warning.
* Introduced the variable ``\texttt{stor\_level\_Δ\_sp}`` using `SparseVariables` to simplify the extension in other `Storage` nodes.
* Replaced the function `EMB.multiple` with the function `scale_op_sp` to avoid issues with respect to a function of the same name in `TimeStruct`.
* This type is now exported, simplifying its application in other packages.
* `EMB.multiple` is still included through a deprecation notice. It is however advisable to switch to the new function.

## Version 0.8.0 (2024-08-20)

### Introduced `EnergyModelsInvestments` as extension
Expand Down
8 changes: 4 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "EnergyModelsBase"
uuid = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
authors = ["Lars Hellemo <[email protected]>, Julian Straus <[email protected]>"]
version = "0.8.0"
version = "0.8.1"

[deps]
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
Expand All @@ -15,8 +15,8 @@ EnergyModelsInvestments = "fca3f8eb-b383-437d-8e7b-aac76bb2004f"
EMIExt = "EnergyModelsInvestments"

[compat]
EnergyModelsInvestments = "0.7"
EnergyModelsInvestments = "0.8"
JuMP = "1"
SparseVariables = "0.7.3"
TimeStruct = "^0.8"
julia = "1.9"
TimeStruct = "0.9"
julia = "1.10"
1 change: 1 addition & 0 deletions examples/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[deps]
EnergyModelsBase = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
EnergyModelsInvestments = "fca3f8eb-b383-437d-8e7b-aac76bb2004f"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Expand Down
18 changes: 9 additions & 9 deletions examples/network.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ using PrettyTables
using TimeStruct

"""
generate_example_data()
generate_example_network()
Generate the data for an example consisting of a simple electricity network.
The more stringent CO₂ emission in latter investment periods force the utilization of the
more expensive natural gas power plant with CCS to reduce emissions.
"""
function generate_example_data()
function generate_example_network()
@info "Generate case data - Simple network example"

# Define the different resources and their emission intensity in tCO2/MWh
Expand Down Expand Up @@ -65,23 +65,23 @@ function generate_example_data()
GenAvailability("Availability", products),
RefSource(
"NG source", # Node id
FixedProfile(1e12), # Capacity in MW
FixedProfile(100), # Capacity in MW
FixedProfile(30), # Variable OPEX in EUR/MW
FixedProfile(0), # Fixed OPEX in EUR/8h
FixedProfile(0), # Fixed OPEX in EUR/MW/8h
Dict(NG => 1), # Output from the Node, in this case, NG
),
RefSource(
"coal source", # Node id
FixedProfile(1e12), # Capacity in MW
FixedProfile(100), # Capacity in MW
FixedProfile(9), # Variable OPEX in EUR/MWh
FixedProfile(0), # Fixed OPEX in EUR/8h
FixedProfile(0), # Fixed OPEX in EUR/MW/8h
Dict(Coal => 1), # Output from the Node, in this case, coal
),
RefNetworkNode(
"NG+CCS power plant", # Node id
FixedProfile(25), # Capacity in MW
FixedProfile(5.5), # Variable OPEX in EUR/MWh
FixedProfile(0), # Fixed OPEX in EUR/8h
FixedProfile(0), # Fixed OPEX in EUR/MW/8h
Dict(NG => 2), # Input to the node with input ratio
Dict(Power => 1, CO2 => 1), # Output from the node with output ratio
# Line above: CO2 is required as output for variable definition, but the
Expand All @@ -92,7 +92,7 @@ function generate_example_data()
"coal power plant", # Node id
FixedProfile(25), # Capacity in MW
FixedProfile(6), # Variable OPEX in EUR/MWh
FixedProfile(0), # Fixed OPEX in EUR/8h
FixedProfile(0), # Fixed OPEX in EUR/MW/8h
Dict(Coal => 2.5), # Input to the node with input ratio
Dict(Power => 1), # Output from the node with output ratio
[emission_data], # Additonal data for emissions
Expand Down Expand Up @@ -144,7 +144,7 @@ function generate_example_data()
end

# Generate the case and model data and run the model
case, model = generate_example_data()
case, model = generate_example_network()
optimizer = optimizer_with_attributes(HiGHS.Optimizer, MOI.Silent() => true)
m = run_model(case, model, optimizer)

Expand Down
196 changes: 196 additions & 0 deletions examples/network_invest.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
using Pkg
# Activate the local environment including EnergyModelsBase, HiGHS, PrettyTables
Pkg.activate(@__DIR__)
# Use dev version if run as part of tests
haskey(ENV, "EMB_TEST") && Pkg.develop(path = joinpath(@__DIR__, ".."))
# Install the dependencies.
Pkg.instantiate()

# Import the required packages
using EnergyModelsBase
using EnergyModelsInvestments
using JuMP
using HiGHS
using PrettyTables
using TimeStruct

"""
generate_example_network_investment()
Generate the data for an example consisting of a simple electricity network.
The more stringent CO₂ emission in latter investment periods force the investment into both
the natural gas power plant with CCS and the CO₂ storage node.
"""
function generate_example_network_investment()
@info "Generate case data - Simple network example with investments"

# Define the different resources and their emission intensity in tCO2/MWh
NG = ResourceEmit("NG", 0.2)
Coal = ResourceCarrier("Coal", 0.35)
Power = ResourceCarrier("Power", 0.0)
CO2 = ResourceEmit("CO2", 1.0)
products = [NG, Coal, Power, CO2]

# Variables for the individual entries of the time structure
op_duration = 2 # Each operational period has a duration of 2
op_number = 4 # There are in total 4 operational periods
operational_periods = SimpleTimes(op_number, op_duration)

# Each operational period should correspond to a duration of 2 h while a duration if 1
# of a strategic period should correspond to a year.
# This implies, that a strategic period is 8760 times longer than an operational period,
# resulting in the values below as "/year".
op_per_strat = 8760

# Creation of the time structure and global data
T = TwoLevel(4, 1, operational_periods; op_per_strat)
model = InvestmentModel(
Dict( # Emission cap for CO₂ in t/year and for NG in MWh/year
CO2 => StrategicProfile([170, 150, 130, 110]) * 1000,
NG => FixedProfile(1e6),
),
Dict( # Emission price for CO₂ in EUR/t and for NG in EUR/MWh

CO2 => FixedProfile(0),
NG => FixedProfile(0),
),
CO2, # CO2 instance
0.07, # Discount rate in absolute value
)

# Creation of the emission data for the individual nodes.
capture_data = CaptureEnergyEmissions(0.9)
emission_data = EmissionsEnergy()

# Create the individual test nodes, corresponding to a system with an electricity demand/sink,
# coal and nautral gas sources, coal and natural gas (with CCS) power plants and CO₂ storage.
nodes = [
GenAvailability("Availability", products),
RefSource(
"NG source", # Node id
FixedProfile(100), # Capacity in MW
FixedProfile(30), # Variable OPEX in EUR/MWh
FixedProfile(0), # Fixed OPEX in EUR/MW/year
Dict(NG => 1), # Output from the Node, in this case, NG
),
RefSource(
"coal source", # Node id
FixedProfile(100), # Capacity in MW
FixedProfile(9), # Variable OPEX in EUR/MWh
FixedProfile(0), # Fixed OPEX in EUR/MW/year
Dict(Coal => 1), # Output from the Node, in this case, coal
),
RefNetworkNode(
"NG+CCS power plant", # Node id
FixedProfile(0), # Capacity in MW
FixedProfile(5.5), # Variable OPEX in EUR/MWh
FixedProfile(0), # Fixed OPEX in EUR/MW/year
Dict(NG => 2), # Input to the node with input ratio
Dict(Power => 1, CO2 => 0), # Output from the node with output ratio
# Line above: CO2 is required as output for variable definition, but the
# value does not matter
[
capture_data, # Additonal data for emissions and CO₂ capture
SingleInvData(
FixedProfile(600 * 1e3), # Capex in EUR/MW
FixedProfile(40), # Max installed capacity [MW]
SemiContinuousInvestment(FixedProfile(5), FixedProfile(40)),
# Line above: Investment mode with the following arguments:
# 1. argument: min added capactity per sp [MW]
# 2. argument: max added capactity per sp [MW]
),
],
),
RefNetworkNode(
"coal power plant", # Node id
FixedProfile(40), # Capacity in MW
FixedProfile(6), # Variable OPEX in EUR/MWh
FixedProfile(0), # Fixed OPEX in EUR/MW/year
Dict(Coal => 2.5), # Input to the node with input ratio
Dict(Power => 1), # Output from the node with output ratio
[emission_data], # Additonal data for emissions
),
RefStorage{AccumulatingEmissions}(
"CO2 storage", # Node id
StorCapOpex(
FixedProfile(0), # Charge capacity in t/h
FixedProfile(9.1), # Storage variable OPEX for the charging in EUR/t
FixedProfile(0) # Storage fixed OPEX for the charging in EUR/(t/h year)
),
StorCap(FixedProfile(1e8)), # Storage capacity in t
CO2, # Stored resource
Dict(CO2 => 1, Power => 0.02), # Input resource with input ratio
# Line above: This implies that storing CO₂ requires Power
Dict(CO2 => 1), # Output from the node with output ratio
# In practice, for CO₂ storage, this is never used.
[
StorageInvData(
charge = NoStartInvData(
FixedProfile(200 * 1e3), # CAPEX [EUR/(t/h)]
FixedProfile(60), # Max installed capacity [EUR/(t/h)]
ContinuousInvestment(FixedProfile(0), FixedProfile(5)),
# Line above: Investment mode with the following arguments:
# 1. argument: min added capactity per sp [t/h]
# 2. argument: max added capactity per sp [t/h]
UnlimitedLife(), # Lifetime mode
),
),
],
),
RefSink(
"electricity demand", # Node id
OperationalProfile([20, 30, 40, 30]), # Demand in MW
Dict(:surplus => FixedProfile(0), :deficit => FixedProfile(1e6)),
# Line above: Surplus and deficit penalty for the node in EUR/MWh
Dict(Power => 1), # Energy demand and corresponding ratio
),
]

# Connect all nodes with the availability node for the overall energy/mass balance
links = [
Direct("Av-NG_pp", nodes[1], nodes[4], Linear())
Direct("Av-coal_pp", nodes[1], nodes[5], Linear())
Direct("Av-CO2_stor", nodes[1], nodes[6], Linear())
Direct("Av-demand", nodes[1], nodes[7], Linear())
Direct("NG_src-av", nodes[2], nodes[1], Linear())
Direct("Coal_src-av", nodes[3], nodes[1], Linear())
Direct("NG_pp-av", nodes[4], nodes[1], Linear())
Direct("Coal_pp-av", nodes[5], nodes[1], Linear())
Direct("CO2_stor-av", nodes[6], nodes[1], Linear())
]

# WIP data structure
case = Dict(
:nodes => nodes,
:links => links,
:products => products,
:T => T,
)
return case, model
end

# Generate the case and model data and run the model
case, model = generate_example_network_investment()
optimizer = optimizer_with_attributes(HiGHS.Optimizer, MOI.Silent() => true)
m = run_model(case, model, optimizer)

# Display some results
ng_ccs_pp, CO2_stor, = case[:nodes][[4, 6]]
@info "Invested capacity for the natural gas plant in the beginning of the \
individual strategic periods"
pretty_table(
JuMP.Containers.rowtable(
value,
m[:cap_add][ng_ccs_pp, :];
header = [:StrategicPeriod, :InvestCapacity],
),
)
@info "Invested capacity for the CO2 storage in the beginning of the
individual strategic periods"
pretty_table(
JuMP.Containers.rowtable(
value,
m[:stor_charge_add][CO2_stor, :];
header = [:StrategicPeriod, :InvestCapacity],
),
)
10 changes: 5 additions & 5 deletions examples/sink_source.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ using PrettyTables
using TimeStruct

"""
generate_example_data_ss()
generate_example_ss()
Generate the data for an example consisting of an electricity source and sink. It shows how
the source adjusts to the demand.
"""
function generate_example_data_ss()
function generate_example_ss()
@info "Generate case data - Simple sink-source example"

# Define the different resources and their emission intensity in tCO2/MWh
Expand Down Expand Up @@ -51,9 +51,9 @@ function generate_example_data_ss()
nodes = [
RefSource(
"electricity source", # Node id
FixedProfile(1e12), # Capacity in MW
FixedProfile(50), # Capacity in MW
FixedProfile(30), # Variable OPEX in EUR/MW
FixedProfile(0), # Fixed OPEX in EUR/8h
FixedProfile(0), # Fixed OPEX in EUR/MW/8h
Dict(Power => 1), # Output from the Node, in this case, Power
),
RefSink(
Expand Down Expand Up @@ -81,7 +81,7 @@ function generate_example_data_ss()
end

# Generate the case and model data and run the model
case, model = generate_example_data_ss()
case, model = generate_example_ss()
optimizer = optimizer_with_attributes(HiGHS.Optimizer, MOI.Silent() => true)
m = run_model(case, model, optimizer)

Expand Down
Loading

2 comments on commit e128eb8

@JulStraus
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/117386

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.8.1 -m "<description of version>" e128eb852b19fbfd9eaea96033cd37d59d15c224
git push origin v0.8.1

Please sign in to comment.