Skip to content

Commit d659861

Browse files
committed
Review updates
- Changes in documentation - Changed argument order in fallback method - Rearranged tests
1 parent e71bbde commit d659861

3 files changed

Lines changed: 174 additions & 228 deletions

File tree

docs/src/how-to/extend-resource-functionality.md

Lines changed: 45 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,41 @@
1-
# [Extend resource functionality](@id how_to-extend-resource-functionality)
1+
# [Extend resource functionality](@id how_to-res_funct)
22

33
```@meta
44
CurrentModule = EMB
55
```
66

7-
This guide shows how to extend resource functionality by adding a custom resource
8-
type and connecting it to custom variables and constraints through
9-
resource-dispatch functions. This is useful for modelling more complex
10-
resource behavior that cannot be captured by the default resource types where the standard
11-
behavior is built around energy or mass flow.
7+
## [Concept](@id how_to-res_funct-concept)
128

13-
The pattern follows the same structure as the resource dispatch test in
14-
`test/test_resource.jl`:
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`:
1513

1614
1. Define a resource subtype with extra parameters.
17-
2. Optionally create a custom node subtype that uses the resource.
18-
3. Add resource-specific variables with `variables_flow_resource`.
19-
4. Add resource-specific constraints with `constraints_resource`.
20-
5. Couple node and link resource variables with `constraints_couple_resource`.
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)
2121

22-
The example in the test suite defines a `PotentialPower` resource that has a potential,
23-
with upper and lower bounds, in addition to energy flow. The flow of this potential
24-
in and out of junctions follow equality constraints, as opposed to the energy and mass flow
25-
which follow sum constraints.
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.
2625

2726
The notation below follows the same conventions as the implementation and tests:
2827

29-
- `𝒩` for nodes
30-
- `` for links
31-
- `𝒫` for resources
32-
- `𝒯` for the time structure
33-
- `ℒᶠʳᵒᵐ`, `ℒᵗᵒ` for outgoing and incoming links of a node
34-
- `𝒫ᵒᵘᵗ`, `𝒫ⁱⁿ`, `𝒫ˡⁱⁿᵏ` for resource subsets on outputs, inputs, and links
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.
3534

36-
## 1. Define a special resource
35+
### 1. Define a special resource
3736

38-
Create a subtype of [`Resource`](@ref) and keep `co2_int` as the second field for
39-
consistency with existing resource structures.
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).
4039

4140
```julia
4241
struct PotentialPower <: Resource
@@ -51,13 +50,11 @@ lower_limit(p::PotentialPower) = p.potential_lower
5150
upper_limit(p::PotentialPower) = p.potential_upper
5251
```
5352

54-
## 2. Define a custom node (optional)
53+
### 2. Define a custom node (optional)
5554

5655
If your resource needs dedicated node behavior, create a custom node subtype.
57-
If the node subtype is parametrized, it can handle different types of resources
58-
in different ways without defining multiple node types. In the dispatch test,
59-
the custom node is an intermediate `NetworkNode` with a potential loss, but
60-
without a loss in energy flow.
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.
6158

6259
```julia
6360
struct PotentialLossNode{T<:PotentialPower} <: NetworkNode
@@ -94,14 +91,15 @@ function PotentialLossNode(
9491
end
9592
```
9693

97-
## 3. Declare resource-specific variables
94+
### 3. Declare resource-specific variables
9895

9996
Use [`variables_flow_resource`](@ref) to create resource variables.
10097

10198
Important:
99+
102100
- Declare each variable name once.
103101
- Filter `𝒩` and `` down to the subsets that actually use the special resource.
104-
- Keep bounds in `constraints_resource` when they depend on dispatch logic.
102+
- You can create resource dependent bounds as well.
105103

106104
```julia
107105
function EMB.variables_flow_resource(
@@ -111,19 +109,18 @@ function EMB.variables_flow_resource(
111109
𝒯,
112110
modeltype::EnergyModel,
113111
)
114-
output_nodes = filter(n -> any(p 𝒫 for p outputs(n)), 𝒩)
115-
input_nodes = filter(n -> any(p 𝒫 for p inputs(n)), 𝒩)
112+
𝒩ᵒᵘᵗ = filter(n -> any(p 𝒫 for p outputs(n)), 𝒩)
113+
𝒩ⁱⁿ = filter(n -> any(p 𝒫 for p inputs(n)), 𝒩)
116114

117-
@variable(
118-
m, energy_potential_node_out[
119-
n output_nodes, t 𝒯, p 𝒫; p outputs(n)
120-
]
115+
@variable(m,
116+
lower_limit(p)
117+
energy_potential_node_out[n 𝒩ᵒᵘᵗ, 𝒯, p intersect(outputs(n), 𝒫)]
118+
upper_limit(p)
121119
)
122-
123-
@variable(
124-
m, energy_potential_node_in[
125-
n input_nodes, t 𝒯, p intersect(inputs(n), 𝒫)
126-
]
120+
@variable(m,
121+
lower_limit(p)
122+
energy_potential_node_in[n 𝒩ⁱⁿ, 𝒯, p intersect(inputs(n), 𝒫)]
123+
upper_limit(p)
127124
)
128125
end
129126

@@ -140,9 +137,11 @@ function EMB.variables_flow_resource(
140137
end
141138
```
142139

143-
## 4. Add resource-specific constraints
140+
### 4. Add resource-specific constraints
144141

145-
Use [`constraints_resource`](@ref) for custom node or link behavior.
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.
146145

147146
```julia
148147
function EMB.constraints_resource(
@@ -159,48 +158,6 @@ function EMB.constraints_resource(
159158
m[:energy_potential_node_out][n, t, p] ==
160159
n.loss_factor * m[:energy_potential_node_in][n, t, p]
161160
)
162-
163-
# Bounds are added as constraints because they rely on `p`,
164-
# which is an index in `energy_potential` variables.
165-
@constraint(m, [t 𝒯, p 𝒫ᵒᵘᵗ],
166-
m[:energy_potential_node_out][n, t, p] >= lower_limit(p)
167-
)
168-
@constraint(m, [t 𝒯, p 𝒫ᵒᵘᵗ],
169-
m[:energy_potential_node_out][n, t, p] <= upper_limit(p)
170-
)
171-
@constraint(m, [t 𝒯, p 𝒫ⁱⁿ],
172-
m[:energy_potential_node_in][n, t, p] >= lower_limit(p)
173-
)
174-
@constraint(m, [t 𝒯, p 𝒫ⁱⁿ],
175-
m[:energy_potential_node_in][n, t, p] <= upper_limit(p)
176-
)
177-
178-
end
179-
180-
function EMB.constraints_resource(
181-
m,
182-
n::EMB.Node,
183-
𝒯,
184-
𝒫::Vector{<:PotentialPower},
185-
modeltype::EnergyModel,
186-
)
187-
𝒫ᵒᵘᵗ = filter(p -> p 𝒫, outputs(n))
188-
𝒫ⁱⁿ = filter(p -> p 𝒫, inputs(n))
189-
190-
# Bounds are added as constraints because they rely on `p`,
191-
# which is an index in `energy_potential` variables.
192-
@constraint(m, [t 𝒯, p 𝒫ᵒᵘᵗ],
193-
m[:energy_potential_node_out][n, t, p] >= lower_limit(p)
194-
)
195-
@constraint(m, [t 𝒯, p 𝒫ᵒᵘᵗ],
196-
m[:energy_potential_node_out][n, t, p] <= upper_limit(p)
197-
)
198-
@constraint(m, [t 𝒯, p 𝒫ⁱⁿ],
199-
m[:energy_potential_node_in][n, t, p] >= lower_limit(p)
200-
)
201-
@constraint(m, [t 𝒯, p 𝒫ⁱⁿ],
202-
m[:energy_potential_node_in][n, t, p] <= upper_limit(p)
203-
)
204161
end
205162

206163
function EMB.constraints_resource(
@@ -218,7 +175,7 @@ function EMB.constraints_resource(
218175
end
219176
```
220177

221-
## 5. Couple node and link variables
178+
### 5. Couple node and link variables
222179

223180
Use [`constraints_couple_resource`](@ref) to connect node and link resource variables.
224181

src/model.jl

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,13 @@ function variables_capacity(m, ℒ::Vector{<:Link}, 𝒳ᵛᵉᶜ, 𝒯, modelty
203203
end
204204

205205
"""
206+
variables_flow(m, 𝒳::Vector{<:AbstractElement}, 𝒳ᵛᵉᶜ, 𝒫, 𝒯, modeltype::EnergyModel)
206207
variables_flow(m, 𝒩::Vector{<:Node}, 𝒳ᵛᵉᶜ, 𝒯, modeltype::EnergyModel)
207208
variables_flow(m, ℒ::Vector{<:Link}, 𝒳ᵛᵉᶜ, 𝒯, modeltype::EnergyModel)
208209
209-
Declaration of flow OPEX variables for the element types introduced in
210-
`EnergyModelsBase`. `EnergyModelsBase` introduces two elements for an energy system, and
211-
hence, provides the user with two individual methods:
210+
Declaration of flow variables for the element types introduced in `EnergyModelsBase`.
211+
`EnergyModelsBase` introduces two elements for an energy system, and hence, provides the
212+
user with two individual methods:
212213
213214
!!! note "Node variables"
214215
- `flow_in[n, t, p]` is the flow _**into**_ node `n` in operational period `t` for
@@ -217,6 +218,8 @@ hence, provides the user with two individual methods:
217218
- `flow_out[n, t, p]` is the flow _**from**_ node `n` in operational period `t`
218219
for resource `p`. The outflow resources of node `n` are extracted using the
219220
function [`outputs`](@ref).
221+
- call of the function [`variables_flow_resource`](@ref) for introducing resource
222+
specific flow variables.
220223
221224
!!! tip "Link variables"
222225
- `link_in[l, t, p]` is the flow _**into**_ link `l` in operational period `t` for
@@ -225,10 +228,15 @@ hence, provides the user with two individual methods:
225228
- `link_out[l, t, p]` is the flow _**from**_ link `l` in operational period `t`
226229
for resource `p`. The outflow resources of link `l` are extracted using the
227230
function [`outputs`](@ref).
231+
- call of the function [`variables_flow_resource`](@ref) for introducing resource
232+
specific flow variables.
228233
229234
By default, all nodes `𝒩` and links `ℒ` only allow for unidirectional flow. You can specify
230235
bidirectional flow through providing a method to the function [`is_unidirectional`](@ref)
231236
for new link/node types.
237+
238+
The fallback solution for `𝒳::Vector{<:AbstractElement}` is in the current stage included
239+
to maintain backwards compatibility for packages that introduce additional [`AbstractElement`](@ref)s.
232240
"""
233241
function variables_flow(m, 𝒩::Vector{<:Node}, 𝒳ᵛᵉᶜ, 𝒫, 𝒯, modeltype::EnergyModel)
234242
# Extract the nodes with inputs and outputs
@@ -251,7 +259,7 @@ function variables_flow(m, 𝒩::Vector{<:Node}, 𝒳ᵛᵉᶜ, 𝒫, 𝒯, mode
251259
end
252260

253261
# Create new flow variables for specific resource types
254-
for p_sub in res_types_vec(𝒫)
262+
for p_sub res_types_vec(𝒫)
255263
variables_flow_resource(m, 𝒩, p_sub, 𝒯, modeltype)
256264
end
257265

@@ -278,11 +286,8 @@ function variables_flow(m, ℒ::Vector{<:Link}, 𝒳ᵛᵉᶜ, 𝒫, 𝒯, model
278286
variables_flow_resource(m, ℒ, p_sub, 𝒯, modeltype)
279287
end
280288
end
281-
282-
# 5-parameter backward compatibility wrapper (for extension packages with old signature)
283-
function variables_flow(m, 𝒳::Vector{<:AbstractElement}, 𝒳ᵛᵉᶜ, 𝒯, modeltype::EnergyModel)
284-
variables_flow(m, 𝒳, 𝒳ᵛᵉᶜ, Resource[], 𝒯, modeltype)
285-
end
289+
variables_flow(m, 𝒳::Vector{<:AbstractElement}, 𝒳ᵛᵉᶜ, 𝒫, 𝒯, modeltype::EnergyModel) =
290+
variables_flow(m, 𝒳, 𝒳ᵛᵉᶜ, 𝒯, modeltype)
286291

287292
"""
288293
variables_flow_resource(m, ℒ::Vector{<:Link}, 𝒫::Vector{<:Resource}, 𝒯, modeltype::EnergyModel)
@@ -594,8 +599,9 @@ end
594599
create_element(m, n::Node, 𝒯, 𝒫, modeltype::EnergyModel)
595600
create_element(m, l::Link, 𝒯, 𝒫, modeltype::EnergyModel)
596601
597-
Calls the create functions for the specific elements to add element specific constraints,
598-
also add resource specific constraints through constraints_resource.
602+
Calls the create functions for the specific elements to add element specific constraints (by
603+
calling individual subfunctions) and add resource specific constraints by calling
604+
[`constraints_resource`](@ref).
599605
600606
`EnergyModelsBase` provides the user with two element types, [`Link`](@ref) and
601607
[`Node`](@ref EnergyModelsBase.Node):
@@ -604,7 +610,7 @@ also add resource specific constraints through constraints_resource.
604610
- `Link` - the subfunction is [`create_link`](@ref).
605611
"""
606612
function create_element(m, n::Node, 𝒯, 𝒫, modeltype::EnergyModel)
607-
613+
608614
create_node(m, n, 𝒯, 𝒫, modeltype)
609615

610616
# Constraints based on the resource types
@@ -1046,4 +1052,4 @@ function create_link(m, 𝒯, 𝒫, l::Link, modeltype::EnergyModel, formulation
10461052
if has_capacity(l)
10471053
constraints_capacity_installed(m, l, 𝒯, modeltype)
10481054
end
1049-
end
1055+
end

0 commit comments

Comments
 (0)