Extend Resource functionality
This guide is the EnergyModelsGeography counterpart to the resource functionality introduced in EnergyModelsBase. It shows how that same pattern is used for geography-specific coupling through a concrete example from test_resource_flow.jl: a PotentialPower resource with dedicated flow variables and coupling constraints.
While we allow resource variable introduction for Areas, we strongly advise against introducing new variables for an Area. It is instead easier to access in the function EMB.constraints_couple_resource the relevant Availability node as outlined below.
This approach allows you to couple the local energy system with the transmission modes with respect to the extra variables.
Practical example: PotentialPower
The goal is to track a resource-specific "potential" flow in parallel with standard transmission flow and enforce a mode-specific loss factor.
1. Define the resource and mode
You can use the same resource type as declared in EnergyModelsBase or any other package. This corresponds to step 1 in the example of EnergyModelsBase.
struct PotentialPower <: Resource
id::String
co2_int::Float64
potential_lower::Float64
potential_upper::Float64
end
EMB.is_resource_emit(::PotentialPower) = false
lower_limit(p::PotentialPower) = p.potential_lower
upper_limit(p::PotentialPower) = p.potential_upper
struct PotentialLossMode{T <: PotentialPower} <: TransmissionMode
id::String
resource::T
trans_cap::TimeProfile
trans_loss::TimeProfile
opex_var::TimeProfile
opex_fixed::TimeProfile
directions::Int
data::Vector{<:ExtensionData}
loss_factor::Float64
end2. Add resource-specific variables
Implement EMB.variables_flow_resource for both [Area] and [Node] to introduce new variables.
function EMB.variables_flow_resource(
m,
ℳ::Vector{<:TransmissionMode},
𝒫::Vector{<:PotentialPower},
𝒯,
modeltype::EnergyModel,
)
ℳᵖ = filter(tm -> any(p -> p ∈ 𝒫, inputs(tm)) || any(p -> p ∈ 𝒫, outputs(tm)), ℳ)
@variable(
m,
lower_limit(p) <=
energy_potential_trans_in[tm ∈ ℳᵖ, 𝒯, p ∈ intersect(inputs(tm), 𝒫)] <=
upper_limit(p)
)
@variable(
m,
lower_limit(p) <=
energy_potential_trans_out[tm ∈ ℳᵖ, 𝒯, p ∈ intersect(outputs(tm), 𝒫)] <=
upper_limit(p)
)
end
function EMB.variables_flow_resource(
m,
𝒩::Vector{<:Node},
𝒫::Vector{<:PotentialPower},
𝒯,
modeltype::EnergyModel,
)
@variable(m, lower_limit(p) <= energy_potential_node_in[n ∈ 𝒩, 𝒯, p ∈ 𝒫] <= upper_limit(p))
@variable(m, lower_limit(p) <= energy_potential_node_out[n ∈ 𝒩, 𝒯, p ∈ 𝒫] <= upper_limit(p))
end3. Use the new variables in the function
Apply the resource-specific variable in the function EMB.constraints_resource. You must be careful when defining the internal constraints due to potential changes in the variables.
function EMB.constraints_resource(
m,
tm::PotentialLossMode,
𝒯::TimeStructure,
𝒫::Vector{<:PotentialPower},
modeltype::EnergyModel,
)
@constraint(m, [t ∈ 𝒯, p ∈ outputs(tm)],
m[:energy_potential_trans_out][tm, t, p] ==
tm.loss_factor * m[:energy_potential_trans_in][tm, t, p]
)
end4. Couple variables between area and transmission mode
Map area-level variables to transmission-level variables with EMG.constraints_couple_resource.
function EMG.constraints_couple_resource(
m,
𝒜::Vector{<:Area},
ℒᵗʳᵃⁿˢ::Vector{<:Transmission},
𝒫::Vector{<:PotentialPower},
𝒯,
modeltype::EnergyModel,
)
for a ∈ 𝒜, p ∈ 𝒫
ℒᶠʳᵒᵐ, ℒᵗᵒ = EMG.trans_sub(ℒᵗʳᵃⁿˢ, a)
ℳᶠʳᵒᵐ = EMG.modes_sub(ℒᶠʳᵒᵐ, p)
ℳᵗᵒ = EMG.modes_sub(ℒᵗᵒ, p)
if !isempty(ℳᶠʳᵒᵐ)
@constraint(m, [t ∈ 𝒯],
m[:energy_potential_node_out][availability_node(a), t, p] ==
sum(m[:energy_potential_trans_in][tm, t, p] for tm ∈ ℳᶠʳᵒᵐ)
)
end
if !isempty(ℳᵗᵒ)
@constraint(m, [t ∈ 𝒯],
m[:energy_potential_node_in][availability_node(a), t, p] ==
sum(m[:energy_potential_trans_out][tm, t, p] for tm ∈ ℳᵗᵒ)
)
end
end
end5. What this gives you
- Bounded resource-specific transmission variables.
- Explicit coupling between area and transmission representation.
- Mode-specific transformations (here: potential loss factor) without changing core code.
Other useful applications
The same extension pattern is useful whenever transport quality matters, not only quantity.
- District heating networks: track temperature state (supply/return quality) and enforce temperature-dependent delivery constraints.
- Natural gas networks: track pressure-related transport limits and represent gas mixtures (e.g., hydrogen blending constraints across corridors).
- Any carrier with quality degradation: track concentration, purity, or state-of-charge style attributes with resource-specific balance equations.