@@ -23,15 +23,15 @@ more expensive natural gas power plant with CCS to reduce emissions.
2323function generate_example_network ()
2424 @info " Generate case data - Simple network example"
2525
26- # Define the different resources and their emission intensity in tCO2 /MWh
27- NG = ResourceEmit (" NG" , 0.2 )
28- Coal = ResourceCarrier (" Coal" , 0.35 )
29- Power = ResourceCarrier (" Power" , 0.0 )
30- CO2 = ResourceEmit (" CO2 " , 1.0 )
31- products = [NG, Coal, Power, CO2 ]
26+ # Define the different resources and their emission intensity in t CO₂ /MWh
27+ ng = ResourceEmit (" NG" , 0.2 )
28+ coal = ResourceCarrier (" Coal" , 0.35 )
29+ power = ResourceCarrier (" Power" , 0.0 )
30+ co2 = ResourceEmit (" CO₂ " , 1.0 )
31+ products = [ng, coal, power, co2 ]
3232
3333 # Variables for the individual entries of the time structure
34- op_duration = 2 # Each operational period has a duration of 2
34+ op_duration = 2 # Each operational period has a duration of 2 (hours)
3535 op_number = 4 # There are in total 4 operational periods
3636 operational_periods = SimpleTimes (op_number, op_duration)
3737
@@ -45,19 +45,24 @@ function generate_example_network()
4545 T = TwoLevel (4 , 1 , operational_periods; op_per_strat)
4646 model = OperationalModel (
4747 Dict ( # Emission cap for CO₂ in t/8h and for NG in MWh/8h
48- CO2 => StrategicProfile ([160 , 140 , 120 , 100 ]),
49- NG => FixedProfile (1e6 ),
48+ co2 => StrategicProfile ([160 , 140 , 120 , 100 ]),
49+ ng => FixedProfile (1e6 ),
5050 ),
5151 Dict ( # Emission price for CO₂ in EUR/t and for NG in EUR/MWh
52- CO2 => FixedProfile (0 ),
53- NG => FixedProfile (0 ),
52+ co2 => FixedProfile (0 ),
53+ ng => FixedProfile (0 ),
5454 ),
55- CO2 , # CO2 instance
55+ co2 , # CO₂ instance
5656 )
5757
5858 # Creation of the emission data for the individual nodes.
59- capture_data = CaptureEnergyEmissions (0.9 )
6059 emission_data = EmissionsEnergy ()
60+ # Line above: `EmissionsEnergy` implies that the emissions data corresponds to
61+ # emissions through fuel usage as calculated by the CO₂ intensity and efficiency.
62+ capture_data = CaptureEnergyEmissions (0.9 )
63+ # Line above: `CaptureEnergyEmissions` implies that the emissions data corresponds
64+ # to emissions through fuel usage as calculated by the CO₂ intensity and efficiency.
65+ # 90 % of the CO₂ emissions are captured as given by the value 0.9.
6166
6267 # Create the individual test nodes, corresponding to a system with an electricity demand/sink,
6368 # coal and nautral gas sources, coal and natural gas (with CCS) power plants and CO₂ storage.
@@ -68,59 +73,64 @@ function generate_example_network()
6873 FixedProfile (100 ), # Capacity in MW
6974 FixedProfile (30 ), # Variable OPEX in EUR/MW
7075 FixedProfile (0 ), # Fixed OPEX in EUR/MW/8h
71- Dict (NG => 1 ), # Output from the Node, in this case, NG
76+ Dict (ng => 1 ), # Output from the Node, in this case, ng
7277 ),
7378 RefSource (
7479 " coal source" , # Node id
7580 FixedProfile (100 ), # Capacity in MW
7681 FixedProfile (9 ), # Variable OPEX in EUR/MWh
7782 FixedProfile (0 ), # Fixed OPEX in EUR/MW/8h
78- Dict (Coal => 1 ), # Output from the Node, in this case, coal
83+ Dict (coal => 1 ), # Output from the Node, in this case, coal
7984 ),
8085 RefNetworkNode (
8186 " NG+CCS power plant" , # Node id
8287 FixedProfile (25 ), # Capacity in MW
8388 FixedProfile (5.5 ), # Variable OPEX in EUR/MWh
8489 FixedProfile (0 ), # Fixed OPEX in EUR/MW/8h
85- Dict (NG => 2 ), # Input to the node with input ratio
86- Dict (Power => 1 , CO2 => 1 ), # Output from the node with output ratio
87- # Line above: CO2 is required as output for variable definition, but the
88- # value does not matter
90+ Dict (ng => 2 ), # Input to the node with input ratio
91+ Dict (power => 1 , co2 => 1 ), # Output from the node with output ratio
92+ # Line above: `co2` is required as output for variable definition, but the
93+ # value does not matter as it is not utilized in the model.
8994 [capture_data], # Additional data for emissions and CO₂ capture
9095 ),
9196 RefNetworkNode (
9297 " coal power plant" , # Node id
9398 FixedProfile (25 ), # Capacity in MW
9499 FixedProfile (6 ), # Variable OPEX in EUR/MWh
95100 FixedProfile (0 ), # Fixed OPEX in EUR/MW/8h
96- Dict (Coal => 2.5 ), # Input to the node with input ratio
97- Dict (Power => 1 ), # Output from the node with output ratio
101+ Dict (coal => 2.5 ), # Input to the node with input ratio
102+ Dict (power => 1 ), # Output from the node with output ratio
98103 [emission_data], # Additional data for emissions
99104 ),
100105 RefStorage {AccumulatingEmissions} (
101- " CO2 storage" , # Node id
106+ " CO₂ storage" , # Node id
102107 StorCapOpex (
103108 FixedProfile (60 ), # Charge capacity in t/h
104109 FixedProfile (9.1 ), # Storage variable OPEX for the charging in EUR/t
105110 FixedProfile (0 ) # Storage fixed OPEX for the charging in EUR/(t/h 8h)
106111 ),
107112 StorCap (FixedProfile (600 )), # Storage capacity in t
108- CO2, # Stored resource
109- Dict (CO2 => 1 , Power => 0.02 ), # Input resource with input ratio
110- # Line above: This implies that storing CO₂ requires Power
111- Dict (CO2 => 1 ), # Output from the node with output ratio
112- # In practice, for CO₂ storage, this is never used.
113+ co2, # Stored resource
114+ Dict (co2 => 1 , power => 0.02 ), # Input resource with input ratio
115+ # Line above: This implies that storing CO₂ requires power
116+ Dict (co2 => 1 ), # Output from the node with output ratio
117+ # Line above: In the case of `AccumulatingEmissions`, you must provide the
118+ # stored resource as one of the keys. Its value does however not matter as the
119+ # outlet flow value is fixed to 0.
113120 ),
114121 RefSink (
115122 " electricity demand" , # Node id
116123 OperationalProfile ([20 , 30 , 40 , 30 ]), # Demand in MW
117124 Dict (:surplus => FixedProfile (0 ), :deficit => FixedProfile (1e6 )),
118125 # Line above: Surplus and deficit penalty for the node in EUR/MWh
119- Dict (Power => 1 ), # Energy demand and corresponding ratio
126+ Dict (power => 1 ), # Energy demand and corresponding ratio
120127 ),
121128 ]
122129
123130 # Connect all nodes with the availability node for the overall energy/mass balance
131+ # NOTE: This hard coding based on indexing is error prone. It is in general advised to
132+ # use a mapping dictionary to avoid any problems when introducing new technology
133+ # nodes.
124134 links = [
125135 Direct (" Av-NG_pp" , nodes[1 ], nodes[4 ], Linear ())
126136 Direct (" Av-coal_pp" , nodes[1 ], nodes[5 ], Linear ())
@@ -134,6 +144,8 @@ function generate_example_network()
134144 ]
135145
136146 # Input data structure
147+ # It is also explained on
148+ # https://energymodelsx.github.io/EnergyModelsBase.jl/stable/library/public/case_element/
137149 case = Case (T, products, [nodes, links], [[get_nodes, get_links]])
138150 return case, model
139151end
@@ -143,21 +155,63 @@ case, model = generate_example_network()
143155optimizer = optimizer_with_attributes (HiGHS. Optimizer, MOI. Silent () => true )
144156m = run_model (case, model, optimizer)
145157
158+ """
159+ process_network_results(m, case)
160+
161+ Function for processing the results to be represented in the a table afterwards.
162+ """
163+ function process_network_results (m, case)
164+ # Extract the nodes and resources from the case data
165+ ng_ccs_pp, coal_pp, = get_nodes (case)[[4 , 5 ]]
166+ co2 = get_products (case)[4 ]
167+ 𝒯ⁱⁿᵛ = strategic_periods (get_time_struct (case))
168+
169+ # Node variables
170+ coal_pp_use = sort ( # Capacity usage of the coal pp
171+ [(
172+ t_inv= t_inv,
173+ val= sum (value .(m[:cap_use ][coal_pp, t])* scale_op_sp (t_inv, t) for t ∈ t_inv)
174+ ) for t_inv ∈ 𝒯ⁱⁿᵛ],
175+ by = x -> x. t_inv,
176+ )
177+ ng_ccs_pp_use = sort ( # Capacity usage of the ng pp
178+ [(
179+ t_inv= t_inv,
180+ val= sum (value .(m[:cap_use ][ng_ccs_pp, t])* scale_op_sp (t_inv, t) for t ∈ t_inv)
181+ ) for t_inv ∈ 𝒯ⁱⁿᵛ],
182+ by = x -> x. t_inv,
183+ )
184+
185+ # Emission variables
186+ strat_emit = sort ( # Strategic emissions
187+ JuMP. Containers. rowtable (
188+ value,
189+ m[:emissions_strategic ][:, co2];
190+ header = [:t_inv , :val ],
191+ ),
192+ by = x -> x. t_inv,
193+ )
194+
195+ # Set up the individual named tuples as a single named tuple
196+ table = [(
197+ t_inv = repr (con_1. t_inv),
198+ coal_pp_use = round (con_1. val; digits= 1 ),
199+ ng_ccs_pp_use = round (con_2. val; digits= 1 ),
200+ CO2_emissions = round (con_3. val; digits= 1 ),
201+ ) for (con_1, con_2, con_3) ∈
202+ zip (coal_pp_use, ng_ccs_pp_use, strat_emit)
203+ ]
204+ return table
205+ end
206+
146207# Display some results
147- ng_ccs_pp, coal_pp, = get_nodes (case)[[4 , 5 ]]
148- @info " Capacity usage of the coal power plant"
149- pretty_table (
150- JuMP. Containers. rowtable (
151- value,
152- m[:cap_use ][coal_pp, :];
153- header = [:t , :Value ],
154- ),
155- )
156- @info " Capacity usage of the natural gas + CCS power plant"
157- pretty_table (
158- JuMP. Containers. rowtable (
159- value,
160- m[:cap_use ][ng_ccs_pp, :];
161- header = [:t , :Value ],
162- ),
208+ table = process_network_results (m, case)
209+
210+ @info (
211+ " Individual strategic results from the simple network:\n " *
212+ " The coal power plant is the preferred power generation unit due to the generation costs.\n " *
213+ " Its usage declines however in subsequent strategic period due to the emission constraints.\n " *
214+ " It is replaced by the natural gas power plant with CO₂ capture as the total strategic\n " *
215+ " emissions follow the emission limits."
163216)
217+ pretty_table (table)
0 commit comments