Flexible demand
This example uses the following two nodes from EnergyModelsFlex:
PeriodDemandSinkto set a demand per day instead of per operational period andMinUpDownTimeNodeto force the production to run for a minimum number of hours once it has started, and to be shut off for a minimum number of hours once it has stopped.
Prerequisites
The example requires EnergyModelsBase, EnergyModelsFlex, and TimeStruct for building the model, JuMP and the solver HiGHS for solving it, and PrettyTables for displaying the results. All packages are included in the environment of the examples folder.
using EnergyModelsBase
using EnergyModelsFlex
using TimeStruct
using HiGHS
using JuMP
using PrettyTablesConstruction of the model
Declare the required resources
power = ResourceCarrier("Power", 0)
product = ResourceCarrier("Product", 0)
co2 = ResourceEmit("CO₂", 0)
𝒫 = [power, product, co2]3-element Vector{Resource}:
Power
Product
CO₂Define a time structure for a single week, modelled with hourly resolution
𝒯 = TwoLevel(1, 1, SimpleTimes(7 * 24, 1))TwoLevel{Int64, Int64, SimpleTimes{Int64}}(1, [1], SimpleTimes{Int64}[SimpleTimes{Int64}(168, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1 … 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 168)], 1.0)Some arbitrary electricity prices. Note that we let the electricity be free during the weekend. This would be a huge incentive to produce during the weekend, if we allowed the PeriodDemandSink to have capacity during the weekend.
day = [1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 9, 8, 7, 6, 5, 4, 3, 2]
el_cost = vcat(repeat(day, 5), zeros(48))
grid = RefSource(
"grid", # Node id
FixedProfile(1e12), # Capacity in kW, virtually infinite
OperationalProfile(el_cost), # Variable OPEX in EUR/kWh
FixedProfile(0), # Fixed OPEX in EUR/kW/week
Dict(power => 1), # Output from the node with output ratio
)n_gridThe production can only run between 6-20 on weekdays, with a capacity of 300 unit/h. First, define the maximum capacity for a regular weekday (24 hours). The capacity is 0 between midnight and 6 am, 300 unit/h between 6 am and 8 pm, and 0 again between 8 pm and midnight.
weekday_prod = vcat(zeros(6), fill(300, 14), zeros(4))24-element Vector{Float64}:
0.0
0.0
0.0
0.0
0.0
0.0
300.0
300.0
300.0
300.0
⋮
300.0
300.0
300.0
300.0
300.0
0.0
0.0
0.0
0.0Repeat the weekday 5 times for a workweek, followed by no production during the weekend
week_prod = vcat(repeat(weekday_prod, 5), fill(0, 2 * 24))
demand = PeriodDemandSink(
"demand_product", # Node id
OperationalProfile(week_prod), # Capacity in unit/h
24, # Duration of a demand period in hours (one day)
PartitionProfile(vcat(ones(5)*1500, [0, 0])), # Demand in each demand period in unit
# Line above: Demand of 1500 unit per weekday and no demand (0) during the weekend
Dict(:surplus => FixedProfile(0), :deficit => FixedProfile(1e8)),
# Line above: Surplus and deficit penalty for each demand period in EUR/unit
Dict(product => 1), # Energy demand and corresponding ratio
)n_demand_productDefine the production line using MinUpDownTimeNode
min_up_time = 8
min_down_time = 5
line = MinUpDownTimeNode(
"line", # Node id
FixedProfile(300), # Capacity in unit/h
FixedProfile(0), # Variable OPEX in EUR/unit
FixedProfile(0), # Fixed OPEX in EUR/(unit/h)/week
Dict(power => 1), # Input to the node with input ratio
Dict(product => 1), # Output from the node with output ratio
min_up_time, # Minimum up time in hours
min_down_time, # Minimum down time in hours
50, # Minimum load when the line is running in unit/h
300, # Maximum load when the line is running in unit/h
)n_lineDefine the simple energy system
𝒩 = [grid, line, demand]
ℒ = [Direct("grid-line", grid, line), Direct("line-demand", line, demand)]
case = Case(𝒯, 𝒫, [𝒩, ℒ])Case(TwoLevel{Int64, Int64, SimpleTimes{Int64}}(1, [1], SimpleTimes{Int64}[SimpleTimes{Int64}(168, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1 … 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 168)], 1.0), Resource[Power, Product, CO₂], Vector[EnergyModelsBase.Node[n_grid, n_line, n_demand_product], Direct[l_n_grid-n_line, l_n_line-n_demand_product]], Vector{Function}[[EnergyModelsBase.get_nodes, EnergyModelsBase.get_links]], Dict{Any, Any}())Define an operational energy model
modeltype = OperationalModel(
Dict(co2 => FixedProfile(1e6)), # Emission cap for CO₂ in t/week
Dict(co2 => FixedProfile(100)), # Emission price for CO₂ in EUR/t
co2, # CO₂ instance
)OperationalModel(Dict{ResourceEmit{Int64}, FixedProfile{Float64}}(CO₂ => FixedProfile{Float64}(1.0e6)), Dict{ResourceEmit{Int64}, FixedProfile{Int64}}(CO₂ => FixedProfile{Int64}(100)), CO₂)Solving of the model
Create and optimize the model using the function run_model with HiGHS as solver
m = run_model(case, modeltype, HiGHS.Optimizer)A JuMP Model
├ solver: HiGHS
├ objective_sense: MAX_SENSE
│ └ objective_function_type: JuMP.AffExpr
├ num_variables: 3381
├ num_constraints: 6919
│ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 2197
│ ├ JuMP.AffExpr in MOI.GreaterThan{Float64}: 168
│ ├ JuMP.AffExpr in MOI.LessThan{Float64}: 1344
│ ├ JuMP.VariableRef in MOI.EqualTo{Float64}: 673
│ ├ JuMP.VariableRef in MOI.GreaterThan{Float64}: 2032
│ ├ JuMP.VariableRef in MOI.LessThan{Float64}: 1
│ └ JuMP.VariableRef in MOI.ZeroOne: 504
└ Names registered in the model
└ :cap_inst, :cap_use, :con_em_tot, :demand_sink_deficit, :demand_sink_surplus, :emissions_link, :emissions_node, :emissions_strategic, :emissions_total, :flow_in, :flow_out, :link_cap_inst, :link_in, :link_opex_fixed, :link_opex_var, :link_out, :offswitch, :on_off, :onswitch, :opex_fixed, :opex_var, :sink_deficit, :sink_surplus, :stor_charge_inst, :stor_charge_use, :stor_discharge_inst, :stor_discharge_use, :stor_level, :stor_level_inst, :stor_level_Δ_op, :stor_level_Δ_spShow the status, which should be optimal
@show termination_status(m)OPTIMAL::TerminationStatusCode = 1Result presentation
Get the full row table of the capacity usage of all nodes
table = JuMP.Containers.rowtable(value, m[:cap_use]; header = [:Node, :TimePeriod, :CapUse]);Filter only for Node == line
line = get_nodes(case)[2]
filtered = filter(row -> row.Node == line, table);Display the filtered table with the resulting optimal production.
- Note that the demand is only satisfied during the set work hours (6-20) on weekdays. This is caused by the restrictions put on
PeriodDemandSinkwith the capacity limited to these time periods. This is also the reason that there is no production during the weekend, even though the electricity is free. - Also note that the production always runs for at least 8 hours, even though the daily demand of 1500 units could be reached in 5 hours running at full capacity. This can be explained by the constraint for a minimum run time of 8 hours on the
MinUpDownTimeNode. To maximize production at low prices, it runs at the minimum capacity of 50 when the electricity is more expensive.
pretty_table(
filtered;
fit_table_in_display_horizontally = false,
fit_table_in_display_vertically = false,
maximum_number_of_rows = -1,
maximum_number_of_columns = -1,
)┌────────┬────────────┬────────┐
│ Node │ TimePeriod │ CapUse │
├────────┼────────────┼────────┤
│ n_line │ sp1-t1 │ 0.0 │
│ n_line │ sp1-t2 │ 0.0 │
│ n_line │ sp1-t3 │ 0.0 │
│ n_line │ sp1-t4 │ 0.0 │
│ n_line │ sp1-t5 │ 0.0 │
│ n_line │ sp1-t6 │ 0.0 │
│ n_line │ sp1-t7 │ 300.0 │
│ n_line │ sp1-t8 │ 300.0 │
│ n_line │ sp1-t9 │ 300.0 │
│ n_line │ sp1-t10 │ 300.0 │
│ n_line │ sp1-t11 │ 150.0 │
│ n_line │ sp1-t12 │ 50.0 │
│ n_line │ sp1-t13 │ 50.0 │
│ n_line │ sp1-t14 │ 50.0 │
│ n_line │ sp1-t15 │ 0.0 │
│ n_line │ sp1-t16 │ 0.0 │
│ n_line │ sp1-t17 │ 0.0 │
│ n_line │ sp1-t18 │ 0.0 │
│ n_line │ sp1-t19 │ 0.0 │
│ n_line │ sp1-t20 │ 0.0 │
│ n_line │ sp1-t21 │ 0.0 │
│ n_line │ sp1-t22 │ 0.0 │
│ n_line │ sp1-t23 │ 0.0 │
│ n_line │ sp1-t24 │ 0.0 │
│ n_line │ sp1-t25 │ 0.0 │
│ n_line │ sp1-t26 │ 0.0 │
│ n_line │ sp1-t27 │ 0.0 │
│ n_line │ sp1-t28 │ 0.0 │
│ n_line │ sp1-t29 │ 0.0 │
│ n_line │ sp1-t30 │ 0.0 │
│ n_line │ sp1-t31 │ 300.0 │
│ n_line │ sp1-t32 │ 300.0 │
│ n_line │ sp1-t33 │ 300.0 │
│ n_line │ sp1-t34 │ 300.0 │
│ n_line │ sp1-t35 │ 150.0 │
│ n_line │ sp1-t36 │ 50.0 │
│ n_line │ sp1-t37 │ 50.0 │
│ n_line │ sp1-t38 │ 50.0 │
│ n_line │ sp1-t39 │ 0.0 │
│ n_line │ sp1-t40 │ 0.0 │
│ n_line │ sp1-t41 │ 0.0 │
│ n_line │ sp1-t42 │ 0.0 │
│ n_line │ sp1-t43 │ 0.0 │
│ n_line │ sp1-t44 │ 0.0 │
│ n_line │ sp1-t45 │ 0.0 │
│ n_line │ sp1-t46 │ 0.0 │
│ n_line │ sp1-t47 │ 0.0 │
│ n_line │ sp1-t48 │ 0.0 │
│ n_line │ sp1-t49 │ 0.0 │
│ n_line │ sp1-t50 │ 0.0 │
│ n_line │ sp1-t51 │ 0.0 │
│ n_line │ sp1-t52 │ 0.0 │
│ n_line │ sp1-t53 │ 0.0 │
│ n_line │ sp1-t54 │ 0.0 │
│ n_line │ sp1-t55 │ 300.0 │
│ n_line │ sp1-t56 │ 300.0 │
│ n_line │ sp1-t57 │ 300.0 │
│ n_line │ sp1-t58 │ 300.0 │
│ n_line │ sp1-t59 │ 150.0 │
│ n_line │ sp1-t60 │ 50.0 │
│ n_line │ sp1-t61 │ 50.0 │
│ n_line │ sp1-t62 │ 50.0 │
│ n_line │ sp1-t63 │ 0.0 │
│ n_line │ sp1-t64 │ 0.0 │
│ n_line │ sp1-t65 │ 0.0 │
│ n_line │ sp1-t66 │ 0.0 │
│ n_line │ sp1-t67 │ 0.0 │
│ n_line │ sp1-t68 │ 0.0 │
│ n_line │ sp1-t69 │ 0.0 │
│ n_line │ sp1-t70 │ 0.0 │
│ n_line │ sp1-t71 │ 0.0 │
│ n_line │ sp1-t72 │ 0.0 │
│ n_line │ sp1-t73 │ 0.0 │
│ n_line │ sp1-t74 │ 0.0 │
│ n_line │ sp1-t75 │ 0.0 │
│ n_line │ sp1-t76 │ 0.0 │
│ n_line │ sp1-t77 │ 0.0 │
│ n_line │ sp1-t78 │ 0.0 │
│ n_line │ sp1-t79 │ 300.0 │
│ n_line │ sp1-t80 │ 300.0 │
│ n_line │ sp1-t81 │ 300.0 │
│ n_line │ sp1-t82 │ 300.0 │
│ n_line │ sp1-t83 │ 150.0 │
│ n_line │ sp1-t84 │ 50.0 │
│ n_line │ sp1-t85 │ 50.0 │
│ n_line │ sp1-t86 │ 50.0 │
│ n_line │ sp1-t87 │ 0.0 │
│ n_line │ sp1-t88 │ 0.0 │
│ n_line │ sp1-t89 │ 0.0 │
│ n_line │ sp1-t90 │ 0.0 │
│ n_line │ sp1-t91 │ 0.0 │
│ n_line │ sp1-t92 │ 0.0 │
│ n_line │ sp1-t93 │ 0.0 │
│ n_line │ sp1-t94 │ 0.0 │
│ n_line │ sp1-t95 │ 0.0 │
│ n_line │ sp1-t96 │ 0.0 │
│ n_line │ sp1-t97 │ 0.0 │
│ n_line │ sp1-t98 │ 0.0 │
│ n_line │ sp1-t99 │ 0.0 │
│ n_line │ sp1-t100 │ 0.0 │
│ n_line │ sp1-t101 │ 0.0 │
│ n_line │ sp1-t102 │ 0.0 │
│ n_line │ sp1-t103 │ 300.0 │
│ n_line │ sp1-t104 │ 300.0 │
│ n_line │ sp1-t105 │ 300.0 │
│ n_line │ sp1-t106 │ 300.0 │
│ n_line │ sp1-t107 │ 150.0 │
│ n_line │ sp1-t108 │ 50.0 │
│ n_line │ sp1-t109 │ 50.0 │
│ n_line │ sp1-t110 │ 50.0 │
│ n_line │ sp1-t111 │ 0.0 │
│ n_line │ sp1-t112 │ 0.0 │
│ n_line │ sp1-t113 │ 0.0 │
│ n_line │ sp1-t114 │ 0.0 │
│ n_line │ sp1-t115 │ 0.0 │
│ n_line │ sp1-t116 │ 0.0 │
│ n_line │ sp1-t117 │ 0.0 │
│ n_line │ sp1-t118 │ 0.0 │
│ n_line │ sp1-t119 │ 0.0 │
│ n_line │ sp1-t120 │ 0.0 │
│ n_line │ sp1-t121 │ 0.0 │
│ n_line │ sp1-t122 │ 0.0 │
│ n_line │ sp1-t123 │ 0.0 │
│ n_line │ sp1-t124 │ 0.0 │
│ n_line │ sp1-t125 │ 0.0 │
│ n_line │ sp1-t126 │ 0.0 │
│ n_line │ sp1-t127 │ 0.0 │
│ n_line │ sp1-t128 │ 0.0 │
│ n_line │ sp1-t129 │ 0.0 │
│ n_line │ sp1-t130 │ 0.0 │
│ n_line │ sp1-t131 │ 0.0 │
│ n_line │ sp1-t132 │ 0.0 │
│ n_line │ sp1-t133 │ 0.0 │
│ n_line │ sp1-t134 │ 0.0 │
│ n_line │ sp1-t135 │ 0.0 │
│ n_line │ sp1-t136 │ 0.0 │
│ n_line │ sp1-t137 │ 0.0 │
│ n_line │ sp1-t138 │ 0.0 │
│ n_line │ sp1-t139 │ 0.0 │
│ n_line │ sp1-t140 │ 0.0 │
│ n_line │ sp1-t141 │ 0.0 │
│ n_line │ sp1-t142 │ 0.0 │
│ n_line │ sp1-t143 │ 0.0 │
│ n_line │ sp1-t144 │ 0.0 │
│ n_line │ sp1-t145 │ 0.0 │
│ n_line │ sp1-t146 │ 0.0 │
│ n_line │ sp1-t147 │ 0.0 │
│ n_line │ sp1-t148 │ 0.0 │
│ n_line │ sp1-t149 │ 0.0 │
│ n_line │ sp1-t150 │ 0.0 │
│ n_line │ sp1-t151 │ 0.0 │
│ n_line │ sp1-t152 │ 0.0 │
│ n_line │ sp1-t153 │ 0.0 │
│ n_line │ sp1-t154 │ 0.0 │
│ n_line │ sp1-t155 │ 0.0 │
│ n_line │ sp1-t156 │ 0.0 │
│ n_line │ sp1-t157 │ 0.0 │
│ n_line │ sp1-t158 │ 0.0 │
│ n_line │ sp1-t159 │ 0.0 │
│ n_line │ sp1-t160 │ 0.0 │
│ n_line │ sp1-t161 │ 0.0 │
│ n_line │ sp1-t162 │ 0.0 │
│ n_line │ sp1-t163 │ 0.0 │
│ n_line │ sp1-t164 │ 0.0 │
│ n_line │ sp1-t165 │ 0.0 │
│ n_line │ sp1-t166 │ 0.0 │
│ n_line │ sp1-t167 │ 0.0 │
│ n_line │ sp1-t168 │ 0.0 │
└────────┴────────────┴────────┘This page was generated using Literate.jl.