Skip to content

Commit bfdfec6

Browse files
authored
Enhance/transfer energy potentials (#81)
* New functions (`variables_flow_resource()`, `constraints_resource()`, `constraints_couple_resource()`) that dispatch on resource types. * New function to indentify the unique resource types of a vector of resources. * New function that segments the vector of resources into sub-vectors based on each resource type. * This allows for creation of new resource-specific variables and constraints in extension packages.
1 parent 3de31b3 commit bfdfec6

11 files changed

Lines changed: 609 additions & 15 deletions

File tree

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release notes
22

3+
## Unversioned
4+
5+
* New functions (`variables_flow_resource()`, `constraints_resource()`, `constraints_couple_resource()`) that dispatch on resource types, which allow for creation of new resource-specific variables and constraints in extension packages.
6+
* New function to indentify the unique resource types of a vector of resources
7+
* New function that segments the vector of resources into sub-vectors based on each resource type
8+
39
## Version 0.9.4 (2025-11-26)
410

511
### Bugfixes

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "EnergyModelsBase"
22
uuid = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
33
authors = ["Lars Hellemo <Lars.Hellemo@sintef.no>, Julian Straus <Julian.Straus@sintef.no>"]
4-
version = "0.9.4"
4+
version = "0.9.5"
55

66
[deps]
77
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ makedocs(
5858
"How to" => Any[
5959
"Create a new element"=>"how-to/create_new_element.md",
6060
"Create a new node"=>"how-to/create-new-node.md",
61+
"Extend resource functionality"=>"how-to/extend-resource-functionality.md",
6162
"Utilize TimeStruct"=>"how-to/utilize-timestruct.md",
6263
"Update models"=>"how-to/update-models.md",
6364
"Contribute to EnergyModelsBase"=>"how-to/contribute.md",
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# [Extend resource functionality](@id how_to-res_funct)
2+
3+
```@meta
4+
CurrentModule = EMB
5+
```
6+
7+
## [Concept](@id how_to-res_funct-concept)
8+
9+
This guide shows how to extend resource functionality by adding a custom resource type and connecting it to custom variables and constraints through resource-dispatch functions.
10+
This is useful for modelling more complex resource behavior that cannot be captured by the default resource types where the standard behavior is built around energy or mass flow.
11+
12+
The pattern follows the same structure as the resource dispatch test in `test/test_resource.jl`:
13+
14+
1. Define a resource subtype with extra parameters.
15+
2. (Optionally) create a custom node subtype that uses the resource.
16+
3. Add resource-specific variables with [`variables_flow_resource`](@ref).
17+
4. Add resource-specific constraints with [`constraints_resource`](@ref).
18+
5. Couple node and link resource variables with [`constraints_couple_resource`](@ref).
19+
20+
## [Example](@id how_to-res_funct-example)
21+
22+
The following example illustrates the different steps that are required for creating a new resource with additional properties.
23+
It defines a `PotentialPower` resource which has as property a potential with upper and lower bounds in addition to its energy flow.
24+
The flow of this potential in and out of junctions follows equality constraints, as opposed to the energy and mass flow which follow sum constraints.
25+
26+
The notation below follows the same conventions as the implementation and tests:
27+
28+
- `𝒩` for nodes,
29+
- `` for links,
30+
- `𝒫` for resources,
31+
- `𝒯` for the time structure,
32+
- `ℒᶠʳᵒᵐ`, `ℒᵗᵒ` for outgoing and incoming links of a node, and
33+
- `𝒫ᵒᵘᵗ`, `𝒫ⁱⁿ`, `𝒫ˡⁱⁿᵏ` for resource subsets on outputs, inputs, and links.
34+
35+
### 1. Define a special resource
36+
37+
Create a subtype of [`Resource`](@ref) and keep `co2_int` as the second field for consistency with existing resource structures.
38+
Alternatively, you can create a new method for the internal function [`co2_int`](@ref).
39+
40+
```julia
41+
struct PotentialPower <: Resource
42+
id::String
43+
co2_int::Float64
44+
potential_lower::Float64
45+
potential_upper::Float64
46+
end
47+
48+
EMB.is_resource_emit(::PotentialPower) = false
49+
lower_limit(p::PotentialPower) = p.potential_lower
50+
upper_limit(p::PotentialPower) = p.potential_upper
51+
```
52+
53+
### 2. Define a custom node (optional)
54+
55+
If your resource needs dedicated node behavior, create a custom node subtype.
56+
If the node subtype is parametrized, it can handle different types of resources in different ways without defining multiple node types.
57+
In the dispatch test, the custom node is an intermediate `NetworkNode` with a potential loss, but without a loss in energy flow.
58+
59+
```julia
60+
struct PotentialLossNode{T<:PotentialPower} <: NetworkNode
61+
id::Any
62+
cap::TimeProfile
63+
opex_var::TimeProfile
64+
opex_fixed::TimeProfile
65+
resource::T
66+
input::Dict{<:Resource,<:Real}
67+
output::Dict{<:Resource,<:Real}
68+
data::Vector{<:ExtensionData}
69+
loss_factor::Float64
70+
end
71+
72+
function PotentialLossNode(
73+
id,
74+
cap::TimeProfile,
75+
opex_var::TimeProfile,
76+
opex_fixed::TimeProfile,
77+
resource::T,
78+
loss_factor::Float64,
79+
) where {T<:PotentialPower}
80+
return PotentialLossNode{T}(
81+
id,
82+
cap,
83+
opex_var,
84+
opex_fixed,
85+
resource,
86+
Dict(resource => 1.0),
87+
Dict(resource => 1.0),
88+
ExtensionData[],
89+
loss_factor,
90+
)
91+
end
92+
```
93+
94+
### 3. Declare resource-specific variables
95+
96+
Use [`variables_flow_resource`](@ref) to create resource variables.
97+
98+
Important:
99+
100+
- Declare each variable name once.
101+
- Filter `𝒩` and `` down to the subsets that actually use the special resource.
102+
- You can create resource dependent bounds as well.
103+
104+
```julia
105+
function EMB.variables_flow_resource(
106+
m,
107+
𝒩::Vector{<:EMB.Node},
108+
𝒫::Vector{<:PotentialPower},
109+
𝒯,
110+
modeltype::EnergyModel,
111+
)
112+
𝒩ᵒᵘᵗ = filter(n -> any(p 𝒫 for p outputs(n)), 𝒩)
113+
𝒩ⁱⁿ = filter(n -> any(p 𝒫 for p inputs(n)), 𝒩)
114+
115+
@variable(m,
116+
lower_limit(p)
117+
energy_potential_node_out[n 𝒩ᵒᵘᵗ, 𝒯, p intersect(outputs(n), 𝒫)]
118+
upper_limit(p)
119+
)
120+
@variable(m,
121+
lower_limit(p)
122+
energy_potential_node_in[n 𝒩ⁱⁿ, 𝒯, p intersect(inputs(n), 𝒫)]
123+
upper_limit(p)
124+
)
125+
end
126+
127+
function EMB.variables_flow_resource(
128+
m,
129+
::Vector{<:Link},
130+
𝒫::Vector{<:PotentialPower},
131+
𝒯,
132+
modeltype::EnergyModel,
133+
)
134+
ℒᵉᵖ = filter(l -> any(p 𝒫 for p EMB.link_res(l)), ℒ)
135+
@variable(m, energy_potential_link_in[ℒᵉᵖ, 𝒯, 𝒫])
136+
@variable(m, energy_potential_link_out[ℒᵉᵖ, 𝒯, 𝒫])
137+
end
138+
```
139+
140+
### 4. Add resource-specific constraints
141+
142+
Create a new method [`constraints_resource`](@ref) for custom node or link behavior.
143+
These methods can be either for the complete set of [`Node`](@ref EnergyModelsBase.Node) and [`Link`](@ref)s or alternatively for only a specified subset of nodes.
144+
If you only specify it for a subset of nodes, it is important that the new resource is only an `input` or `output` of this subset.
145+
146+
```julia
147+
function EMB.constraints_resource(
148+
m,
149+
n::PotentialLossNode,
150+
𝒯,
151+
𝒫::Vector{<:PotentialPower},
152+
modeltype::EnergyModel,
153+
)
154+
𝒫ᵒᵘᵗ = filter(p -> p 𝒫, outputs(n))
155+
𝒫ⁱⁿ = filter(p -> p 𝒫, inputs(n))
156+
157+
@constraint(m, [t 𝒯, p 𝒫ᵒᵘᵗ],
158+
m[:energy_potential_node_out][n, t, p] ==
159+
n.loss_factor * m[:energy_potential_node_in][n, t, p]
160+
)
161+
end
162+
163+
function EMB.constraints_resource(
164+
m,
165+
l::Link,
166+
𝒯,
167+
𝒫::Vector{<:PotentialPower},
168+
modeltype::EnergyModel,
169+
)
170+
𝒫ˡⁱⁿᵏ = filter(p -> p 𝒫, EMB.link_res(l))
171+
@constraint(m, [t 𝒯, p 𝒫ˡⁱⁿᵏ],
172+
m[:energy_potential_link_in][l, t, p] ==
173+
m[:energy_potential_link_out][l, t, p]
174+
)
175+
end
176+
```
177+
178+
### 5. Couple node and link variables
179+
180+
Use [`constraints_couple_resource`](@ref) to connect node and link resource variables.
181+
182+
```julia
183+
function EMB.constraints_couple_resource(
184+
m,
185+
𝒩::Vector{<:EMB.Node},
186+
::Vector{<:Link},
187+
𝒫::Vector{<:PotentialPower},
188+
𝒯,
189+
modeltype::EnergyModel,
190+
)
191+
for n 𝒩
192+
ℒᶠʳᵒᵐ, ℒᵗᵒ = EMB.link_sub(ℒ, n)
193+
𝒫ᵒᵘᵗ = filter(p -> p 𝒫, outputs(n))
194+
𝒫ⁱⁿ = filter(p -> p 𝒫, inputs(n))
195+
196+
@constraint(m, [t 𝒯, p 𝒫ᵒᵘᵗ, l ℒᶠʳᵒᵐ],
197+
m[:energy_potential_node_out][n, t, p] ==
198+
m[:energy_potential_link_in][l, t, p]
199+
)
200+
201+
@constraint(m, [t 𝒯, p 𝒫ⁱⁿ, l ℒᵗᵒ],
202+
m[:energy_potential_link_out][l, t, p] ==
203+
m[:energy_potential_node_in][n, t, p]
204+
)
205+
end
206+
end
207+
```

docs/src/how-to/utilize-timestruct.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ op_number = length(op_duration)
3939
operational_periods = SimpleTimes(op_number, op_duration)
4040
4141
# output
42-
SimpleTimes{Int64}(11, [4, 2, 1, 1, 2, 4, 2, 1, 1, 2, 4])
42+
SimpleTimes{Int64}(11, [4, 2, 1, 1, 2, 4, 2, 1, 1, 2, 4], 24)
4343
```
4444

4545
In this case, we model the day not with hourly resolution, but only have hourly resolution in the morning and afternoon.
@@ -60,7 +60,7 @@ Instead, one can also write
6060
operational_periods = SimpleTimes(op_duration)
6161
6262
# output
63-
SimpleTimes{Int64}(11, [4, 2, 1, 1, 2, 4, 2, 1, 1, 2, 4])
63+
SimpleTimes{Int64}(11, [4, 2, 1, 1, 2, 4, 2, 1, 1, 2, 4], 24)
6464
```
6565

6666
and a constructor will automatically deduce that there have to be 11 operational periods.

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Depth = 1
6161
Pages = [
6262
"how-to/create_new_element.md",
6363
"how-to/create-new-node.md",
64+
"how-to/extend-resource-functionality.md",
6465
"how-to/utilize-timestruct.md",
6566
"how-to/update-models.md",
6667
"how-to/contribute.md",

docs/src/library/internals/functions.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ emissions_operational
2828
constraints_emissions
2929
constraints_elements
3030
constraints_couple
31+
constraints_couple_resource
32+
constraints_resource
3133
constraints_level_iterate
3234
constraints_level_rp
3335
constraints_level_scp
@@ -39,6 +41,7 @@ constraints_level_bounds
3941
```@docs
4042
variables_capacity
4143
variables_flow
44+
variables_flow_resource
4245
variables_opex
4346
variables_capex
4447
variables_emission
@@ -96,4 +99,6 @@ res_sub
9699
```@docs
97100
collect_types
98101
sort_types
102+
res_types
103+
res_types_vec
99104
```

0 commit comments

Comments
 (0)