Skip to content

Commit 54145a7

Browse files
JulStrausZetison
andauthored
Improvement of PeriodDemandSink (#12)
* Updated the PeriodDemandSink test set * Reworked PeriodDemandSink based on PartitionDuration * Allow for time profiles for period_duration * Included legacy constructor for `PeriodDemandSink` --------- Co-authored-by: Jon Vegard Venås <jon.venas@ife.no>
1 parent 9c5b52e commit 54145a7

19 files changed

Lines changed: 639 additions & 284 deletions

NEWS.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Release notes
22

3+
## Unversioned
4+
5+
### Breaking changes
6+
7+
#### Rework of `PeriodDemandSink`
8+
9+
* Rewrote `PeriodDemandSink` with `PeriodPartition` (introduced in `TimeStruct` 0.9.12) to increase flexibility of node with respect to the time structure.
10+
* Rewriting changed input arguments as well as behavior of the node.
11+
312
## Version 0.3.0 (2026-04-16)
413

514
### Breaking changes
@@ -9,7 +18,7 @@
918
* Early retirement changes the model behavior.
1019
* Model worked without adjustments except for compatibility updates.
1120
* Breaking change still included to maintain the possibility to do bug fixes in version 0.1.x for existing models with `EnergyModelsBase` v0.9.x.
12-
* UpdMoved from `Data` to `ExtensionData` in fields and tests.
21+
* Moved from `Data` to `ExtensionData` in fields and tests.
1322
* Improved `MinUpDownTimeNode`:
1423
* Rewrote the function `constraints_capacity` based on core functionality.
1524
* Updated the test set to identify all potential problems with the node.

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
1010
TimeStruct = "f9ed5ce0-9f41-4eaa-96da-f38ab8df101c"
1111

1212
[compat]
13-
EnergyModelsBase = "0.10"
13+
EnergyModelsBase = "0.10.5"
1414
EnergyModelsRenewableProducers = "0.7"
1515
JuMP = "1.23"
1616
TimeStruct = "0.9"

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
> The different node types are partly experimental.
1414
> They have furthermore some limitations with respect to the chosen `TimeStructure` or whether they are able to handle investments.
1515
> As a consequence, it is advised to read the documentation for each node to identify their usefulness.
16-
> Is is planned to removesome nodes and rewrite the behaviour of other nodes to improve their flexibility.
16+
> Is is planned to remove some nodes and rewrite the behaviour of other nodes to improve their flexibility.
1717
>
18-
> Version 0.2.8 is based on [`EnergyModelsBase` v0.8](https://energymodelsx.github.io/EnergyModelsBase.jl/v0.8/) while version 0.2.9 is adding support for [`EnergyModelsBase` v0.9](https://energymodelsx.github.io/EnergyModelsBase.jl/v0.9/).
18+
> Among others, using `PeriodDemandSink` in combination with `EnergyModelsGUI` results in errors when trying to access field values with `PartitionProfile`.
1919
2020
## Usage
2121

@@ -26,7 +26,7 @@ Please refer to the *[documentation](https://energymodelsx.github.io/EnergyModel
2626

2727
## Cite
2828

29-
If you find `EnergyModelsFlex` useful in your work, we kindly request that you cite the following [publication](https://doi.org/10.69997/sct.119164):
29+
If you find `EnergyModelsFlex` useful in your work, we kindly request that you cite the following *[publication](https://doi.org/10.69997/sct.119164)*:
3030

3131
```bibtex
3232
@article{venas2025energymodelsflex,

docs/make.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ makedocs(
7474
"Links" => Any[
7575
"CapacityCostLink"=>"links/capacitycostlink.md",
7676
],
77-
"How-to" =>
78-
Any["Contribute"=>"how-to/contribute.md"],
77+
"How-to" => Any[
78+
"Contribute"=>"how-to/contribute.md",
79+
"Update models"=>"how-to/update-models.md",
80+
],
7981
"Library" => Any[
8082
"Public"=>"library/public.md",
8183
"Internals"=>String[

docs/src/how-to/update-models.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# [Update your model to the latest versions](@id how_to-update)
2+
3+
`EnergyModelsFlex` is still in a pre-release version.
4+
Hence, there are frequently breaking changes occuring, although we plan to keep backwards compatibility.
5+
This document is designed to provide users with information regarding how they have to adjust their nodes to keep compatibility to the latest changes.
6+
7+
## [Adjustments from 0.3.0](@id how_to-update-03)
8+
9+
The introduction of `PartitionProfile` in *[`TimeStruct` v0.9.12](https://github.com/sintefore/TimeStruct.jl/releases/tag/v0.9.12)* allowed a rewrite of `PeriodDemandSink`:
10+
11+
```julia
12+
# The previous nodal description for a `PeriodDemandSink` was given by:
13+
PeriodDemandSink(
14+
id::Any,
15+
period_length::Int,
16+
period_demand::Array{<:Real},
17+
cap::TimeProfile,
18+
penalty::Dict{Symbol,<:TimeProfile},
19+
input::Dict{<:Resource,<:Real},
20+
data::Vector{<:ExtensionData} = ExtensionData[],
21+
)
22+
23+
# This translates to the following new version
24+
PeriodDemandSink(
25+
id,
26+
cap,
27+
FixedProfile(period_length),
28+
PartitionProfile(period_demand),
29+
penalty,
30+
input,
31+
data
32+
)
33+
```

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ Depth = 1
6464

6565
```@contents
6666
Pages = [
67+
"how-to/update-models.md",
6768
"how-to/contribute.md",
68-
"how-to/utilize.md",
6969
]
7070
Depth = 1
7171
```

docs/src/library/internals/methods-EMF.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ Pages = ["methods-EMF.md"]
99
## [Check methods](@id lib-int-met-check)
1010

1111
```@docs
12-
EMF.check_period_ts
1312
EMF.check_limits_default
1413
EMF.check_input
1514
EMF.check_cap_price_periods

docs/src/library/internals/methods-fields.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ Pages = ["methods-fields.md"]
1111

1212
```@docs
1313
EnergyModelsFlex.period_demand
14-
EnergyModelsFlex.period_length
15-
EnergyModelsFlex.period_index
14+
EnergyModelsFlex.period_duration
15+
EnergyModelsFlex.periods
1616
EnergyModelsFlex.number_of_periods
1717
```
1818

@@ -35,4 +35,4 @@ EnergyModelsFlex.cap_price
3535
```@docs
3636
EnergyModelsFlex.limits
3737
EnergyModelsFlex.heat_resource
38-
```
38+
```

docs/src/nodes/sink/perioddemand.md

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ This node can, *e.g.*, be combined with [`MinUpDownTimeNode`](@ref), to allow pr
1010

1111
!!! warning "TimeStructure for node"
1212
This node is designed for **uniform or repetitive duration of operational periods**.
13-
Irregular durations may cause misalignment of shifted loads, especially if the field `period_length` does not align with the chosen [`SimpleTimes`](@extref TimeStruct.SimpleTimes) structure representing the operational periods.
13+
Irregular durations may cause misalignment of shifted loads, especially if the field `period_duration` does not align with the chosen [`SimpleTimes`](@extref TimeStruct.SimpleTimes) structure representing the operational periods.
14+
15+
!!! warning "`PeriodDemandSink` and `EnergyModelsGUI`"
16+
Some of the fields of this node cannot be represented in `EnergyModelsGUI`.
17+
The reason for that limitation is that `EnergyModelsGUI` does not yet support partitions of `TimePeriod`s.
18+
`EnergyModelsGUI` can still be utilized for all other fields.
1419

1520
## [Introduced type and its fields](@id nodes-perioddemandsink-fields)
1621

@@ -33,12 +38,11 @@ The standard fields are given as:
3338
- **`penalty::Dict{Symbol,<:TimeProfile}`**:\
3439
The penalty dictionary is used for providing penalties for soft constraints to allow for both over and under delivering the demand.\
3540
It must include the fields `:surplus` and `:deficit`.
36-
In addition, it is crucial that the sum of both values is larger than 0 to avoid an unconstrained model.
41+
In addition, it is crucial that the sum of both values in each demand period is larger than 0 to avoid an unconstrained model.
3742

3843
!!! warning "Chosen values"
39-
The current implementation does not represent the proper cost due to the summation.
40-
Instead, you must consider the duration of an operational period and the field `period_length` when providing a value.
41-
In this case, the value should be multiplied by ``1/period\_length(n) \times duration(t)``.
44+
The implementation is relative to the chosen `period_duration` (see below).
45+
If the period duration is ``24``, then the cost is for the unsatisfied demand within the ``24`` demand period, multiplied with the probability and the repetitons within a strategic period.
4246

4347
- **`input::Dict{<:Resource,<:Real}`**:\
4448
The field `input` includes [`Resource`](@extref EnergyModelsBase.Resource)s with their corresponding conversion factors as dictionaries.\
@@ -60,18 +64,27 @@ The standard fields are given as:
6064

6165
[`AbstractPeriodDemandSink`](@ref EnergyModelsFlex.AbstractPeriodDemandSink)s require additional fields to specify both the periods and their respective demands:
6266

63-
- **`period_length::Int`**:\
64-
Defines how many operational periods are included in a single demand period.\
65-
For instance, if the duration of the operational periods is 1 hour and `period_length = 24`, then each demand period spans one day.
66-
The demand of this node (for a given day, see below) must then be filled on a daily basis, without any restrictions on *when* during the day the demand must be filled.
67+
- **`period_duration::TimeProfile`**:\
68+
Defines the total duration of a single demand period.\
69+
For instance, if the duration of 1 of the operational time structure is 1 hour and `period_duration = FixedProfile(24)`, then each demand period spans one day.
70+
The demand of this node (for a given day, see below) must then be filled on a daily basis, without any restrictions on *when* during the day the demand must be filled given the available capacity.\
71+
Due to a constructor, it can either be specified as number (the same duration in all demand periods), as a vector (varying duration of each demand period), or as a time profile (*e.g.*, varying period durations due to varying operational time structures).
72+
It cannot be specified as `OperationalProfile`.
73+
74+
- **`period_demand::TimeProfile`**:\
75+
The total demand to be met during each demand period.
76+
The length of this time profile should match the number of periods (*e.g.*, days) in the time structure.
77+
If the time structure represents one year with hourly resolution and the demand periods correspond to a day, this time profile must then have 365 elements.
6778

68-
- **`period_demand::Vector{<:Real}`**:\
69-
The total demand to be met during each demand period. The length of this vector should match the number of periods (*e.g.*, days) in the time structure. If the time structure represents on year with hourly resolution, this vector must then have 365 elements.
79+
It is best to utilize the [`PartitionProfile`](@extref TimeStruct.PartitionProfile) type if the demand is varying.
80+
If it is constant, you can also utilize [`StrategicProfile`][@extref TimeStruct.StrategicProfile], [`RepresentativeProfile`][@extref TimeStruct.RepresentativeProfile], or [`ScenarioProfile`][@extref TimeStruct.ScenarioProfile], depending on your chosen time structure.
81+
It cannot be specified as `OperationalProfile`.
7082

7183
!!! warning "Time consistency"
72-
Ensure that the `period_demand` vector length aligns with the total time horizon divided by `period_length`. Mismatches can lead to indexing errors or inconsistent demand enforcement.
84+
Ensure that the `period_demand` time profile length aligns with the operational time horizon duration divided by `period_duration`
85+
Mismatches can lead to indexing errors or inconsistent demand enforcement.
7386

74-
These fields are at the 2ⁿᵈ and 3ʳᵈ position below the field `id` as shown in [`PeriodDemandSink`](@ref).
87+
These fields are at the 3ʳᵈ and 4ᵗʰ position below the field `cap` as shown in [`PeriodDemandSink`](@ref).
7588

7689
## [Mathematical description](@id nodes-perioddemandsink-math)
7790

@@ -105,12 +118,12 @@ The variables include:
105118
#### [Additional variables](@id nodes-perioddemandsink-math-add)
106119

107120
[`AbstractPeriodDemandSink`](@ref EnergyModelsFlex.AbstractPeriodDemandSink) nodes declare in addition several variables through dispatching on the method [`EnergyModelsBase.variables_element()`](@ref) for including constraints for deficits and surplus for individual resources as well as what the fraction satisfied by each resource.
108-
These variables are for a [`AbstractPeriodDemandSink`](@ref EnergyModelsFlex.AbstractPeriodDemandSink) node ``n`` in demand periods ``i``:
121+
These variables are for a [`AbstractPeriodDemandSink`](@ref EnergyModelsFlex.AbstractPeriodDemandSink) node ``n`` in demand periods ``t_pd``:
109122

110-
- ``\texttt{demand\_sink\_surplus}[n, i]``:\
111-
Surplus of energy delivered beyond the required `period_demand` in demand period `i`.
112-
- ``\texttt{demand\_sink\_deficit}[n, i]``:\
113-
Deficit of energy delivered relative to the `period_demand` in period `i`.
123+
- ``\texttt{demand\_sink\_surplus}[n, t_pd]``:\
124+
Surplus of energy delivered beyond the required `period_demand` of demand period `t_pd` .
125+
- ``\texttt{demand\_sink\_deficit}[n, t_pd]``:\
126+
Deficit of energy delivered relative to the `period_demand` of demand period `t_pd` .
114127

115128
### [Constraints](@id nodes-perioddemandsink-math-con)
116129

@@ -155,33 +168,37 @@ These standard constraints are:
155168
- `constraints_data`:\
156169
This function is only called for specified additional data, see above.
157170

158-
The function `constraints_capacity` is extended with a new method to account for the calculation of the period demand deficit and surplus through:
171+
The function `constraints_capacity` is extended with a new method to account for the calculation of the period demand deficit and surplus.
172+
173+
The overall balance is modified as
159174

160175
```math
161-
\texttt{cap\_use}[n, t] + \texttt{sink\_deficit}[n, t] = \texttt{cap\_inst}[n, t] + \texttt{sink\_surplus}[n, t]
176+
\texttt{cap\_use}[n, t] + \texttt{sink\_deficit}[n, t] = \texttt{cap\_inst}[n, t]
162177
```
163178

179+
while operational period surplus ``\texttt{sink\_surplus}[n, t]`` is fixed to 0.
180+
181+
The surplus and deficit of the demand period can then be calculated as
182+
164183
```math
165184
\begin{aligned}
166-
\texttt{demand\_sink\_deficit}[n, i] + & \sum_{t \in P_i} \texttt{​cap\_use}[n,t] = \\
167-
& \texttt{demand\_sink\_surplus}[n, i] + period\_demand(n, i)
185+
\texttt{demand\_sink\_deficit}[n, t_{pd}] + & \sum_{t \in t_{pd}} \texttt{​cap\_use}[n, t] \times duration(t) = \\
186+
& \texttt{demand\_sink\_surplus}[n, t_{pd}] + period\_demand(n, t_{pd})
168187
\end{aligned}
169188
```
170189

171-
where ``P_i`` is the set of operational periods in demand period ``i``.
190+
where ``t_{pd}`` is the demand period consisting of a set of operational periods.
172191

173192
As a consequence, `constraints_opex_var` requires as well a new method as we only consider the deficit within a complete period:
174193

175194
```math
176195
\begin{aligned}
177-
\texttt{opex\_var}[n, t_{inv}] = \sum_{t ∈ t_{inv}}(& \texttt{demand\_sink\_surplus}[n, i_t] \times \texttt{surplus\_penalty}(n, t) + \\
178-
& \texttt{demand\_sink\_deficit}[n, i_t] \times \texttt{deficit\_penalty}(n, t)) \times \\
179-
& scale\_op\_sp(t_{inv}, t)
196+
\texttt{opex\_var}[n, t_{inv}] = \sum_{t_{pd}periods(t_{inv})}(& \texttt{demand\_sink\_surplus}[n, t_{pd}] \times \texttt{surplus\_penalty}(n, t_{pd}) + \\
197+
& \texttt{demand\_sink\_deficit}[n, t_{pd}] \times \texttt{deficit\_penalty}(n, t_{pd})) \times \\
198+
& scale\_op\_sp(t_{inv}, first(t_{pd})) / duration(first(t_{pd}))
180199
\end{aligned}
181200
```
182201

183-
where ``i_t`` is the period index such that ``t \in P_{i_t}``.
184-
185-
!!! tip "The function `scale_op_sp`"
186-
The function [``scale\_op\_sp(t_{inv}, t)``](@extref EnergyModelsBase.scale_op_sp) calculates the scaling factor between operational and investment periods.
187-
It also takes into account potential operational scenarios and their probability as well as representative periods.
202+
!!! note "`scale_op_sp` and `duration`"
203+
The function [`scale_op_sp(t_inv, t)`](@extref EnergyModelsBase.scale_op_sp) calculates the scaling factor between operational and investment periods including the `duration` of each operational period.
204+
It hence must be divided by the `duration` of the first operational period to avoid including the duration of the operational period.

examples/flexible_demand.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ CO2 = ResourceEmit("CO2", 0)
2828
# This would be a huge incentive to produce during the weekend, if we allowed the
2929
# `PeriodDemandSink` capacity during the weekend.
3030
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]
31-
el_cost = [repeat(day, 5)..., fill(0, 2 * 24)...]
31+
el_cost = vcat(repeat(day, 5), zeros(48))
3232

3333
grid = RefSource(
3434
"grid",
@@ -41,16 +41,16 @@ grid = RefSource(
4141
# The production can only run between 6-20 on weekdays, with a capacity of 300 kW.
4242
# First, define the maximum capacity for a regular weekday (24 hours).
4343
# 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.
44-
weekday_prod = [fill(0, 6)..., fill(300, 14)..., fill(0, 4)...]
45-
@assert length(weekday_prod) == 24
44+
weekday_prod = vcat(zeros(6), fill(300, 14), zeros(4))
45+
4646
# Repeat a weekday 5 times, for a workweek, then no production on the weekends.
47-
week_prod = [repeat(weekday_prod, 5)..., fill(0, 2 * 24)...]
47+
week_prod = vcat(repeat(weekday_prod, 5), fill(0, 2 * 24))
4848

4949
demand = PeriodDemandSink(
5050
"demand_product",
51-
24, # 24 hours per day.
52-
[fill(1500, 5)..., 0, 0], # Demand of 1500 units per day, and nothing (0) in the weekend.
5351
OperationalProfile(week_prod), # kW - installed capacity
52+
24, # 24 hours per day.
53+
PartitionProfile(vcat(ones(5)*1500, [0, 0])), # Demand of 1500 units per day, and nothing (0) in the weekend.
5454
Dict(:surplus => FixedProfile(0), :deficit => FixedProfile(1e8)), # € / Demand - Price for not delivering products
5555
Dict(Product => 1),
5656
)

0 commit comments

Comments
 (0)