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
10 changes: 9 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Release notes

## Unversioned

### Enhancements

* Improved `MinUpDownTimeNode`:
* Rewrote the function `constraints_capacity` based on core functionality.
* Updated the test set to identify all potential problems with the node.


## Version 0.2.11 (2026-02-16)

### Enhancements
Expand All @@ -18,7 +27,6 @@
* Removed `ext/EMGUIExt/descriptive_names.yml` as this will now be provided directly in `EnergyModelsGUI`.
* Removed `docs/src/example/flexible_demand.md` as the markdown versions of the example files are now generated automatically (and these are thus added to the `.gitignore`-file).


## Version 0.2.9 (2025-07-08)

* Adjusted to [`EnergyModelsBase` v0.9.0](https://github.com/EnergyModelsX/EnergyModelsBase.jl/releases/tag/v0.9.0):
Expand Down
2 changes: 1 addition & 1 deletion docs/src/links/capacitycostlink.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ with square brackets, while functions are represented as

``func\_example(index_1, index_2)``

with paranthesis.
with parantheses.

### [Variables](@id links-CapacityCostLink-math-var)

Expand Down
2 changes: 1 addition & 1 deletion docs/src/nodes/network/activationcostnode.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The standard fields are given as:
If the node should contain investments through the application of [`EnergyModelsInvestments`](https://energymodelsx.github.io/EnergyModelsInvestments.jl/), it is important to note that you can only use `FixedProfile` or `StrategicProfile` for the capacity, but not `RepresentativeProfile` or `OperationalProfile`.
In addition, all values have to be non-negative.
- **`opex_var::TimeProfile`**:\
The variable operational expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
The variable operating expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
Hence, it is directly related to the specified `input` and `output` ratios.
The variable operating expenses can be provided as `OperationalProfile` as well.
- **`opex_fixed::TimeProfile`**:\
Expand Down
2 changes: 1 addition & 1 deletion docs/src/nodes/network/combustion.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The standard fields are given as:
If the node should contain investments through the application of [`EnergyModelsInvestments`](https://energymodelsx.github.io/EnergyModelsInvestments.jl/), it is important to note that you can only use `FixedProfile` or `StrategicProfile` for the capacity, but not `RepresentativeProfile` or `OperationalProfile`.
In addition, all values have to be non-negative.
- **`opex_var::TimeProfile`**:\
The variable operational expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
The variable operating expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
Hence, it is directly related to the specified `input` and `output` ratios.
The variable operating expenses can be provided as `OperationalProfile` as well.
- **`opex_fixed::TimeProfile`**:\
Expand Down
4 changes: 2 additions & 2 deletions docs/src/nodes/network/flexibleoutput.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The standard fields of a [`FlexibleOutput`](@ref) node are given as:
If the node should contain investments through the application of [`EnergyModelsInvestments`](https://energymodelsx.github.io/EnergyModelsInvestments.jl/), it is important to note that you can only use `FixedProfile` or `StrategicProfile` for the capacity, but not `RepresentativeProfile` or `OperationalProfile`.
In addition, all values have to be non-negative.
- **`opex_var::TimeProfile`**:\
The variable operational expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
The variable operating expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
Hence, it is directly related to the specified `input` and `output` ratios.
The variable operating expenses can be provided as `OperationalProfile` as well.
- **`opex_fixed::TimeProfile`**:\
Expand Down Expand Up @@ -66,7 +66,7 @@ with square brackets, while functions are represented as

``func\_example(index_1, index_2)``

with paranthesis.
with parantheses.

### [Variables](@id nodes-FlexibleOutput-math-var)

Expand Down
2 changes: 1 addition & 1 deletion docs/src/nodes/network/limitedflexibleinput.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The standard fields are given as:
If the node should contain investments through the application of [`EnergyModelsInvestments`](https://energymodelsx.github.io/EnergyModelsInvestments.jl/), it is important to note that you can only use `FixedProfile` or `StrategicProfile` for the capacity, but not `RepresentativeProfile` or `OperationalProfile`.
In addition, all values have to be non-negative.
- **`opex_var::TimeProfile`**:\
The variable operational expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
The variable operating expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
Hence, it is directly related to the specified `input` and `output` ratios.
The variable operating expenses can be provided as `OperationalProfile` as well.
- **`opex_fixed::TimeProfile`**:\
Expand Down
18 changes: 9 additions & 9 deletions docs/src/nodes/network/minupdowntimenode.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The standard fields are given as:
If the node should contain investments through the application of [`EnergyModelsInvestments`](https://energymodelsx.github.io/EnergyModelsInvestments.jl/), it is important to note that you can only use `FixedProfile` or `StrategicProfile` for the capacity, but not `RepresentativeProfile` or `OperationalProfile`.
In addition, all values have to be non-negative.
- **`opex_var::TimeProfile`**:\
The variable operational expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
The variable operating expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
Hence, it is directly related to the specified `input` and `output` ratios.
The variable operating expenses can be provided as `OperationalProfile` as well.
- **`opex_fixed::TimeProfile`**:\
Expand Down Expand Up @@ -55,18 +55,18 @@ The standard fields are given as:

[`MinUpDownTimeNode`](@ref) nodes add four additional fields compared to a [`NetworkNode`](@extref EnergyModelsBase nodes-network_node):

- **`minUpTime::Real`**:\
- **`min_time_up::Real`**:\
Minimum number of operational periods the unit must remain on after being started.
- **`minDownTime::Real`**:\
- **`min_time_down::Real`**:\
Minimum number of operational periods the unit must remain off after being stopped.
- **`minCapacity::Real`**:\
- **`load_min::Real`**:\
Minimum power output when the unit is on. The value must be larger than zero.
- **`maxCapacity::Real`**:\
- **`load_max::Real`**:\
Maximum power output when the unit is on (usually aligned with `cap`).
The value must not be less than `minCapacity`.
The value must not be less than `load_min`.

!!! tip
The fields `minUpTime` and `minDownTime` are defined in terms of operational period durations and should be consistent with the time granularity of the model.
The fields `min_time_up` and `min_time_down` are defined in terms of operational period durations and should be consistent with the time granularity of the model.

## [Mathematical description](@id nodes-minupdowntimenode-math)

Expand Down Expand Up @@ -194,11 +194,11 @@ The function `constraints_capacity` receives a new method to handle the minimum
- **Capacity conditional on on/off status:**

```math
\texttt{cap\_use}[n, t] \leq \texttt{on\_off}[n, t] \times n.maxCapacity
\texttt{cap\_use}[n, t] \leq \texttt{on\_off}[n, t] \times n.load_max
```

```math
\texttt{cap\_use}[n, t] \geq \texttt{on\_off}[n, t] \times n.minCapacity
\texttt{cap\_use}[n, t] \geq \texttt{on\_off}[n, t] \times n.load_min
```

- **Upper bound by installed capacity:**
Expand Down
2 changes: 1 addition & 1 deletion docs/src/nodes/sink/loadshiftingnode.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ with square brackets, while functions are represented as

``func\_example(index_1, index_2)``

with paranthesis.
with parantheses.

### [Variables](@id nodes-loadshiftingnode-math-var)

Expand Down
2 changes: 1 addition & 1 deletion docs/src/nodes/sink/multipleinputsink.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ with square brackets, while functions are represented as

``func\_example(index_1, index_2)``

with paranthesis.
with parantheses.

### [Variables](@id nodes-mul_in_sink-math-var)

Expand Down
2 changes: 1 addition & 1 deletion docs/src/nodes/sink/multipleinputsinkstrat.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ with square brackets, while functions are represented as

``func\_example(index_1, index_2)``

with paranthesis.
with parantheses.

### [Variables](@id nodes-mul_in_sink_strat-math-var)

Expand Down
2 changes: 1 addition & 1 deletion docs/src/nodes/sink/perioddemand.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ with square brackets, while functions are represented as

``func\_example(index_1, index_2)``

with paranthesis.
with parantheses.

### [Variables](@id nodes-perioddemandsink-math-var)

Expand Down
4 changes: 2 additions & 2 deletions docs/src/nodes/source/inflexiblesource.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The [`InflexibleSource`](@ref) has the same standard fields as the [`RefSource`]
If the node should contain investments through the application of [`EnergyModelsInvestments`](https://energymodelsx.github.io/EnergyModelsInvestments.jl/stable/), it is important to note that you can only use `FixedProfile` or `StrategicProfile` for the capacity, but not `RepresentativeProfile` or `OperationalProfile`.
In addition, all values have to be non-negative.
- **`opex_var::TimeProfile`**:\
The variable operational expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
The variable operating expenses are based on the capacity utilization through the variable [`:cap_use`](@extref EnergyModelsBase man-opt_var-cap).
Hence, it is directly related to the specified `output` ratios.
The variable operating expenses can be provided as `OperationalProfile` as well.
- **`opex_fixed::TimeProfile`**:\
Expand Down Expand Up @@ -59,7 +59,7 @@ with square brackets, while functions are represented as

``func\_example(index_1, index_2)``

with paranthesis.
with parantheses.

### [Variables](@id nodes-inflexiblesource-math-var)

Expand Down
8 changes: 5 additions & 3 deletions docs/src/nodes/source/payasproducedppa.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The fields of a [`PayAsProducedPPA`](@ref) are:
In addition, all values should be in the range ``[0, 1]``.

- **`opex_var::TimeProfile`**:\
The variable operational expenses are based on the capacity utilization through the variable `:cap_use`.
The variable operating expenses are based on the capacity utilization through the variable `:cap_use`.
Hence, they are directly related to the specified `output` ratios.
The variable operating expenses can be provided as `OperationalProfile` as well.

Expand Down Expand Up @@ -55,7 +55,7 @@ with square brackets, while functions are represented as

``func\_example(index_1, index_2)``

with paranthesis.
with parantheses.

### [Variables](@id nodes-payasproducedppa-math-var)

Expand Down Expand Up @@ -100,8 +100,8 @@ These standard constraints are:
profile(n, t) \times \texttt{cap\_inst}[n, t]
```


- `constraints_capacity_installed`:

```math
\texttt{cap\_inst}[n, t] = capacity(n, t)
```
Expand All @@ -111,13 +111,15 @@ These standard constraints are:
Nodes with investments are then no longer constrained by the parameter capacity.

- `constraints_flow_out`:

```math
\texttt{flow\_out}[n, t, p] =
outputs(n, p) \times \texttt{cap\_use}[n, t]
\qquad \forall p ∈ outputs(n) \setminus \{\text{CO}_2\}
```

- `constraints_opex_fixed`:

```math
\texttt{opex\_fixed}[n, t_{inv}] = opex\_fixed(n, t_{inv}) \times \texttt{cap\_inst}[n, first(t_{inv})]
```
Expand Down
2 changes: 1 addition & 1 deletion docs/src/nodes/storage/storageefficiency.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ with square brackets, while functions are represented as

``func\_example(index_1, index_2)``

with paranthesis.
with parantheses.

### [Variables](@id nodes-stor_eff-math-var)

Expand Down
9 changes: 4 additions & 5 deletions examples/flexible_demand.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,10 @@ line = MinUpDownTimeNode(
FixedProfile(0),
Dict(Power => 1),
Dict(Product => 1),
min_up_time, # minUpTime
min_down_time, # minDownTime
50, # minCapacity
300, # maxCapacity
[],
min_up_time, # min_time_up
min_down_time, # min_time_down
50, # load_min
300, # load_max
)

# Define the simple energy system
Expand Down
2 changes: 1 addition & 1 deletion src/link/datastructures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ EMB.capacity(l::CapacityCostLink, t) = l.cap[t]
"""
EMB.has_opex(l::CapacityCostLink)

A `CapacityCostLink` `l` has operational expenses.
A `CapacityCostLink` `l` has operating expenses.
"""
EMB.has_opex(l::CapacityCostLink) = true

Expand Down
23 changes: 17 additions & 6 deletions src/network/checks.jl
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
"""
EMB.check_node(n::MinUpDownTimeNode, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)

This method checks that a `MinUpDownTimeNode` node is valid.
This method checks that a [`MinUpDownTimeNode`](@ref) node is valid.

It reuses the standard checks of a `NetworkNode` node through calling the function
[`EMB.check_node_default`](@extref EnergyModelsBase.check_node_default), but adds an
additional check on the data.

## Checks
- The minimum capacity must be greater than zero.
- The minimum capacity must not be larger than maximum capacity.
- The field `cap` is required to be non-negative.
- The value of the field `fixed_opex` is required to be non-negative and
accessible through a `StrategicPeriod` as outlined in the function
[`EMB.check_fixed_opex()`](@extref EnergyModelsBase.check_fixed_opex).
- The values of the dictionary `input` are required to be non-negative.
- The values of the dictionary `output` are required to be non-negative.

- The field `load_min` is required to be greater than zero.
- The field `load_min` is required to be not larger than the field `load_max`.
"""
function EMB.check_node(
n::MinUpDownTimeNode,
𝒯,
modeltype::EnergyModel,
check_timeprofiles::Bool,
)
# EMB.check_node_default(n, 𝒯, modeltype, check_timeprofiles)
EMB.check_node_default(n, 𝒯, modeltype, check_timeprofiles)

# We need the minimum capacity to be greater than zero.
@assert_or_log(
n.minCapacity > 0,
n.load_min > 0,
"The minimum capacity must be greater than zero."
)

@assert_or_log(
n.minCapacity <= n.maxCapacity,
n.load_min ≤ n.load_max,
"The minimum capacity must not be larger than maximum capacity."
)
end
Expand Down
29 changes: 14 additions & 15 deletions src/network/constraint_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,42 +24,41 @@ function EMB.constraints_capacity(
𝒯::TimeStructure,
modeltype::EnergyModel,
)
# Extract the strategic time structure
𝒯ᴵⁿᵛ = strategic_periods(𝒯)
sps = collect(𝒯ᴵⁿᵛ)

for spsps
ops = collect(sp) #array of al operational periodes
for t_inv𝒯ᴵⁿᵛ
ops = collect(t_inv) #array of al operational periodes

N_h = n.minDownTime #min down time in the tinme unit used in the case
M_h = n.minUpTime
durations = [duration(t) for t ∈ ops]
N_h = n.min_time_down #min down time in the tinme unit used in the case
M_h = n.min_time_up
durations = [duration(t) for t ∈ t_inv]

N_arr = zeros(length(ops))
M_arr = zeros(length(ops))
N_arr = zeros(length(t_inv))
M_arr = zeros(length(t_inv))

for j ∈ 1:length(ops)
sum_duration_N = durations[j]
for (j, t) ∈ enumerate(t_inv)
sum_duration_N = duration(t)
count = 1
while sum_duration_N < N_h
sum_duration_N += circshift(durations, -count)[j]
count += 1
end
N_arr[j] = count
count = 1
sum_duration_M = durations[j]
sum_duration_M = duration(t)
while sum_duration_M < M_h
sum_duration_M += circshift(durations, -count)[j]
count += 1
end
M_arr[j] = count
end

min_cap = n.minCapacity
max_cap = n.maxCapacity
for i ∈ 1:length(ops) # i from 1 to number of operational periodes
min_cap = n.load_min
max_cap = n.load_max
for (i, t) ∈ enumerate(t_inv) # i from 1 to number of operational periodes
M = Int(M_arr[i])
N = Int(N_arr[i])
t = ops[i]

@constraint(
m,
Expand Down
Loading
Loading