Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release notes

## Unversioned

### Minor updates

* Added additional comments (and processing routines) to the examples for improving the understanding.

## Version 0.9.2 (2025-07-03)

### Bugfixes
Expand Down
10 changes: 5 additions & 5 deletions docs/src/nodes/sink.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ The variables of [`Sink`](@ref) nodes include:
- [``\texttt{opex\_fixed}``](@ref man-opt_var-opex)
- [``\texttt{cap\_use}``](@ref man-opt_var-cap)
- [``\texttt{cap\_inst}``](@ref man-opt_var-cap)
- [``\texttt{flow\_out}``](@ref man-opt_var-flow)
- [``\texttt{flow\_in}``](@ref man-opt_var-flow)
- [``\texttt{sink\_surplus}``](@ref man-opt_var-sink)
- [``\texttt{sink\_deficit}``](@ref man-opt_var-sink)
- [``\texttt{emissions\_node}``](@ref man-opt_var-emissions) if `EmissionsData` is added to the field `data`
Expand Down Expand Up @@ -110,10 +110,10 @@ Hence, if you do not have to call additional functions, but only plan to include

```math
\begin{aligned}
\texttt{opex\_var}[n, t_{inv}] = & \\
\sum_{t \in t_{inv}} & surplus\_penalty(n, t) \times \texttt{sink\_surplus}[n, t] + \\ &
deficit\_penalty(n, t) \times \texttt{sink\_deficit}[n, t] \times \\ &
scale\_op\_sp(t_{inv}, t)
\texttt{opex\_var}[n, t_{inv}] = & scale\_op\_sp(t_{inv}, t) \times \\
\sum_{t \in t_{inv}} & ( surplus\_penalty(n, t) \times \texttt{sink\_surplus}[n, t] + \\ &
deficit\_penalty(n, t) \times \texttt{sink\_deficit}[n, t]) \\ &

\end{aligned}
```

Expand Down
142 changes: 98 additions & 44 deletions examples/network.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ more expensive natural gas power plant with CCS to reduce emissions.
function generate_example_network()
@info "Generate case data - Simple network example"

# 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]
# Define the different resources and their emission intensity in t CO₂/MWh
ng = ResourceEmit("NG", 0.2)
coal = ResourceCarrier("Coal", 0.35)
power = ResourceCarrier("Power", 0.0)
co2 = ResourceEmit("CO₂", 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_duration = 2 # Each operational period has a duration of 2 (hours)
op_number = 4 # There are in total 4 operational periods
operational_periods = SimpleTimes(op_number, op_duration)

Expand All @@ -45,19 +45,24 @@ function generate_example_network()
T = TwoLevel(4, 1, operational_periods; op_per_strat)
model = OperationalModel(
Dict( # Emission cap for CO₂ in t/8h and for NG in MWh/8h
CO2 => StrategicProfile([160, 140, 120, 100]),
NG => FixedProfile(1e6),
co2 => StrategicProfile([160, 140, 120, 100]),
ng => FixedProfile(1e6),
),
Dict( # Emission price for CO₂ in EUR/t and for NG in EUR/MWh
CO2 => FixedProfile(0),
NG => FixedProfile(0),
co2 => FixedProfile(0),
ng => FixedProfile(0),
),
CO2, # CO2 instance
co2, # CO₂ instance
)

# Creation of the emission data for the individual nodes.
capture_data = CaptureEnergyEmissions(0.9)
emission_data = EmissionsEnergy()
# Line above: `EmissionsEnergy` implies that the emissions data corresponds to
# emissions through fuel usage as calculated by the CO₂ intensity and efficiency.
capture_data = CaptureEnergyEmissions(0.9)
# Line above: `CaptureEnergyEmissions` implies that the emissions data corresponds
# to emissions through fuel usage as calculated by the CO₂ intensity and efficiency.
# 90 % of the CO₂ emissions are captured as given by the value 0.9.

# 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.
Expand All @@ -68,59 +73,64 @@ function generate_example_network()
FixedProfile(100), # Capacity in MW
FixedProfile(30), # Variable OPEX in EUR/MW
FixedProfile(0), # Fixed OPEX in EUR/MW/8h
Dict(NG => 1), # Output from the Node, in this case, NG
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/8h
Dict(Coal => 1), # Output from the Node, in this case, coal
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/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
# value does not matter
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
# value does not matter as it is not utilized in the model.
[capture_data], # Additional data for emissions and CO₂ capture
),
RefNetworkNode(
"coal power plant", # Node id
FixedProfile(25), # Capacity in MW
FixedProfile(6), # Variable OPEX in EUR/MWh
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
Dict(coal => 2.5), # Input to the node with input ratio
Dict(power => 1), # Output from the node with output ratio
[emission_data], # Additional data for emissions
),
RefStorage{AccumulatingEmissions}(
"CO2 storage", # Node id
"CO₂ storage", # Node id
StorCapOpex(
FixedProfile(60), # 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 8h)
),
StorCap(FixedProfile(600)), # 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.
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
# Line above: In the case of `AccumulatingEmissions`, you must provide the
# stored resource as one of the keys. Its value does however not matter as the
# outlet flow value is fixed to 0.
),
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
Dict(power => 1), # Energy demand and corresponding ratio
),
]

# Connect all nodes with the availability node for the overall energy/mass balance
# NOTE: This hard coding based on indexing is error prone. It is in general advised to
# use a mapping dictionary to avoid any problems when introducing new technology
# nodes.
links = [
Direct("Av-NG_pp", nodes[1], nodes[4], Linear())
Direct("Av-coal_pp", nodes[1], nodes[5], Linear())
Expand All @@ -134,6 +144,8 @@ function generate_example_network()
]

# Input data structure
# It is also explained on
# https://energymodelsx.github.io/EnergyModelsBase.jl/stable/library/public/case_element/
case = Case(T, products, [nodes, links], [[get_nodes, get_links]])
return case, model
end
Expand All @@ -143,21 +155,63 @@ case, model = generate_example_network()
optimizer = optimizer_with_attributes(HiGHS.Optimizer, MOI.Silent() => true)
m = run_model(case, model, optimizer)

"""
process_network_results(m, case)

Function for processing the results to be represented in the a table afterwards.
"""
function process_network_results(m, case)
# Extract the nodes and resources from the case data
ng_ccs_pp, coal_pp, = get_nodes(case)[[4, 5]]
co2 = get_products(case)[4]
𝒯ⁱⁿᵛ = strategic_periods(get_time_struct(case))

# Node variables
coal_pp_use = sort( # Capacity usage of the coal pp
[(
t_inv=t_inv,
val=sum(value.(m[:cap_use][coal_pp, t])*scale_op_sp(t_inv, t) for t ∈ t_inv)
) for t_inv ∈ 𝒯ⁱⁿᵛ],
by = x -> x.t_inv,
)
ng_ccs_pp_use = sort( # Capacity usage of the ng pp
[(
t_inv=t_inv,
val=sum(value.(m[:cap_use][ng_ccs_pp, t])*scale_op_sp(t_inv, t) for t ∈ t_inv)
) for t_inv ∈ 𝒯ⁱⁿᵛ],
by = x -> x.t_inv,
)

# Emission variables
strat_emit = sort( # Strategic emissions
JuMP.Containers.rowtable(
value,
m[:emissions_strategic][:, co2];
header = [:t_inv, :val],
),
by = x -> x.t_inv,
)

# Set up the individual named tuples as a single named tuple
table = [(
t_inv = repr(con_1.t_inv),
coal_pp_use = round(con_1.val; digits=1),
ng_ccs_pp_use = round(con_2.val; digits=1),
CO2_emissions = round(con_3.val; digits=1),
) for (con_1, con_2, con_3) ∈
zip(coal_pp_use, ng_ccs_pp_use, strat_emit)
]
return table
end

# Display some results
ng_ccs_pp, coal_pp, = get_nodes(case)[[4, 5]]
@info "Capacity usage of the coal power plant"
pretty_table(
JuMP.Containers.rowtable(
value,
m[:cap_use][coal_pp, :];
header = [:t, :Value],
),
)
@info "Capacity usage of the natural gas + CCS power plant"
pretty_table(
JuMP.Containers.rowtable(
value,
m[:cap_use][ng_ccs_pp, :];
header = [:t, :Value],
),
table = process_network_results(m, case)

@info(
"Individual strategic results from the simple network:\n" *
"The coal power plant is the preferred power generation unit due to the generation costs.\n" *
"Its usage declines however in subsequent strategic period due to the emission constraints.\n" *
"It is replaced by the natural gas power plant with CO₂ capture as the total strategic\n" *
"emissions follow the emission limits."
)
pretty_table(table)
Loading
Loading