|
| 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 | +``` |
0 commit comments