Flexible demand
This example uses the following two nodes from EnergyModelsFlex
:
PeriodDemandSink
to set a demand per day instead of per operational period andMinUpDownTimeNode
to force the production to run for a minimum number of hours if it has first started, and be shut off for a minimum number of hours if it has first stopped.
using EnergyModelsBase
using EnergyModelsFlex
using TimeStruct
using HiGHS
using JuMP
using PrettyTables
Declare the required resources.
Power = ResourceCarrier("Power", 0)
Product = ResourceCarrier("Product", 0)
CO2 = ResourceEmit("CO2", 0)
CO2
Define a timestructure for a single week.
T = 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])], 1.0)
Some arbitrary electricity prices. Note we let the energy be free in the weekend. This would be a huge incentive to produce during the weekend, if we allowed the PeriodDemandSink
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 = [repeat(day, 5)..., fill(0, 2 * 24)...]
grid = RefSource(
"grid",
FixedProfile(1e12), # kW - virtually infinite
OperationalProfile(el_cost),
FixedProfile(0),
Dict(Power => 1),
)
n_grid
The production can only run between 6-20 on weekdays, with a capacity of 300 kW. First, define the maximum capacity for a regular weekday (24 hours). The capacity is 0 between 0 am and 6 am, 300 kW between 6 am and 8 pm, and 0 again between 8 pm and midnight.
weekday_prod = [fill(0, 6)..., fill(300, 14)..., fill(0, 4)...]
@assert length(weekday_prod) == 24
Repeat a weekday 5 times, for a workweek, then no production on the weekends.
week_prod = [repeat(weekday_prod, 5)..., fill(0, 2 * 24)...]
demand = PeriodDemandSink(
"demand_product",
24, # 24 hours per day.
[fill(1500, 5)..., 0, 0], # Demand of 1500 units per day, and nothing (0) in the weekend.
OperationalProfile(week_prod), # kW - installed capacity
Dict(:surplus => FixedProfile(0), :deficit => FixedProfile(1e8)), # € / Demand - Price for not delivering products
Dict(Product => 1),
)
n_demand_product
Define the production line using MinUpDownTimeNode
min_up_time = 8
min_down_time = 5
line = MinUpDownTimeNode(
"line",
FixedProfile(300), # kW - installed capacity for both lines
FixedProfile(0),
FixedProfile(0),
Dict(Power => 1),
Dict(Product => 1),
min_up_time, # minUpTime
min_down_time, # minDownTime
50, # minCapacity
300, # maxCapacity
[],
)
n_line
Define the simple energy system
nodes = [grid, line, demand]
links = [Direct("grid-line", grid, line), Direct("line-demand", line, demand)]
case = Dict(:T => T, :nodes => nodes, :products => [Power, Product], :links => links)
Dict{Symbol, Any} with 4 entries:
:T => TwoLevel{Int64, Int64, SimpleTimes{Int64}}(1, [1], SimpleTimes{I…
:links => Direct[l_n_grid-n_line, l_n_line-n_demand_product]
:products => ResourceCarrier{Int64}[Power, Product]
:nodes => Node[n_grid, n_line, n_demand_product]
Define as operational energy model
modeltype = OperationalModel(
Dict(CO2 => FixedProfile(1e6)),
Dict(CO2 => FixedProfile(100)),
CO2,
)
OperationalModel(Dict{ResourceEmit{Int64}, FixedProfile{Float64}}(CO2 => FixedProfile{Float64}(1.0e6)), Dict{ResourceEmit{Int64}, FixedProfile{Int64}}(CO2 => FixedProfile{Int64}(100)), CO2)
Optimize the model
m = run_model(case, modeltype, HiGHS.Optimizer)
A JuMP Model
├ solver: HiGHS
├ objective_sense: MAX_SENSE
│ └ objective_function_type: JuMP.AffExpr
├ num_variables: 3212
├ num_constraints: 6917
│ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 2028
│ ├ JuMP.AffExpr in MOI.GreaterThan{Float64}: 168
│ ├ JuMP.AffExpr in MOI.LessThan{Float64}: 1512
│ ├ JuMP.VariableRef in MOI.EqualTo{Float64}: 505
│ ├ JuMP.VariableRef in MOI.GreaterThan{Float64}: 2200
│ └ 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_Δ_sp
Show status, should be optimal
@show termination_status(m)
OPTIMAL::TerminationStatusCode = 1
Get the full row table
table = JuMP.Containers.rowtable(value, m[:cap_use]; header = [:Node, :TimePeriod, :CapUse])
504-element Vector{NamedTuple{(:Node, :TimePeriod, :CapUse)}}:
(Node = n_grid, TimePeriod = sp1-t1, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t1, CapUse = 0.0)
(Node = n_demand_product, TimePeriod = sp1-t1, CapUse = 0.0)
(Node = n_grid, TimePeriod = sp1-t2, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t2, CapUse = 0.0)
(Node = n_demand_product, TimePeriod = sp1-t2, CapUse = 0.0)
(Node = n_grid, TimePeriod = sp1-t3, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t3, CapUse = 0.0)
(Node = n_demand_product, TimePeriod = sp1-t3, CapUse = 0.0)
(Node = n_grid, TimePeriod = sp1-t4, CapUse = 0.0)
⋮
(Node = n_grid, TimePeriod = sp1-t166, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t166, CapUse = 0.0)
(Node = n_demand_product, TimePeriod = sp1-t166, CapUse = 0.0)
(Node = n_grid, TimePeriod = sp1-t167, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t167, CapUse = 0.0)
(Node = n_demand_product, TimePeriod = sp1-t167, CapUse = 0.0)
(Node = n_grid, TimePeriod = sp1-t168, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t168, CapUse = 0.0)
(Node = n_demand_product, TimePeriod = sp1-t168, CapUse = 0.0)
Filter only for Node == line
line = case[:nodes][2]
filtered = filter(row -> row.Node == line, table)
168-element Vector{NamedTuple{(:Node, :TimePeriod, :CapUse)}}:
(Node = n_line, TimePeriod = sp1-t1, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t2, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t3, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t4, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t5, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t6, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t7, CapUse = 300.0)
(Node = n_line, TimePeriod = sp1-t8, CapUse = 300.0)
(Node = n_line, TimePeriod = sp1-t9, CapUse = 300.0)
(Node = n_line, TimePeriod = sp1-t10, CapUse = 300.0)
⋮
(Node = n_line, TimePeriod = sp1-t160, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t161, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t162, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t163, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t164, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t165, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t166, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t167, CapUse = 0.0)
(Node = n_line, TimePeriod = sp1-t168, CapUse = 0.0)
Display the filtered table with the resulting optimal production.
- Note that the demand is only satisfied during the set workhours (6-20) on weekdays. This is cause by the restrictions put on
PeriodDemandSink
with 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 is always run 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 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; crop = :none)
┌────────┬────────────┬────────┐
│ 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.