From 2f62de05633a5a208e23b863c095c80ca34d448e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Fri, 14 Mar 2025 14:09:58 +0100 Subject: [PATCH 01/17] Introduced new type of resource that allow for energy potential transfer - CompoundResource is a super type for ResourcePotential - Any CompoundResource creates new varaibles potential_in and potential_out on nodes and links. - In junctions the pontentials are transferred by equality rather than summation (as for flow_in, link_in etc.) - This allows for transfers of voltages and pressures, along with a quantity (e.g. energy or material flows) --- src/constraint_functions.jl | 8 +++++++ src/model.jl | 43 +++++++++++++++++++++++++++++++++++++ src/structures/resource.jl | 23 ++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/src/constraint_functions.jl b/src/constraint_functions.jl index a7a6e872..6acfb41d 100644 --- a/src/constraint_functions.jl +++ b/src/constraint_functions.jl @@ -184,6 +184,14 @@ function constraints_flow_out(m, n::Storage, ๐’ฏ::TimeStructure, modeltype::Ene ) end +""" + constraints_potential(m, n::Node, ๐’ฏ::TimeStructure, modeltype::EnergyModel) + +Function for creating the constraint on the potential at a generic `Node`. +This function serves as fallback option if no other function is specified for a `Node`. +""" +function constraints_potential(m, n::Node, ๐’ฏ::TimeStructure, modeltype::EnergyModel)end + """ constraints_level(m, n::Storage, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) diff --git a/src/model.jl b/src/model.jl index e5701bc5..e6c3dc5c 100644 --- a/src/model.jl +++ b/src/model.jl @@ -238,6 +238,10 @@ function variables_flow(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype: @variable(m, flow_in[n_in โˆˆ ๐’ฉโฑโฟ, ๐’ฏ, inputs(n_in)]) @variable(m, flow_out[n_out โˆˆ ๐’ฉแต’แต˜แต—, ๐’ฏ, outputs(n_out)]) + # Create the node potential variables + @variable(m, potential_in[n_in โˆˆ ๐’ฉโฑโฟ, ๐’ฏ, res_sub(inputs(n), CompundResource)] โ‰ฅ 0) + @variable(m, potential_out[n_out โˆˆ ๐’ฉแต’แต˜แต—, ๐’ฏ, res_sub(outputs(n), CompundResource) โ‰ฅ 0]) + # Set the bounds for unidirectional nodes ๐’ฉโฑโฟโปแต˜โฟโฑ = filter(is_unidirectional, ๐’ฉโฑโฟ) ๐’ฉแต’แต˜แต—โปแต˜โฟโฑ = filter(is_unidirectional, ๐’ฉแต’แต˜แต—) @@ -254,6 +258,10 @@ function variables_flow(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype:: @variable(m, link_in[l โˆˆ โ„’, ๐’ฏ, inputs(l)]) @variable(m, link_out[l โˆˆ โ„’, ๐’ฏ, outputs(l)]) + # Create the node potential variables + @variable(m, potential_in[l โˆˆ โ„’, ๐’ฏ, res_sub(inputs(l), CompundResource)] โ‰ฅ 0) + @variable(m, potential_out[l โˆˆ โ„’, ๐’ฏ, res_sub(outputs(l), CompundResource) โ‰ฅ 0]) + # Set the bounds for unidirectional links โ„’แต˜โฟโฑ = filter(is_unidirectional, โ„’) @@ -522,6 +530,10 @@ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, m[:flow_out][n, t, p] == sum(m[:link_in][l, t, p] for l โˆˆ โ„’แถ สณแต’แต if p โˆˆ outputs(l)) ) + # Set the potential for incoming resources with potential + @constraint(m, [t โˆˆ ๐’ฏ, l โˆˆ โ„’แถ สณแต’แต, p โˆˆ res_sub(outputs(n), CompoundResource)], + m[:potential_out][n, t, p] == m[:potential_in][l, t, p] + ) end # Constraint for input flowrate and output links. if has_input(n) @@ -529,6 +541,10 @@ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, m[:flow_in][n, t, p] == sum(m[:link_out][l, t, p] for l โˆˆ โ„’แต—แต’ if p โˆˆ inputs(l)) ) + # Set the potential for outgoing resources with potential + @constraint(m, [t โˆˆ ๐’ฏ, l โˆˆ โ„’แต—แต’, p โˆˆ res_sub(inputs(n), CompoundResource)], + m[:potential_in][n, t, p] == m[:potential_out][l, t, p] + ) end end end @@ -719,6 +735,9 @@ function create_node(m, n::Source, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) # Call of the function for the outlet flow from the `Source` node constraints_flow_out(m, n, ๐’ฏ, modeltype) + # Call of the function for the potential of the `Source` node. + constraints_potential(m, n, ๐’ฏ, modeltype) + # Call of the function for limiting the capacity to the maximum installed capacity constraints_capacity(m, n, ๐’ฏ, modeltype) @@ -755,6 +774,9 @@ function create_node(m, n::NetworkNode, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) constraints_flow_in(m, n, ๐’ฏ, modeltype) constraints_flow_out(m, n, ๐’ฏ, modeltype) + # Call of the function for the potential of the `Network` node. + constraints_potential(m, n, ๐’ฏ, modeltype) + # Call of the function for limiting the capacity to the maximum installed capacity constraints_capacity(m, n, ๐’ฏ, modeltype) @@ -795,6 +817,9 @@ function create_node(m, n::Storage, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) constraints_flow_in(m, n, ๐’ฏ, modeltype) constraints_flow_out(m, n, ๐’ฏ, modeltype) + # Call of the function for the potential of the `Storage` node. + constraints_potential(m, n, ๐’ฏ, modeltype) + # Call of the function for limiting the capacity to the maximum installed capacity constraints_capacity(m, n, ๐’ฏ, modeltype) @@ -829,6 +854,9 @@ function create_node(m, n::Sink, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) # Call of the function for the inlet flow to the `Sink` node constraints_flow_in(m, n, ๐’ฏ, modeltype) + # Call of the function for the potential of the `Sink` node. + constraints_potential(m, n, ๐’ฏ, modeltype) + # Call of the function for limiting the capacity to the maximum installed capacity constraints_capacity(m, n, ๐’ฏ, modeltype) @@ -853,6 +881,11 @@ function create_node(m, n::Availability, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ inputs(n)], m[:flow_in][n, t, p] == m[:flow_out][n, t, p] ) + + # Potential balance constraints for an availability node. + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ res_sub(inputs(n), CompoundResource)], + m[:potential_in][n, t, p] == m[:potential_out][n, t, p] + ) end """ @@ -880,6 +913,11 @@ function create_link(m, l::Direct, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], m[:link_out][l, t, p] == m[:link_in][l, t, p] ) + + # Potential balance constraints for an availability node. + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ res_sub(link_res(l), CompoundResource)], + m[:potential_in][l, t, p] == m[:potential_out][l, t, p] + ) end function create_link(m, ๐’ฏ, ๐’ซ, l::Link, modeltype::EnergyModel, formulation::Formulation) @@ -888,6 +926,11 @@ function create_link(m, ๐’ฏ, ๐’ซ, l::Link, modeltype::EnergyModel, formulation m[:link_out][l, t, p] == m[:link_in][l, t, p] ) + # Potential balance constraints for an availability node. + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ res_sub(link_res(l), CompoundResource)], + m[:potential_in][l, t, p] == m[:potential_out][l, t, p] + ) + # Call of the function for limiting the capacity to the maximum installed capacity if has_capacity(l) constraints_capacity_installed(m, l, ๐’ฏ, modeltype) diff --git a/src/structures/resource.jl b/src/structures/resource.jl index 7e73f508..a56c1a3b 100644 --- a/src/structures/resource.jl +++ b/src/structures/resource.jl @@ -4,6 +4,14 @@ General resource supertype to be used for the declaration of subtypes. abstract type Resource end Base.show(io::IO, r::Resource) = print(io, "$(r.id)") +""" +Compund resources that have a potential in addition to a flow rate, +these potential behave differently when summarized in a junction. +E.q. electric power which consist of voltage (potential) and power/current (flow rate), + or gas which have both pressure (potential) and gas flow (flow rate). +""" +abstract type CompoundResource <: Resource end + """ ResourceEmit{T<:Real} <: Resource @@ -37,6 +45,21 @@ struct ResourceCarrier{T<:Real} <: Resource co2_int::T end +""" + ResourcePotential{T<:Real} <: CompundResource + +Resources that can be transported and converted, but also has a energy potential. + +# Fields +- **`id`** is the name/identifyer of the resource. +- **`co2_int::T`** is the COโ‚‚ intensity, *e.g.*, t/MWh. +""" +struct ResourcePotential{T<:Real} <: CompoundResource + id::Any + co2_int::T + potential_id::Any +end + """ co2_int(p::Resource) From 8524f628872575af396b8324a34f5049778461a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Wed, 19 Mar 2025 09:56:56 +0100 Subject: [PATCH 02/17] Added functions for new flow variables and constraints - added function `variables_flow_resource` to create new variables by dispatching on resource types, it is called inside `variables_flow` - added function `constraints_link_resource` to create new constraints by dispatching on resource types, it is called inside `create_link` - added function `res_types` to extract an array of unique resource types from an array of resources --- src/model.jl | 71 ++++++++++++++++++++++++++------------ src/structures/resource.jl | 7 ++++ 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/model.jl b/src/model.jl index e6c3dc5c..ba4b5b21 100644 --- a/src/model.jl +++ b/src/model.jl @@ -60,7 +60,7 @@ function create_model( # Declaration of element variables and constraints of the problem for ๐’ณ โˆˆ ๐’ณแต›แต‰แถœ variables_capacity(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype) - variables_flow(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype) + variables_flow(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype) variables_opex(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype) variables_capex(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype) variables_emission(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype) @@ -229,7 +229,7 @@ By default, all nodes `๐’ฉ` and links `โ„’` only allow for unidirectional flow. bidirectional flow through providing a method to the function [`is_unidirectional`](@ref) for new link/node types. """ -function variables_flow(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype::EnergyModel) +function variables_flow(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) # Extract the nodes with inputs and outputs ๐’ฉโฑโฟ = filter(has_input, ๐’ฉ) ๐’ฉแต’แต˜แต— = filter(has_output, ๐’ฉ) @@ -238,10 +238,6 @@ function variables_flow(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype: @variable(m, flow_in[n_in โˆˆ ๐’ฉโฑโฟ, ๐’ฏ, inputs(n_in)]) @variable(m, flow_out[n_out โˆˆ ๐’ฉแต’แต˜แต—, ๐’ฏ, outputs(n_out)]) - # Create the node potential variables - @variable(m, potential_in[n_in โˆˆ ๐’ฉโฑโฟ, ๐’ฏ, res_sub(inputs(n), CompundResource)] โ‰ฅ 0) - @variable(m, potential_out[n_out โˆˆ ๐’ฉแต’แต˜แต—, ๐’ฏ, res_sub(outputs(n), CompundResource) โ‰ฅ 0]) - # Set the bounds for unidirectional nodes ๐’ฉโฑโฟโปแต˜โฟโฑ = filter(is_unidirectional, ๐’ฉโฑโฟ) ๐’ฉแต’แต˜แต—โปแต˜โฟโฑ = filter(is_unidirectional, ๐’ฉแต’แต˜แต—) @@ -252,16 +248,18 @@ function variables_flow(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype: for n_out โˆˆ ๐’ฉแต’แต˜แต—โปแต˜โฟโฑ, t โˆˆ ๐’ฏ, p โˆˆ outputs(n_out) set_lower_bound(m[:flow_out][n_out, t, p], 0) end + + # Create new flow variables for specific resource types + for rt in res_types(๐’ซ) + variables_flow_resource(m, ๐’ฉ, rt, ๐’ฏ, modeltype) + end + end -function variables_flow(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype::EnergyModel) +function variables_flow(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) # Create the link flow variables @variable(m, link_in[l โˆˆ โ„’, ๐’ฏ, inputs(l)]) @variable(m, link_out[l โˆˆ โ„’, ๐’ฏ, outputs(l)]) - - # Create the node potential variables - @variable(m, potential_in[l โˆˆ โ„’, ๐’ฏ, res_sub(inputs(l), CompundResource)] โ‰ฅ 0) - @variable(m, potential_out[l โˆˆ โ„’, ๐’ฏ, res_sub(outputs(l), CompundResource) โ‰ฅ 0]) - + # Set the bounds for unidirectional links โ„’แต˜โฟโฑ = filter(is_unidirectional, โ„’) @@ -273,8 +271,24 @@ function variables_flow(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype:: set_lower_bound(m[:link_out][l, t, p], 0) end end + + # Create new flow variables for specific resource types + for rt in res_types(๐’ซ) + variables_flow_resource(m, โ„’, rt, ๐’ฏ, modeltype) + end end +""" + variables_flow_resource(m, โ„’::Vector{<:Link}, rt::Type{Resource}, ๐’ฏ, modeltype::EnergyModel) + +Declaration of flow variables for the differrent resource types. + +The default method is empty but it is required for multiple dispatch in energy flow models. +""" +function variables_flow_resource(m, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end +function variables_flow_resource(m, ๐’ฉ::Vector{<:Node}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end + + """ variables_opex(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype::EnergyModel) variables_opex(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype::EnergyModel) @@ -914,25 +928,36 @@ function create_link(m, l::Direct, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) m[:link_out][l, t, p] == m[:link_in][l, t, p] ) - # Potential balance constraints for an availability node. - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ res_sub(link_res(l), CompoundResource)], - m[:potential_in][l, t, p] == m[:potential_out][l, t, p] - ) + # Add flow constraints of specific resources on links. + for rt in res_types(๐’ซ) + constraints_link_resource(m, l, ๐’ฏ, rt, modeltype) + end end -function create_link(m, ๐’ฏ, ๐’ซ, l::Link, modeltype::EnergyModel, formulation::Formulation) +function create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel, formulation::Formulation) # Generic link in which each output corresponds to the input @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], m[:link_out][l, t, p] == m[:link_in][l, t, p] - ) - - # Potential balance constraints for an availability node. - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ res_sub(link_res(l), CompoundResource)], - m[:potential_in][l, t, p] == m[:potential_out][l, t, p] - ) + ) # Call of the function for limiting the capacity to the maximum installed capacity if has_capacity(l) constraints_capacity_installed(m, l, ๐’ฏ, modeltype) end + + # Add flow constraints of specific resources on links. + for rt in res_types(๐’ซ) + constraints_link_resource(m, l, ๐’ฏ, rt, modeltype, formulation) + end end + +""" + constraints_link__resource(m, l::Direct, rt::Type{Resource}, ๐’ฏ, modeltype::EnergyModel) + constraints_link__resource(m, l::Link, rt::Type{Resource}, ๐’ฏ, modeltype::EnergyModel) + +Declaration of flow constraints for the differrent resource types. + +The default method is empty but it is required for multiple dispatch in energy flow models. +""" +function constraints_link_resource(m, l::Direct, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel) end +function constraints_link_resource(m, l::Link, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel, formulation::Formulation) end \ No newline at end of file diff --git a/src/structures/resource.jl b/src/structures/resource.jl index a56c1a3b..5aa84908 100644 --- a/src/structures/resource.jl +++ b/src/structures/resource.jl @@ -107,3 +107,10 @@ Returns all emission resources for a """ res_em(๐’ซ::Array{<:Resource}) = filter(is_resource_emit, ๐’ซ) res_em(๐’ซ::Dict) = filter(p -> is_resource_emit(first(p)), ๐’ซ) + +""" + res_types(โ„’::Array{<:Link}) + +Return the unique resource types transported for a Array of resources `๐’ซ`. +""" +res_types(๐’ซ::Array{<:Resource}) = unique([typeof(p) for p in ๐’ซ]) \ No newline at end of file From 2d76031c46788ea11801b58ff240dd552619f4ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Wed, 19 Mar 2025 16:29:31 +0100 Subject: [PATCH 03/17] Removed added resource type function on node-link coupling - Added function for resouce type constratints on node-link coupling that dispatch on resouce type - Removed old functions on potential variables and constratints --- src/constraint_functions.jl | 8 ----- src/model.jl | 68 +++++++++++++++++++++---------------- src/structures/resource.jl | 23 ------------- 3 files changed, 38 insertions(+), 61 deletions(-) diff --git a/src/constraint_functions.jl b/src/constraint_functions.jl index 6acfb41d..a7a6e872 100644 --- a/src/constraint_functions.jl +++ b/src/constraint_functions.jl @@ -184,14 +184,6 @@ function constraints_flow_out(m, n::Storage, ๐’ฏ::TimeStructure, modeltype::Ene ) end -""" - constraints_potential(m, n::Node, ๐’ฏ::TimeStructure, modeltype::EnergyModel) - -Function for creating the constraint on the potential at a generic `Node`. -This function serves as fallback option if no other function is specified for a `Node`. -""" -function constraints_potential(m, n::Node, ๐’ฏ::TimeStructure, modeltype::EnergyModel)end - """ constraints_level(m, n::Storage, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) diff --git a/src/model.jl b/src/model.jl index ba4b5b21..26adb602 100644 --- a/src/model.jl +++ b/src/model.jl @@ -544,10 +544,10 @@ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, m[:flow_out][n, t, p] == sum(m[:link_in][l, t, p] for l โˆˆ โ„’แถ สณแต’แต if p โˆˆ outputs(l)) ) - # Set the potential for incoming resources with potential - @constraint(m, [t โˆˆ ๐’ฏ, l โˆˆ โ„’แถ สณแต’แต, p โˆˆ res_sub(outputs(n), CompoundResource)], - m[:potential_out][n, t, p] == m[:potential_in][l, t, p] - ) + # Set constraints incoming resources types + for rt in res_types(๐’ซ) + constraints_couple_resource_from(m, n, โ„’แถ สณแต’แต, rt, ๐’ฏ, modeltype) + end end # Constraint for input flowrate and output links. if has_input(n) @@ -555,10 +555,10 @@ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, m[:flow_in][n, t, p] == sum(m[:link_out][l, t, p] for l โˆˆ โ„’แต—แต’ if p โˆˆ inputs(l)) ) - # Set the potential for outgoing resources with potential - @constraint(m, [t โˆˆ ๐’ฏ, l โˆˆ โ„’แต—แต’, p โˆˆ res_sub(inputs(n), CompoundResource)], - m[:potential_in][n, t, p] == m[:potential_out][l, t, p] - ) + # Set constraints for outgoing resource types + for rt in res_types(๐’ซ) + constraints_couple_resource_to(m, n, โ„’แต—แต’, rt, ๐’ฏ, modeltype) + end end end end @@ -566,6 +566,17 @@ function constraints_couple(m, โ„’::Vector{<:Link}, ๐’ฉ::Vector{<:Node}, ๐’ซ, return constraints_couple(m, ๐’ฉ, โ„’, ๐’ซ, ๐’ฏ, modeltype) end +""" + constraints_couple_resource_from(m, n::Node, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + constraints_couple_resource_to(m, n::Node, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + +Declaration of link flow constraints for the differrent resource types. + +The default method is empty but it is required for multiple dispatch in energy flow models. +""" +function constraints_couple_resource_from(m, n::Node, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end +function constraints_couple_resource_to(m, n::Node, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end + """ constraints_emissions(m, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) @@ -749,9 +760,6 @@ function create_node(m, n::Source, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) # Call of the function for the outlet flow from the `Source` node constraints_flow_out(m, n, ๐’ฏ, modeltype) - # Call of the function for the potential of the `Source` node. - constraints_potential(m, n, ๐’ฏ, modeltype) - # Call of the function for limiting the capacity to the maximum installed capacity constraints_capacity(m, n, ๐’ฏ, modeltype) @@ -788,9 +796,6 @@ function create_node(m, n::NetworkNode, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) constraints_flow_in(m, n, ๐’ฏ, modeltype) constraints_flow_out(m, n, ๐’ฏ, modeltype) - # Call of the function for the potential of the `Network` node. - constraints_potential(m, n, ๐’ฏ, modeltype) - # Call of the function for limiting the capacity to the maximum installed capacity constraints_capacity(m, n, ๐’ฏ, modeltype) @@ -831,9 +836,6 @@ function create_node(m, n::Storage, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) constraints_flow_in(m, n, ๐’ฏ, modeltype) constraints_flow_out(m, n, ๐’ฏ, modeltype) - # Call of the function for the potential of the `Storage` node. - constraints_potential(m, n, ๐’ฏ, modeltype) - # Call of the function for limiting the capacity to the maximum installed capacity constraints_capacity(m, n, ๐’ฏ, modeltype) @@ -868,9 +870,6 @@ function create_node(m, n::Sink, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) # Call of the function for the inlet flow to the `Sink` node constraints_flow_in(m, n, ๐’ฏ, modeltype) - # Call of the function for the potential of the `Sink` node. - constraints_potential(m, n, ๐’ฏ, modeltype) - # Call of the function for limiting the capacity to the maximum installed capacity constraints_capacity(m, n, ๐’ฏ, modeltype) @@ -896,12 +895,21 @@ function create_node(m, n::Availability, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) m[:flow_in][n, t, p] == m[:flow_out][n, t, p] ) - # Potential balance constraints for an availability node. - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ res_sub(inputs(n), CompoundResource)], - m[:potential_in][n, t, p] == m[:potential_out][n, t, p] - ) + # Add node flow constraints for specific resource types. + for rt in res_types(๐’ซ) + constraints_node_resource(m, n, ๐’ฏ, rt, modeltype) + end end +""" + constraints_node_resource(m, n::Availability, ๐’ฏ, rt::Type{Resource}, modeltype::EnergyModel) + +Declaration of node flow constraints for the differrent resource types. + +The default method is empty but it is required for multiple dispatch in energy flow models. +""" +function constraints_node_resource(m, n::Availability, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel) end + """ create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) create_link(m, l::Direct, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) @@ -928,7 +936,7 @@ function create_link(m, l::Direct, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) m[:link_out][l, t, p] == m[:link_in][l, t, p] ) - # Add flow constraints of specific resources on links. + # Add link flow constraints for specific resource types. for rt in res_types(๐’ซ) constraints_link_resource(m, l, ๐’ฏ, rt, modeltype) end @@ -938,24 +946,24 @@ function create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel, formulation # Generic link in which each output corresponds to the input @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], m[:link_out][l, t, p] == m[:link_in][l, t, p] - ) + ) # Call of the function for limiting the capacity to the maximum installed capacity if has_capacity(l) constraints_capacity_installed(m, l, ๐’ฏ, modeltype) end - # Add flow constraints of specific resources on links. + # Add link flow constraints for specific resource types. for rt in res_types(๐’ซ) constraints_link_resource(m, l, ๐’ฏ, rt, modeltype, formulation) end end """ - constraints_link__resource(m, l::Direct, rt::Type{Resource}, ๐’ฏ, modeltype::EnergyModel) - constraints_link__resource(m, l::Link, rt::Type{Resource}, ๐’ฏ, modeltype::EnergyModel) + constraints_link_resource(m, l::Direct, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel) + constraints_link_resource(m, l::Link, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel, formulation::Formulation) -Declaration of flow constraints for the differrent resource types. +Declaration of link flow constraints for the differrent resource types. The default method is empty but it is required for multiple dispatch in energy flow models. """ diff --git a/src/structures/resource.jl b/src/structures/resource.jl index 5aa84908..6d5f7924 100644 --- a/src/structures/resource.jl +++ b/src/structures/resource.jl @@ -4,14 +4,6 @@ General resource supertype to be used for the declaration of subtypes. abstract type Resource end Base.show(io::IO, r::Resource) = print(io, "$(r.id)") -""" -Compund resources that have a potential in addition to a flow rate, -these potential behave differently when summarized in a junction. -E.q. electric power which consist of voltage (potential) and power/current (flow rate), - or gas which have both pressure (potential) and gas flow (flow rate). -""" -abstract type CompoundResource <: Resource end - """ ResourceEmit{T<:Real} <: Resource @@ -45,21 +37,6 @@ struct ResourceCarrier{T<:Real} <: Resource co2_int::T end -""" - ResourcePotential{T<:Real} <: CompundResource - -Resources that can be transported and converted, but also has a energy potential. - -# Fields -- **`id`** is the name/identifyer of the resource. -- **`co2_int::T`** is the COโ‚‚ intensity, *e.g.*, t/MWh. -""" -struct ResourcePotential{T<:Real} <: CompoundResource - id::Any - co2_int::T - potential_id::Any -end - """ co2_int(p::Resource) From 01cdf0c05d16a93fd485a9f4f514fca8e1bfbd96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Wed, 19 Mar 2025 16:39:27 +0100 Subject: [PATCH 04/17] Small changes to comments and documentation --- src/model.jl | 6 +++--- src/structures/resource.jl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/model.jl b/src/model.jl index 26adb602..88f28039 100644 --- a/src/model.jl +++ b/src/model.jl @@ -259,7 +259,7 @@ function variables_flow(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, model # Create the link flow variables @variable(m, link_in[l โˆˆ โ„’, ๐’ฏ, inputs(l)]) @variable(m, link_out[l โˆˆ โ„’, ๐’ฏ, outputs(l)]) - + # Set the bounds for unidirectional links โ„’แต˜โฟโฑ = filter(is_unidirectional, โ„’) @@ -544,7 +544,7 @@ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, m[:flow_out][n, t, p] == sum(m[:link_in][l, t, p] for l โˆˆ โ„’แถ สณแต’แต if p โˆˆ outputs(l)) ) - # Set constraints incoming resources types + # Additional constraints based on resources types for rt in res_types(๐’ซ) constraints_couple_resource_from(m, n, โ„’แถ สณแต’แต, rt, ๐’ฏ, modeltype) end @@ -555,7 +555,7 @@ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, m[:flow_in][n, t, p] == sum(m[:link_out][l, t, p] for l โˆˆ โ„’แต—แต’ if p โˆˆ inputs(l)) ) - # Set constraints for outgoing resource types + # Additional constraints based on resource types for rt in res_types(๐’ซ) constraints_couple_resource_to(m, n, โ„’แต—แต’, rt, ๐’ฏ, modeltype) end diff --git a/src/structures/resource.jl b/src/structures/resource.jl index 6d5f7924..43f7fb63 100644 --- a/src/structures/resource.jl +++ b/src/structures/resource.jl @@ -86,8 +86,8 @@ res_em(๐’ซ::Array{<:Resource}) = filter(is_resource_emit, ๐’ซ) res_em(๐’ซ::Dict) = filter(p -> is_resource_emit(first(p)), ๐’ซ) """ - res_types(โ„’::Array{<:Link}) + res_types(๐’ซ::Array{<:Resource}) -Return the unique resource types transported for a Array of resources `๐’ซ`. +Return the unique resource types in an Array of resources `๐’ซ`. """ res_types(๐’ซ::Array{<:Resource}) = unique([typeof(p) for p in ๐’ซ]) \ No newline at end of file From 7a82100e171a9bd4771fa0a3e091dc6aa8e1e185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Thu, 20 Mar 2025 16:41:20 +0100 Subject: [PATCH 05/17] Changed to a design that mostly dispatches original functions - Resource vector is segmented into sub-vectors based on resource type - Constraint functions for flows can be dispatched on these sub-vectors - New function are created to say if EMB flow-variables should be added, which can be dispatched on resource type, default is true - Function is available for adding new variables that are specific for a resource type --- src/model.jl | 124 +++++++++++-------------------------- src/structures/resource.jl | 23 ++++++- 2 files changed, 58 insertions(+), 89 deletions(-) diff --git a/src/model.jl b/src/model.jl index 88f28039..bf316335 100644 --- a/src/model.jl +++ b/src/model.jl @@ -57,6 +57,9 @@ function create_model( ๐’ณแต›แต‰แถœ = get_elements_vec(case) ๐’ณ_๐’ณ = get_couplings(case) + # Segment array of products by sub-type + ๐’ซหขแต˜แต‡ = res_types_seg(๐’ซ) + # Declaration of element variables and constraints of the problem for ๐’ณ โˆˆ ๐’ณแต›แต‰แถœ variables_capacity(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype) @@ -66,13 +69,17 @@ function create_model( variables_emission(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype) variables_elements(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype) - constraints_elements(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype) + for p_sub in ๐’ซหขแต˜แต‡ + constraints_elements(m, ๐’ณ, ๐’ณแต›แต‰แถœ, p_sub, ๐’ฏ, modeltype) + end end # Declaration of coupling constraints of the problem for couple โˆˆ ๐’ณ_๐’ณ elements_vec = [cpl(case) for cpl โˆˆ couple] - constraints_couple(m, elements_vec..., ๐’ซ, ๐’ฏ, modeltype) + for p_sub in ๐’ซหขแต˜แต‡ + constraints_couple(m, elements_vec..., p_sub, ๐’ฏ, modeltype) + end end # Declaration of global vairables and constraints @@ -235,46 +242,46 @@ function variables_flow(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, mode ๐’ฉแต’แต˜แต— = filter(has_output, ๐’ฉ) # Create the node flow variables - @variable(m, flow_in[n_in โˆˆ ๐’ฉโฑโฟ, ๐’ฏ, inputs(n_in)]) - @variable(m, flow_out[n_out โˆˆ ๐’ฉแต’แต˜แต—, ๐’ฏ, outputs(n_out)]) + @variable(m, flow_in[n_in โˆˆ ๐’ฉโฑโฟ, ๐’ฏ, res_flow(inputs(n_in))]) + @variable(m, flow_out[n_out โˆˆ ๐’ฉแต’แต˜แต—, ๐’ฏ, res_flow(outputs(n_out))]) # Set the bounds for unidirectional nodes ๐’ฉโฑโฟโปแต˜โฟโฑ = filter(is_unidirectional, ๐’ฉโฑโฟ) ๐’ฉแต’แต˜แต—โปแต˜โฟโฑ = filter(is_unidirectional, ๐’ฉแต’แต˜แต—) - for n_in โˆˆ ๐’ฉโฑโฟโปแต˜โฟโฑ, t โˆˆ ๐’ฏ, p โˆˆ inputs(n_in) + for n_in โˆˆ ๐’ฉโฑโฟโปแต˜โฟโฑ, t โˆˆ ๐’ฏ, p โˆˆ res_flow(inputs(n_in)) set_lower_bound(m[:flow_in][n_in, t, p], 0) end - for n_out โˆˆ ๐’ฉแต’แต˜แต—โปแต˜โฟโฑ, t โˆˆ ๐’ฏ, p โˆˆ outputs(n_out) + for n_out โˆˆ ๐’ฉแต’แต˜แต—โปแต˜โฟโฑ, t โˆˆ ๐’ฏ, p โˆˆ res_flow(outputs(n_out)) set_lower_bound(m[:flow_out][n_out, t, p], 0) end # Create new flow variables for specific resource types - for rt in res_types(๐’ซ) - variables_flow_resource(m, ๐’ฉ, rt, ๐’ฏ, modeltype) + for p_sub in res_types_seg(๐’ซ) + variables_flow_resource(m, ๐’ฉ, p_sub, ๐’ฏ, modeltype) end end function variables_flow(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) # Create the link flow variables - @variable(m, link_in[l โˆˆ โ„’, ๐’ฏ, inputs(l)]) - @variable(m, link_out[l โˆˆ โ„’, ๐’ฏ, outputs(l)]) + @variable(m, link_in[l โˆˆ โ„’, ๐’ฏ, res_flow(inputs(l))]) + @variable(m, link_out[l โˆˆ โ„’, ๐’ฏ, res_flow(outputs(l))]) # Set the bounds for unidirectional links โ„’แต˜โฟโฑ = filter(is_unidirectional, โ„’) for l โˆˆ โ„’แต˜โฟโฑ, t โˆˆ ๐’ฏ - for p โˆˆ inputs(l) + for p โˆˆ res_flow(inputs(l)) set_lower_bound(m[:link_in][l, t, p], 0) end - for p โˆˆ outputs(l) + for p โˆˆ res_flow(outputs(l)) set_lower_bound(m[:link_out][l, t, p], 0) end end # Create new flow variables for specific resource types - for rt in res_types(๐’ซ) - variables_flow_resource(m, โ„’, rt, ๐’ฏ, modeltype) + for p_sub in res_types_seg(๐’ซ) + variables_flow_resource(m, โ„’, p_sub, ๐’ฏ, modeltype) end end @@ -285,8 +292,8 @@ Declaration of flow variables for the differrent resource types. The default method is empty but it is required for multiple dispatch in energy flow models. """ -function variables_flow_resource(m, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end -function variables_flow_resource(m, ๐’ฉ::Vector{<:Node}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end +function variables_flow_resource(m, โ„’::Vector{<:Link}, rt::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end +function variables_flow_resource(m, ๐’ฉ::Vector{<:Node}, rt::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end """ @@ -526,8 +533,8 @@ create_element(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) = create_link(m, l, ๐’ฏ, ๐’ซ, modeltype) """ - constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) - constraints_couple(m, โ„’::Vector{<:Link}, ๐’ฉ::Vector{<:Node}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) + constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + constraints_couple(m, โ„’::Vector{<:Link}, ๐’ฉ::Vector{<:Node}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) Create the couple constraints in `EnergyModelsBase`. @@ -535,30 +542,22 @@ Only couplings between two types are introducded in energy models base. A fallba is available for the coupling between [`AbstractElement`](@ref)s while a method is implemented for the coupling between a [`Link`](@ref) and a [`Node`](@ref EnergyModelsBase.Node). """ -function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) +function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) for n โˆˆ ๐’ฉ โ„’แถ สณแต’แต, โ„’แต—แต’ = link_sub(โ„’, n) # Constraint for output flowrate and input links. if has_output(n) - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ outputs(n)], + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(outputs(n), ๐’ซ)], m[:flow_out][n, t, p] == sum(m[:link_in][l, t, p] for l โˆˆ โ„’แถ สณแต’แต if p โˆˆ outputs(l)) ) - # Additional constraints based on resources types - for rt in res_types(๐’ซ) - constraints_couple_resource_from(m, n, โ„’แถ สณแต’แต, rt, ๐’ฏ, modeltype) - end end # Constraint for input flowrate and output links. if has_input(n) - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ inputs(n)], + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(inputs(n), ๐’ซ)], m[:flow_in][n, t, p] == sum(m[:link_out][l, t, p] for l โˆˆ โ„’แต—แต’ if p โˆˆ inputs(l)) ) - # Additional constraints based on resource types - for rt in res_types(๐’ซ) - constraints_couple_resource_to(m, n, โ„’แต—แต’, rt, ๐’ฏ, modeltype) - end end end end @@ -566,17 +565,6 @@ function constraints_couple(m, โ„’::Vector{<:Link}, ๐’ฉ::Vector{<:Node}, ๐’ซ, return constraints_couple(m, ๐’ฉ, โ„’, ๐’ซ, ๐’ฏ, modeltype) end -""" - constraints_couple_resource_from(m, n::Node, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) - constraints_couple_resource_to(m, n::Node, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) - -Declaration of link flow constraints for the differrent resource types. - -The default method is empty but it is required for multiple dispatch in energy flow models. -""" -function constraints_couple_resource_from(m, n::Node, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end -function constraints_couple_resource_to(m, n::Node, โ„’::Vector{<:Link}, rt::Type{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end - """ constraints_emissions(m, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) @@ -879,7 +867,7 @@ function create_node(m, n::Sink, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) end """ - create_node(m, n::Availability, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) + create_node(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) Set all constraints for a `Availability`. Can serve as fallback option for all unspecified subtypes of `Availability`. @@ -888,28 +876,14 @@ subtypes of `Availability`. available node except if one wants to include as well transport between different `Availability` nodes with associated costs (not implemented at the moment). """ -function create_node(m, n::Availability, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) +function create_node(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) # Mass/energy balance constraints for an availability node. - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ inputs(n)], + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(inputs(n), ๐’ซ)], m[:flow_in][n, t, p] == m[:flow_out][n, t, p] ) - - # Add node flow constraints for specific resource types. - for rt in res_types(๐’ซ) - constraints_node_resource(m, n, ๐’ฏ, rt, modeltype) - end end -""" - constraints_node_resource(m, n::Availability, ๐’ฏ, rt::Type{Resource}, modeltype::EnergyModel) - -Declaration of node flow constraints for the differrent resource types. - -The default method is empty but it is required for multiple dispatch in energy flow models. -""" -function constraints_node_resource(m, n::Availability, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel) end - """ create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) create_link(m, l::Direct, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) @@ -930,42 +904,16 @@ function create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) ) return create_link(m, ๐’ฏ, ๐’ซ, l, modeltype, formulation(l)) end -function create_link(m, l::Direct, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) +function create_link(m, l::Direct, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) # Generic link in which each output corresponds to the input - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(link_res(l), ๐’ซ)], m[:link_out][l, t, p] == m[:link_in][l, t, p] ) - - # Add link flow constraints for specific resource types. - for rt in res_types(๐’ซ) - constraints_link_resource(m, l, ๐’ฏ, rt, modeltype) - end end -function create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel, formulation::Formulation) +function create_link(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel, formulation::Formulation) # Generic link in which each output corresponds to the input - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ itersect(link_res(l), ๐’ซ)], m[:link_out][l, t, p] == m[:link_in][l, t, p] ) - - # Call of the function for limiting the capacity to the maximum installed capacity - if has_capacity(l) - constraints_capacity_installed(m, l, ๐’ฏ, modeltype) - end - - # Add link flow constraints for specific resource types. - for rt in res_types(๐’ซ) - constraints_link_resource(m, l, ๐’ฏ, rt, modeltype, formulation) - end -end - -""" - constraints_link_resource(m, l::Direct, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel) - constraints_link_resource(m, l::Link, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel, formulation::Formulation) - -Declaration of link flow constraints for the differrent resource types. - -The default method is empty but it is required for multiple dispatch in energy flow models. -""" -function constraints_link_resource(m, l::Direct, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel) end -function constraints_link_resource(m, l::Link, ๐’ฏ, rt::Type{<:Resource}, modeltype::EnergyModel, formulation::Formulation) end \ No newline at end of file +end \ No newline at end of file diff --git a/src/structures/resource.jl b/src/structures/resource.jl index 43f7fb63..1bba9b69 100644 --- a/src/structures/resource.jl +++ b/src/structures/resource.jl @@ -44,6 +44,20 @@ Returns the COโ‚‚ intensity of resource `p` """ co2_int(p::Resource) = p.co2_int +""" + res_flow(๐’ซ::Vector{<:Resource}) + +Filter resources with flow variables. +""" +res_flow(๐’ซ::Vector{<:Resource}) = filter(p -> add_flow_var(p), ๐’ซ) + +""" + add_flow_var(p::Resource) + +Checks whether the Resource `p` should add flow variables. +""" +add_flow_var(p::Resource) = true + """ is_resource_emit(p::Resource) @@ -90,4 +104,11 @@ res_em(๐’ซ::Dict) = filter(p -> is_resource_emit(first(p)), ๐’ซ) Return the unique resource types in an Array of resources `๐’ซ`. """ -res_types(๐’ซ::Array{<:Resource}) = unique([typeof(p) for p in ๐’ซ]) \ No newline at end of file +res_types(๐’ซ::Array{<:Resource}) = unique(map(x -> typeof(x), ๐’ซ)) + +""" + res_types_seg(๐’ซ::Array{<:Resource}) + +Return a Vector-of-Vectors of resources segmented by the sub-types. +""" +res_types_seg(๐’ซ::Array{<:Resource}) = [Vector{rt}(filter(x -> isa(x, rt), ๐’ซ)) for rt in res_types(๐’ซ)] \ No newline at end of file From 08b8f5d295a9e9dacb2c5394c658c48aaca74e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Fri, 21 Mar 2025 09:39:09 +0100 Subject: [PATCH 06/17] Resource segmentation moved down to constraint level - Avioids repeating constraints that are not dependent on resources (doesnt have resouce in the constraint index) --- src/model.jl | 93 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/src/model.jl b/src/model.jl index bf316335..00b58b02 100644 --- a/src/model.jl +++ b/src/model.jl @@ -57,9 +57,6 @@ function create_model( ๐’ณแต›แต‰แถœ = get_elements_vec(case) ๐’ณ_๐’ณ = get_couplings(case) - # Segment array of products by sub-type - ๐’ซหขแต˜แต‡ = res_types_seg(๐’ซ) - # Declaration of element variables and constraints of the problem for ๐’ณ โˆˆ ๐’ณแต›แต‰แถœ variables_capacity(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype) @@ -69,17 +66,13 @@ function create_model( variables_emission(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype) variables_elements(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype) - for p_sub in ๐’ซหขแต˜แต‡ - constraints_elements(m, ๐’ณ, ๐’ณแต›แต‰แถœ, p_sub, ๐’ฏ, modeltype) - end + constraints_elements(m, ๐’ณ, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype) end # Declaration of coupling constraints of the problem for couple โˆˆ ๐’ณ_๐’ณ elements_vec = [cpl(case) for cpl โˆˆ couple] - for p_sub in ๐’ซหขแต˜แต‡ - constraints_couple(m, elements_vec..., p_sub, ๐’ฏ, modeltype) - end + constraints_couple(m, elements_vec..., p_sub, ๐’ฏ, modeltype) end # Declaration of global vairables and constraints @@ -530,11 +523,11 @@ differentiation in extension packages. create_element(m, n::Node, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) = create_node(m, n, ๐’ฏ, ๐’ซ, modeltype) create_element(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) = - create_link(m, l, ๐’ฏ, ๐’ซ, modeltype) + create_link(m, l, ๐’ฏ, p_sub, modeltype) """ - constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) - constraints_couple(m, โ„’::Vector{<:Link}, ๐’ฉ::Vector{<:Node}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) + constraints_couple(m, โ„’::Vector{<:Link}, ๐’ฉ::Vector{<:Node}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) Create the couple constraints in `EnergyModelsBase`. @@ -542,22 +535,17 @@ Only couplings between two types are introducded in energy models base. A fallba is available for the coupling between [`AbstractElement`](@ref)s while a method is implemented for the coupling between a [`Link`](@ref) and a [`Node`](@ref EnergyModelsBase.Node). """ -function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) +function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) + ๐’ซหขแต˜แต‡ = res_types_seg(๐’ซ) for n โˆˆ ๐’ฉ โ„’แถ สณแต’แต, โ„’แต—แต’ = link_sub(โ„’, n) - # Constraint for output flowrate and input links. - if has_output(n) - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(outputs(n), ๐’ซ)], - m[:flow_out][n, t, p] == - sum(m[:link_in][l, t, p] for l โˆˆ โ„’แถ สณแต’แต if p โˆˆ outputs(l)) - ) - end - # Constraint for input flowrate and output links. - if has_input(n) - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(inputs(n), ๐’ซ)], - m[:flow_in][n, t, p] == - sum(m[:link_out][l, t, p] for l โˆˆ โ„’แต—แต’ if p โˆˆ inputs(l)) - ) + for p_sub in ๐’ซหขแต˜แต‡ + if has_output(n) + constraints_couple_from(m, n, โ„’แถ สณแต’แต, p_sub, ๐’ฏ, modeltype) + end + if has_input(n) + constraints_couple_to(m, n, โ„’แต—แต’, p_sub, ๐’ฏ, modeltype) + end end end end @@ -565,6 +553,30 @@ function constraints_couple(m, โ„’::Vector{<:Link}, ๐’ฉ::Vector{<:Node}, ๐’ซ, return constraints_couple(m, ๐’ฉ, โ„’, ๐’ซ, ๐’ฏ, modeltype) end +""" + constraints_couple_from(m, n::Node, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + +Create constraints for output flowrate and input links. +""" +function constraints_couple_from(m, n::Node, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(outputs(n), ๐’ซ)], + m[:flow_out][n, t, p] == + sum(m[:link_in][l, t, p] for l โˆˆ โ„’ if p โˆˆ outputs(l)) + ) +end + +""" + constraints_couple_to(m, n::Node, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + +Create constraints for input flowrate and output links. +""" +function constraints_couple_to(m, n::Node, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(inputs(n), ๐’ซ)], + m[:flow_in][n, t, p] == + sum(m[:link_out][l, t, p] for l โˆˆ โ„’ if p โˆˆ inputs(l)) + ) +end + """ constraints_emissions(m, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) @@ -879,8 +891,15 @@ available node except if one wants to include as well transport between differen function create_node(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) # Mass/energy balance constraints for an availability node. + for p_sub in res_types_seg(๐’ซ) + constraints_flow_balance(m, n, ๐’ฏ, p_sub, modeltype) + end +end + + +function constraints_flow_balance(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(inputs(n), ๐’ซ)], - m[:flow_in][n, t, p] == m[:flow_out][n, t, p] + m[:flow_in][n, t, p] == m[:flow_out][n, t, p] ) end @@ -905,14 +924,24 @@ function create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) return create_link(m, ๐’ฏ, ๐’ซ, l, modeltype, formulation(l)) end function create_link(m, l::Direct, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) - # Generic link in which each output corresponds to the input - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(link_res(l), ๐’ซ)], - m[:link_out][l, t, p] == m[:link_in][l, t, p] - ) + # Create flow balance on liks for each resource type + for p_sub in res_types_seg(๐’ซ) + constraints_flow_balance(m, l, ๐’ฏ, p_sub, modeltype) + end end function create_link(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel, formulation::Formulation) + # Create flow balance on liks for each resource type + for p_sub in res_types_seg(๐’ซ) + constraints_flow_balance(m, l, ๐’ฏ, p_sub, modeltype) + end +end + +""" + constraints_flow_balance(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) - # Generic link in which each output corresponds to the input +Create constraints for the resources balances on links. By default, inflow equals outflow for all resources. +""" +function constraints_flow_balance(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ itersect(link_res(l), ๐’ซ)], m[:link_out][l, t, p] == m[:link_in][l, t, p] ) From ad926cd3f0b44904185e0c1a1b4f44901f5ecbbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Wed, 9 Jul 2025 10:35:50 +0200 Subject: [PATCH 07/17] Reorganize for minimal changes to existing code --- Project.toml | 2 +- src/model.jl | 136 ++++++++++++++++++++----------------- src/structures/resource.jl | 14 ---- 3 files changed, 75 insertions(+), 77 deletions(-) diff --git a/Project.toml b/Project.toml index 0a88c7f8..63698aa8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "EnergyModelsBase" uuid = "5d7e687e-f956-46f3-9045-6f5a5fd49f50" authors = ["Lars Hellemo , Julian Straus "] -version = "0.9.0" +version = "0.9.1" [deps] JuMP = "4076af6c-e467-56ae-b986-b466b2749572" diff --git a/src/model.jl b/src/model.jl index 00b58b02..551f0fa0 100644 --- a/src/model.jl +++ b/src/model.jl @@ -72,7 +72,7 @@ function create_model( # Declaration of coupling constraints of the problem for couple โˆˆ ๐’ณ_๐’ณ elements_vec = [cpl(case) for cpl โˆˆ couple] - constraints_couple(m, elements_vec..., p_sub, ๐’ฏ, modeltype) + constraints_couple(m, elements_vec..., ๐’ซ, ๐’ฏ, modeltype) end # Declaration of global vairables and constraints @@ -235,17 +235,17 @@ function variables_flow(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, mode ๐’ฉแต’แต˜แต— = filter(has_output, ๐’ฉ) # Create the node flow variables - @variable(m, flow_in[n_in โˆˆ ๐’ฉโฑโฟ, ๐’ฏ, res_flow(inputs(n_in))]) - @variable(m, flow_out[n_out โˆˆ ๐’ฉแต’แต˜แต—, ๐’ฏ, res_flow(outputs(n_out))]) + @variable(m, flow_in[n_in โˆˆ ๐’ฉโฑโฟ, ๐’ฏ, inputs(n_in)]) + @variable(m, flow_out[n_out โˆˆ ๐’ฉแต’แต˜แต—, ๐’ฏ, outputs(n_out)]) # Set the bounds for unidirectional nodes ๐’ฉโฑโฟโปแต˜โฟโฑ = filter(is_unidirectional, ๐’ฉโฑโฟ) ๐’ฉแต’แต˜แต—โปแต˜โฟโฑ = filter(is_unidirectional, ๐’ฉแต’แต˜แต—) - for n_in โˆˆ ๐’ฉโฑโฟโปแต˜โฟโฑ, t โˆˆ ๐’ฏ, p โˆˆ res_flow(inputs(n_in)) + for n_in โˆˆ ๐’ฉโฑโฟโปแต˜โฟโฑ, t โˆˆ ๐’ฏ, p โˆˆ inputs(n_in) set_lower_bound(m[:flow_in][n_in, t, p], 0) end - for n_out โˆˆ ๐’ฉแต’แต˜แต—โปแต˜โฟโฑ, t โˆˆ ๐’ฏ, p โˆˆ res_flow(outputs(n_out)) + for n_out โˆˆ ๐’ฉแต’แต˜แต—โปแต˜โฟโฑ, t โˆˆ ๐’ฏ, p โˆˆ outputs(n_out) set_lower_bound(m[:flow_out][n_out, t, p], 0) end @@ -257,17 +257,17 @@ function variables_flow(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, mode end function variables_flow(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) # Create the link flow variables - @variable(m, link_in[l โˆˆ โ„’, ๐’ฏ, res_flow(inputs(l))]) - @variable(m, link_out[l โˆˆ โ„’, ๐’ฏ, res_flow(outputs(l))]) + @variable(m, link_in[l โˆˆ โ„’, ๐’ฏ, inputs(l)]) + @variable(m, link_out[l โˆˆ โ„’, ๐’ฏ, outputs(l)]) # Set the bounds for unidirectional links โ„’แต˜โฟโฑ = filter(is_unidirectional, โ„’) for l โˆˆ โ„’แต˜โฟโฑ, t โˆˆ ๐’ฏ - for p โˆˆ res_flow(inputs(l)) + for p โˆˆ inputs(l) set_lower_bound(m[:link_in][l, t, p], 0) end - for p โˆˆ res_flow(outputs(l)) + for p โˆˆ outputs(l) set_lower_bound(m[:link_out][l, t, p], 0) end end @@ -279,14 +279,15 @@ function variables_flow(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, model end """ - variables_flow_resource(m, โ„’::Vector{<:Link}, rt::Type{Resource}, ๐’ฏ, modeltype::EnergyModel) + variables_flow_resource(m, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + variables_flow_resource(m, ๐’ฉ::Vector{<:Node}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) Declaration of flow variables for the differrent resource types. The default method is empty but it is required for multiple dispatch in energy flow models. """ -function variables_flow_resource(m, โ„’::Vector{<:Link}, rt::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end -function variables_flow_resource(m, ๐’ฉ::Vector{<:Node}, rt::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end +function variables_flow_resource(m, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end +function variables_flow_resource(m, ๐’ฉ::Vector{<:Node}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end """ @@ -523,7 +524,7 @@ differentiation in extension packages. create_element(m, n::Node, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) = create_node(m, n, ๐’ฏ, ๐’ซ, modeltype) create_element(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) = - create_link(m, l, ๐’ฏ, p_sub, modeltype) + create_link(m, l, ๐’ฏ, ๐’ซ, modeltype) """ constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) @@ -536,46 +537,39 @@ is available for the coupling between [`AbstractElement`](@ref)s while a method for the coupling between a [`Link`](@ref) and a [`Node`](@ref EnergyModelsBase.Node). """ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) - ๐’ซหขแต˜แต‡ = res_types_seg(๐’ซ) for n โˆˆ ๐’ฉ โ„’แถ สณแต’แต, โ„’แต—แต’ = link_sub(โ„’, n) - for p_sub in ๐’ซหขแต˜แต‡ - if has_output(n) - constraints_couple_from(m, n, โ„’แถ สณแต’แต, p_sub, ๐’ฏ, modeltype) - end - if has_input(n) - constraints_couple_to(m, n, โ„’แต—แต’, p_sub, ๐’ฏ, modeltype) - end + + if has_output(n) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ outputs(n)], + m[:flow_out][n, t, p] == + sum(m[:link_in][l, t, p] for l โˆˆ โ„’แถ สณแต’แต if p โˆˆ outputs(l)) + ) + end + + if has_input(n) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ inputs(n)], + m[:flow_in][n, t, p] == + sum(m[:link_out][l, t, p] for l โˆˆ โ„’แต—แต’ if p โˆˆ inputs(l)) + ) end end + + # Create new constraints for specific resource types + for p_sub in res_types_seg(๐’ซ) + constraints_couple_resource(m, ๐’ฉ, โ„’, p_sub, ๐’ฏ, modeltype) + end end function constraints_couple(m, โ„’::Vector{<:Link}, ๐’ฉ::Vector{<:Node}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) return constraints_couple(m, ๐’ฉ, โ„’, ๐’ซ, ๐’ฏ, modeltype) end """ - constraints_couple_from(m, n::Node, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) + constraints_couple_resource(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) Create constraints for output flowrate and input links. """ -function constraints_couple_from(m, n::Node, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(outputs(n), ๐’ซ)], - m[:flow_out][n, t, p] == - sum(m[:link_in][l, t, p] for l โˆˆ โ„’ if p โˆˆ outputs(l)) - ) -end - -""" - constraints_couple_to(m, n::Node, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) - -Create constraints for input flowrate and output links. -""" -function constraints_couple_to(m, n::Node, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(inputs(n), ๐’ซ)], - m[:flow_in][n, t, p] == - sum(m[:link_out][l, t, p] for l โˆˆ โ„’ if p โˆˆ inputs(l)) - ) -end +function constraints_couple_resource(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end """ constraints_emissions(m, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) @@ -888,20 +882,25 @@ subtypes of `Availability`. available node except if one wants to include as well transport between different `Availability` nodes with associated costs (not implemented at the moment). """ -function create_node(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) +function create_node(m, n::Availability, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) - # Mass/energy balance constraints for an availability node. - for p_sub in res_types_seg(๐’ซ) - constraints_flow_balance(m, n, ๐’ฏ, p_sub, modeltype) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ inputs(n)], + m[:flow_in][n, t, p] == m[:flow_out][n, t, p] + ) + + # Constraints based on the resource types + for p_sub in res_types_seg(inputs(n)) + constraints_flow_resource(m, n, ๐’ฏ, p_sub, modeltype) end end +""" + constraints_flow_resource(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) -function constraints_flow_balance(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ intersect(inputs(n), ๐’ซ)], - m[:flow_in][n, t, p] == m[:flow_out][n, t, p] - ) -end +Create constraints for the flow of resources through an `Availability` node for specific resource types. +The function is empty by default and can be implemented in the extension packages. +""" +function constraints_flow_resource(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) end """ create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) @@ -924,25 +923,38 @@ function create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) return create_link(m, ๐’ฏ, ๐’ซ, l, modeltype, formulation(l)) end function create_link(m, l::Direct, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) - # Create flow balance on liks for each resource type - for p_sub in res_types_seg(๐’ซ) - constraints_flow_balance(m, l, ๐’ฏ, p_sub, modeltype) + + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], + m[:link_out][l, t, p] == m[:link_in][l, t, p] + ) + + # Constraints based on the resource types + for p_sub in res_types_seg(link_res(l)) + constraints_flow_resource(m, l, ๐’ฏ, p_sub, modeltype) end end function create_link(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel, formulation::Formulation) - # Create flow balance on liks for each resource type - for p_sub in res_types_seg(๐’ซ) - constraints_flow_balance(m, l, ๐’ฏ, p_sub, modeltype) + + # Generic link in which each output corresponds to the input + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], + m[:link_out][l, t, p] == m[:link_in][l, t, p] + ) + + # Call of the function for limiting the capacity to the maximum installed capacity + if has_capacity(l) + constraints_capacity_installed(m, l, ๐’ฏ, modeltype) + end + + # Constraints based on the resource types + for p_sub in res_types_seg(link_res(l)) + constraints_flow_resource(m, l, ๐’ฏ, p_sub, modeltype) end end """ - constraints_flow_balance(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) + constraints_flow_resource(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) -Create constraints for the resources balances on links. By default, inflow equals outflow for all resources. +Create constraints for the flow of resources through a link for specific resource types. +The function is empty by default and can be implemented in the extension packages. """ -function constraints_flow_balance(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) - @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ itersect(link_res(l), ๐’ซ)], - m[:link_out][l, t, p] == m[:link_in][l, t, p] - ) -end \ No newline at end of file +function constraints_flow_resource(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) end \ No newline at end of file diff --git a/src/structures/resource.jl b/src/structures/resource.jl index 1bba9b69..22b6de7b 100644 --- a/src/structures/resource.jl +++ b/src/structures/resource.jl @@ -44,20 +44,6 @@ Returns the COโ‚‚ intensity of resource `p` """ co2_int(p::Resource) = p.co2_int -""" - res_flow(๐’ซ::Vector{<:Resource}) - -Filter resources with flow variables. -""" -res_flow(๐’ซ::Vector{<:Resource}) = filter(p -> add_flow_var(p), ๐’ซ) - -""" - add_flow_var(p::Resource) - -Checks whether the Resource `p` should add flow variables. -""" -add_flow_var(p::Resource) = true - """ is_resource_emit(p::Resource) From 55404583f76dbf4211d6ed9a4e930c56348fb02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Tue, 30 Sep 2025 12:46:36 +0200 Subject: [PATCH 08/17] Moved the location of resource constraints - Moved the constraint-function for special resource constraint to create_element function --- src/model.jl | 69 ++++++++++++++++++++++--------------------- test/test_resource.jl | 0 2 files changed, 35 insertions(+), 34 deletions(-) create mode 100644 test/test_resource.jl diff --git a/src/model.jl b/src/model.jl index 551f0fa0..5220d797 100644 --- a/src/model.jl +++ b/src/model.jl @@ -521,11 +521,43 @@ differentiation in extension packages. - `Node` - the subfunction is [`create_node`](@ref). - `Link` - the subfunction is [`create_link`](@ref). """ -create_element(m, n::Node, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) = +function create_element(m, n::Node, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) + create_node(m, n, ๐’ฏ, ๐’ซ, modeltype) -create_element(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) = + + # Constraints based on the resource types + node_resources = Vector{Resource}(unique(vcat(inputs(n), outputs(n)))) + for p_sub in res_types_seg(node_resources) + constraints_resource(m, n, ๐’ฏ, p_sub, modeltype) + end +end + +function create_element(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) + create_link(m, l, ๐’ฏ, ๐’ซ, modeltype) + # Constraints based on the resource types + for p_sub in res_types_seg(link_res(l)) + constraints_resource(m, l, ๐’ฏ, p_sub, modeltype) + end +end + +""" + constraints_resource(m, n::Node, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) + +Create constraints for the flow of resources through a node for specific resource types. +The function is empty by default and can be implemented in the extension packages. +""" +function constraints_resource(m, n::Node, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) end + +""" + constraints_resource(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) + +Create constraints for the flow of resources through a link for specific resource types. +The function is empty by default and can be implemented in the extension packages. +""" +function constraints_resource(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) end + """ constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) constraints_couple(m, โ„’::Vector{<:Link}, ๐’ฉ::Vector{<:Node}, ๐’ซ, ๐’ฏ, modeltype::EnergyModel) @@ -887,21 +919,8 @@ function create_node(m, n::Availability, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ inputs(n)], m[:flow_in][n, t, p] == m[:flow_out][n, t, p] ) - - # Constraints based on the resource types - for p_sub in res_types_seg(inputs(n)) - constraints_flow_resource(m, n, ๐’ฏ, p_sub, modeltype) - end end -""" - constraints_flow_resource(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) - -Create constraints for the flow of resources through an `Availability` node for specific resource types. -The function is empty by default and can be implemented in the extension packages. -""" -function constraints_flow_resource(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) end - """ create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) create_link(m, l::Direct, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) @@ -927,11 +946,6 @@ function create_link(m, l::Direct, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::En @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], m[:link_out][l, t, p] == m[:link_in][l, t, p] ) - - # Constraints based on the resource types - for p_sub in res_types_seg(link_res(l)) - constraints_flow_resource(m, l, ๐’ฏ, p_sub, modeltype) - end end function create_link(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel, formulation::Formulation) @@ -944,17 +958,4 @@ function create_link(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::Ener if has_capacity(l) constraints_capacity_installed(m, l, ๐’ฏ, modeltype) end - - # Constraints based on the resource types - for p_sub in res_types_seg(link_res(l)) - constraints_flow_resource(m, l, ๐’ฏ, p_sub, modeltype) - end -end - -""" - constraints_flow_resource(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) - -Create constraints for the flow of resources through a link for specific resource types. -The function is empty by default and can be implemented in the extension packages. -""" -function constraints_flow_resource(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) end \ No newline at end of file +end \ No newline at end of file diff --git a/test/test_resource.jl b/test/test_resource.jl new file mode 100644 index 00000000..e69de29b From 49c7fa51e5a9cccdca0ea863da2b20517c161ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Thu, 23 Oct 2025 13:02:39 +0200 Subject: [PATCH 09/17] Small notation change for resource subset --- src/model.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/model.jl b/src/model.jl index 5220d797..9722b985 100644 --- a/src/model.jl +++ b/src/model.jl @@ -527,8 +527,8 @@ function create_element(m, n::Node, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) # Constraints based on the resource types node_resources = Vector{Resource}(unique(vcat(inputs(n), outputs(n)))) - for p_sub in res_types_seg(node_resources) - constraints_resource(m, n, ๐’ฏ, p_sub, modeltype) + for ๐’ซหขแต˜แต‡ in res_types_seg(node_resources) + constraints_resource(m, n, ๐’ฏ, ๐’ซหขแต˜แต‡, modeltype) end end @@ -537,8 +537,8 @@ function create_element(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) create_link(m, l, ๐’ฏ, ๐’ซ, modeltype) # Constraints based on the resource types - for p_sub in res_types_seg(link_res(l)) - constraints_resource(m, l, ๐’ฏ, p_sub, modeltype) + for ๐’ซหขแต˜แต‡ in res_types_seg(link_res(l)) + constraints_resource(m, l, ๐’ฏ, ๐’ซหขแต˜แต‡, modeltype) end end From 9bff364e8979a6733e331b6bccc19352e0757afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Mon, 23 Mar 2026 14:15:00 +0100 Subject: [PATCH 10/17] Updated and added tests for new resource functions * Updated `res_types` and `res_types_seg` to take `Vector{<:Resource}` instead of `Array{<:Resource}` as input. * Added missing tests for new resource functions --- src/structures/resource.jl | 10 +++--- test/Project.toml | 1 + test/runtests.jl | 4 +++ test/test_resource.jl | 62 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/structures/resource.jl b/src/structures/resource.jl index 22b6de7b..93c7f7de 100644 --- a/src/structures/resource.jl +++ b/src/structures/resource.jl @@ -86,15 +86,15 @@ res_em(๐’ซ::Array{<:Resource}) = filter(is_resource_emit, ๐’ซ) res_em(๐’ซ::Dict) = filter(p -> is_resource_emit(first(p)), ๐’ซ) """ - res_types(๐’ซ::Array{<:Resource}) + res_types(๐’ซ::Vector{<:Resource}) -Return the unique resource types in an Array of resources `๐’ซ`. +Return the unique resource types in an Vector of resources `๐’ซ`. """ -res_types(๐’ซ::Array{<:Resource}) = unique(map(x -> typeof(x), ๐’ซ)) +res_types(๐’ซ::Vector{<:Resource}) = unique(map(x -> typeof(x), ๐’ซ)) """ - res_types_seg(๐’ซ::Array{<:Resource}) + res_types_seg(๐’ซ::Vector{<:Resource}) Return a Vector-of-Vectors of resources segmented by the sub-types. """ -res_types_seg(๐’ซ::Array{<:Resource}) = [Vector{rt}(filter(x -> isa(x, rt), ๐’ซ)) for rt in res_types(๐’ซ)] \ No newline at end of file +res_types_seg(๐’ซ::Vector{<:Resource}) = [Vector{rt}(filter(x -> isa(x, rt), ๐’ซ)) for rt in res_types(๐’ซ)] \ No newline at end of file diff --git a/test/Project.toml b/test/Project.toml index 367040c8..54669be4 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] +EnergyModelsBase = "5d7e687e-f956-46f3-9045-6f5a5fd49f50" EnergyModelsInvestments = "fca3f8eb-b383-437d-8e7b-aac76bb2004f" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" diff --git a/test/runtests.jl b/test/runtests.jl index b12417d6..898f27de 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -21,6 +21,10 @@ ENV["EMB_TEST"] = true # Set flag for example scripts to check if they are run a include("test_data.jl") end + @testset "Base | Resource" begin + include("test_resource.jl") + end + @testset "Base | Node" begin include("test_nodes.jl") end diff --git a/test/test_resource.jl b/test/test_resource.jl index e69de29b..aa2a23bf 100644 --- a/test/test_resource.jl +++ b/test/test_resource.jl @@ -0,0 +1,62 @@ + +Power = ResourceCarrier("Power", 0.0) +Heat = ResourceCarrier("Heat", 0.0) +CO2 = ResourceEmit("CO2", 1.0) + +๐’ซ = [Power, Heat, CO2] + +@testset "Resource - get resource types" begin + # returns a Vector of DataTypes + @test typeof(EMB.res_types(๐’ซ)) == Vector{DataType} + + # returns the correct number of unique resource types + @test length(EMB.res_types(๐’ซ)) == 2 +end + +@testset "Resource - get resource vectors by type" begin + # returns a Vector of Vectors + @test typeof(EMB.res_types_seg(๐’ซ)) == Vector{Vector} + + # returns the correct number of segments + @test length(EMB.res_types_seg(๐’ซ)) == 2 + + # the length of the first segment should be 2 (2 ResourceCarriers) + @test length(EMB.res_types_seg(๐’ซ)[1]) == 2 + + # the length of the second segment should be 1 (1 ResourceEmit) + @test length(EMB.res_types_seg(๐’ซ)[2]) == 1 + +end + +# Add a new resource type and check that it is correctly identified by res_types and res_types_seg +struct TestResource <: Resource + id::String + a::Float64 + b::Int64 +end + +# Add a new resource of type TestResource to the resource vector +๐’ซ = vcat(๐’ซ, [TestResource("Test", 0.5, 1)]) + +@testset "Resource - get resource types w/ custom resource type" begin + # returns a Vector of DataTypes (now including TestResource) + @test typeof(EMB.res_types(๐’ซ)) == Vector{DataType} + + # returns the correct number of unique resource types (now 3) + @test length(EMB.res_types(๐’ซ)) == 3 + +end + +@testset "Resource - get resource vectors by type w/ custom resource type" begin + # returns the correct number of segments (now 3) + @test length(EMB.res_types_seg(๐’ซ)) == 3 + + # the length of the first segment should be 2 (2 ResourceCarriers) + @test length(EMB.res_types_seg(๐’ซ)[1]) == 2 + + # the length of the second segment should be 1 (1 ResourceEmit) + @test length(EMB.res_types_seg(๐’ซ)[2]) == 1 + + # the length of the third segment should be 1 (1 TestResource) + @test length(EMB.res_types_seg(๐’ซ)[3]) == 1 +end From b77124e10b003dc69ec0528ca841d8c182f78fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Mon, 23 Mar 2026 14:21:12 +0100 Subject: [PATCH 11/17] Updated Project.toml for tests --- test/Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index 54669be4..367040c8 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,4 @@ [deps] -EnergyModelsBase = "5d7e687e-f956-46f3-9045-6f5a5fd49f50" EnergyModelsInvestments = "fca3f8eb-b383-437d-8e7b-aac76bb2004f" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" From 11263a295945cc78882edde39c7a8a42499d34eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Mon, 23 Mar 2026 14:34:07 +0100 Subject: [PATCH 12/17] Updated version number and News.md --- NEWS.md | 6 ++++++ Project.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 9dd53b3e..4f600eb4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # Release notes +## Version 0.9.5 (2025-03-23) + +* 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. +* 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 + ## Version 0.9.4 (2025-11-26) ### Bugfixes diff --git a/Project.toml b/Project.toml index a6366609..1f2b997f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "EnergyModelsBase" uuid = "5d7e687e-f956-46f3-9045-6f5a5fd49f50" authors = ["Lars Hellemo , Julian Straus "] -version = "0.9.4" +version = "0.9.5" [deps] JuMP = "4076af6c-e467-56ae-b986-b466b2749572" From 0692c5cb4f37efcb8e533296539ec97cba3abad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Mon, 23 Mar 2026 16:32:21 +0100 Subject: [PATCH 13/17] Revert some unnecessary changes and fix doc output * Revert changes for create_link * `total_duration` is added to SimpleTimes, and the docstring is updated to reflect this. --- docs/src/how-to/utilize-timestruct.md | 4 ++-- src/model.jl | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/src/how-to/utilize-timestruct.md b/docs/src/how-to/utilize-timestruct.md index 85c5dc50..79c253da 100644 --- a/docs/src/how-to/utilize-timestruct.md +++ b/docs/src/how-to/utilize-timestruct.md @@ -39,7 +39,7 @@ op_number = length(op_duration) operational_periods = SimpleTimes(op_number, op_duration) # output -SimpleTimes{Int64}(11, [4, 2, 1, 1, 2, 4, 2, 1, 1, 2, 4]) +SimpleTimes{Int64}(11, [4, 2, 1, 1, 2, 4, 2, 1, 1, 2, 4], 24) ``` 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 operational_periods = SimpleTimes(op_duration) # output -SimpleTimes{Int64}(11, [4, 2, 1, 1, 2, 4, 2, 1, 1, 2, 4]) +SimpleTimes{Int64}(11, [4, 2, 1, 1, 2, 4, 2, 1, 1, 2, 4], 24) ``` and a constructor will automatically deduce that there have to be 11 operational periods. diff --git a/src/model.jl b/src/model.jl index 0ee107b1..96214a44 100644 --- a/src/model.jl +++ b/src/model.jl @@ -646,6 +646,7 @@ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, for n โˆˆ ๐’ฉ โ„’แถ สณแต’แต, โ„’แต—แต’ = link_sub(โ„’, n) + # Constraint for output flowrate and input links. if has_output(n) @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ outputs(n)], m[:flow_out][n, t, p] == @@ -653,6 +654,7 @@ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, ) end + # Constraint for input flowrate and output links. if has_input(n) @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ inputs(n)], m[:flow_in][n, t, p] == @@ -991,7 +993,7 @@ available node except if one wants to include as well transport between differen function create_node(m, n::Availability, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ inputs(n)], - m[:flow_in][n, t, p] == m[:flow_out][n, t, p] + m[:flow_in][n, t, p] == m[:flow_out][n, t, p] ) end @@ -1015,17 +1017,17 @@ function create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) ) return create_link(m, ๐’ฏ, ๐’ซ, l, modeltype, formulation(l)) end -function create_link(m, l::Direct, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) +function create_link(m, l::Direct, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], - m[:link_out][l, t, p] == m[:link_in][l, t, p] + m[:link_out][l, t, p] == m[:link_in][l, t, p] ) end -function create_link(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel, formulation::Formulation) +function create_link(m, ๐’ฏ, ๐’ซ, l::Link, modeltype::EnergyModel, formulation::Formulation) # Generic link in which each output corresponds to the input @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], - m[:link_out][l, t, p] == m[:link_in][l, t, p] + m[:link_out][l, t, p] == m[:link_in][l, t, p] ) # Call of the function for limiting the capacity to the maximum installed capacity From 9f58fc93e12446f07c09420b4e8c331adc9c2ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Mon, 23 Mar 2026 16:53:08 +0100 Subject: [PATCH 14/17] Add new functions to docs and reset inline comments --- docs/src/library/internals/functions.md | 5 +++++ src/model.jl | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/src/library/internals/functions.md b/docs/src/library/internals/functions.md index 09cf9c18..5756b331 100644 --- a/docs/src/library/internals/functions.md +++ b/docs/src/library/internals/functions.md @@ -28,6 +28,8 @@ emissions_operational constraints_emissions constraints_elements constraints_couple +constraints_couple_resource +constraints_resource constraints_level_iterate constraints_level_rp constraints_level_scp @@ -39,6 +41,7 @@ constraints_level_bounds ```@docs variables_capacity variables_flow +variables_flow_resource variables_opex variables_capex variables_emission @@ -96,4 +99,6 @@ res_sub ```@docs collect_types sort_types +res_types +res_types_seg ``` diff --git a/src/model.jl b/src/model.jl index 96214a44..5bd6d9df 100644 --- a/src/model.jl +++ b/src/model.jl @@ -981,7 +981,7 @@ function create_node(m, n::Sink, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) end """ - create_node(m, n::Availability, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) + create_node(m, n::Availability, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) Set all constraints for a `Availability`. Can serve as fallback option for all unspecified subtypes of `Availability`. @@ -992,6 +992,7 @@ available node except if one wants to include as well transport between differen """ function create_node(m, n::Availability, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) + # Mass/energy balance constraints for an availability node. @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ inputs(n)], m[:flow_in][n, t, p] == m[:flow_out][n, t, p] ) @@ -1018,18 +1019,17 @@ function create_link(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) return create_link(m, ๐’ฏ, ๐’ซ, l, modeltype, formulation(l)) end function create_link(m, l::Direct, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) - + # Generic link in which each output corresponds to the input @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], m[:link_out][l, t, p] == m[:link_in][l, t, p] ) end function create_link(m, ๐’ฏ, ๐’ซ, l::Link, modeltype::EnergyModel, formulation::Formulation) - # Generic link in which each output corresponds to the input @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ link_res(l)], m[:link_out][l, t, p] == m[:link_in][l, t, p] ) - + # Call of the function for limiting the capacity to the maximum installed capacity if has_capacity(l) constraints_capacity_installed(m, l, ๐’ฏ, modeltype) From 4cffb4bcc63d46131dfc18d8a02d5096d00d3a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Mon, 23 Mar 2026 17:26:13 +0100 Subject: [PATCH 15/17] Updated docs-strings --- src/model.jl | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/model.jl b/src/model.jl index 5bd6d9df..e9008b56 100644 --- a/src/model.jl +++ b/src/model.jl @@ -283,9 +283,13 @@ end variables_flow_resource(m, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) variables_flow_resource(m, ๐’ฉ::Vector{<:Node}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) -Declaration of flow variables for the differrent resource types. +Create resource-specific flow variables for links or nodes. -The default method is empty but it is required for multiple dispatch in energy flow models. +This function is called from [`variables_flow`](@ref) for each subset of resources +sharing the same type. It can be used to add variables and bounds for specialized +resource classes while keeping the default flow variables unchanged. + +The default methods are empty and intended to be implemented in extension packages. """ function variables_flow_resource(m, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end function variables_flow_resource(m, ๐’ฉ::Vector{<:Node}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end @@ -618,18 +622,16 @@ end """ constraints_resource(m, n::Node, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) + constraints_resource(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) -Create constraints for the flow of resources through a node for specific resource types. -The function is empty by default and can be implemented in the extension packages. -""" -function constraints_resource(m, n::Node, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) end +Create constraints for the flow of resources through an [`AbstractElement`](@ref) for +specific resource types. In `EnergyModelsBase`, this method is provided for +[`Node`](@ref EnergyModelsBase.Node) and [`Link`](@ref). +The function is empty by default and can be implemented in extension packages. """ - constraints_resource(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) +function constraints_resource(m, n::Node, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) end -Create constraints for the flow of resources through a link for specific resource types. -The function is empty by default and can be implemented in the extension packages. -""" function constraints_resource(m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:Resource}, modeltype::EnergyModel) end """ @@ -675,7 +677,13 @@ end """ constraints_couple_resource(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) -Create constraints for output flowrate and input links. +Create resource-specific coupling constraints between nodes and links. + +This function is called from [`constraints_couple`](@ref) for each subset of resources +sharing the same type. It can be used to add additional coupling constraints for +specialized resource classes while keeping the default node-link flow balance unchanged. + +The default method is empty and intended to be implemented in extension packages. """ function constraints_couple_resource(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) end From 2de6e3ddf952e91b6b44c1175685836a931d7301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Tue, 24 Mar 2026 17:53:04 +0100 Subject: [PATCH 16/17] add resource-type dispatch support and functional resource tests - add resource-type dispatch integration in `model.jl` for variable creation and coupling constraints - add resource type helpers in `resource.jl` - extend functional docs index and internals references in `make.jl`, `index.md`, and `functions.md` - add/expand end-to-end resource dispatch tests, including node/resource unpacking and bound constraints, in `test_resource.jl` - add release note update in `NEWS.md` - add new how-to page `extend-resource-functionality.md` --- NEWS.md | 2 +- docs/make.jl | 1 + .../how-to/extend-resource-functionality.md | 250 ++++++++++++++++++ docs/src/index.md | 1 + docs/src/library/internals/functions.md | 2 +- src/model.jl | 15 +- src/structures/resource.jl | 6 +- test/test_resource.jl | 249 ++++++++++++++++- 8 files changed, 504 insertions(+), 22 deletions(-) create mode 100644 docs/src/how-to/extend-resource-functionality.md diff --git a/NEWS.md b/NEWS.md index 4f600eb4..5099c893 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # Release notes -## Version 0.9.5 (2025-03-23) +## Unversioned * 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. * New function to indentify the unique resource types of a vector of resources diff --git a/docs/make.jl b/docs/make.jl index f09693c4..9beed0a6 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -58,6 +58,7 @@ makedocs( "How to" => Any[ "Create a new element"=>"how-to/create_new_element.md", "Create a new node"=>"how-to/create-new-node.md", + "Extend resource functionality"=>"how-to/extend-resource-functionality.md", "Utilize TimeStruct"=>"how-to/utilize-timestruct.md", "Update models"=>"how-to/update-models.md", "Contribute to EnergyModelsBase"=>"how-to/contribute.md", diff --git a/docs/src/how-to/extend-resource-functionality.md b/docs/src/how-to/extend-resource-functionality.md new file mode 100644 index 00000000..6cbbdf89 --- /dev/null +++ b/docs/src/how-to/extend-resource-functionality.md @@ -0,0 +1,250 @@ +# [Extend resource functionality](@id how_to-extend-resource-functionality) + +```@meta +CurrentModule = EMB +``` + +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. 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. + +The pattern follows the same structure as the resource dispatch test in +`test/test_resource.jl`: + +1. Define a resource subtype with extra parameters. +2. Optionally create a custom node subtype that uses the resource. +3. Add resource-specific variables with `variables_flow_resource`. +4. Add resource-specific constraints with `constraints_resource`. +5. Couple node and link resource variables with `constraints_couple_resource`. + +The example in the test suite defines a `PotentialPower` resource that has a potential, +with upper and lower bounds, in addition to energy flow. The flow of this potential +in and out of junctions follow equality constraints, as opposed to the energy and mass flow +which follow sum constraints. + +The notation below follows the same conventions as the implementation and tests: + +- `๐’ฉ` for nodes +- `โ„’` for links +- `๐’ซ` for resources +- `๐’ฏ` for the time structure +- `โ„’แถ สณแต’แต`, `โ„’แต—แต’` for outgoing and incoming links of a node +- `๐’ซแต’แต˜แต—`, `๐’ซโฑโฟ`, `๐’ซหกโฑโฟแต` for resource subsets on outputs, inputs, and links + +## 1. Define a special resource + +Create a subtype of [`Resource`](@ref) and keep `co2_int` as the second field for +consistency with existing resource structures. + +```julia +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 +``` + +## 2. Define a custom node (optional) + +If your resource needs dedicated node behavior, create a custom node subtype. +If the node subtype is parametrized, it can handle different types of resources +in different ways without defining multiple node types. In the dispatch test, +the custom node is an intermediate `NetworkNode` with a potential loss, but +without a loss in energy flow. + +```julia +struct PotentialLossNode{T<:PotentialPower} <: NetworkNode + id::Any + cap::TimeProfile + opex_var::TimeProfile + opex_fixed::TimeProfile + resource::T + input::Dict{<:Resource,<:Real} + output::Dict{<:Resource,<:Real} + data::Vector{<:ExtensionData} + loss_factor::Float64 +end + +function PotentialLossNode( + id, + cap::TimeProfile, + opex_var::TimeProfile, + opex_fixed::TimeProfile, + resource::T, + loss_factor::Float64, +) where {T<:PotentialPower} + return PotentialLossNode{T}( + id, + cap, + opex_var, + opex_fixed, + resource, + Dict(resource => 1.0), + Dict(resource => 1.0), + ExtensionData[], + loss_factor, + ) +end +``` + +## 3. Declare resource-specific variables + +Use [`variables_flow_resource`](@ref) to create resource variables. + +Important: +- Declare each variable name once. +- Filter `๐’ฉ` and `โ„’` down to the subsets that actually use the special resource. +- Keep bounds in `constraints_resource` when they depend on dispatch logic. + +```julia +function EMB.variables_flow_resource( + m, + ๐’ฉ::Vector{<:EMB.Node}, + ๐’ซ::Vector{<:PotentialPower}, + ๐’ฏ, + modeltype::EnergyModel, +) + output_nodes = filter(n -> any(p โˆˆ ๐’ซ for p โˆˆ outputs(n)), ๐’ฉ) + input_nodes = filter(n -> any(p โˆˆ ๐’ซ for p โˆˆ inputs(n)), ๐’ฉ) + + @variable( + m, energy_potential_node_out[ + n โˆˆ output_nodes, t โˆˆ ๐’ฏ, p โˆˆ ๐’ซ; p โˆˆ outputs(n) + ] + ) + + @variable( + m, energy_potential_node_in[ + n โˆˆ input_nodes, t โˆˆ ๐’ฏ, p โˆˆ intersect(inputs(n), ๐’ซ) + ] + ) +end + +function EMB.variables_flow_resource( + m, + โ„’::Vector{<:Link}, + ๐’ซ::Vector{<:PotentialPower}, + ๐’ฏ, + modeltype::EnergyModel, +) + โ„’แต‰แต– = filter(l -> any(p โˆˆ ๐’ซ for p โˆˆ EMB.link_res(l)), โ„’) + @variable(m, energy_potential_link_in[โ„’แต‰แต–, ๐’ฏ, ๐’ซ]) + @variable(m, energy_potential_link_out[โ„’แต‰แต–, ๐’ฏ, ๐’ซ]) +end +``` + +## 4. Add resource-specific constraints + +Use [`constraints_resource`](@ref) for custom node or link behavior. + +```julia +function EMB.constraints_resource( + m, + n::PotentialLossNode, + ๐’ฏ, + ๐’ซ::Vector{<:PotentialPower}, + modeltype::EnergyModel, +) + ๐’ซแต’แต˜แต— = filter(p -> p โˆˆ ๐’ซ, outputs(n)) + ๐’ซโฑโฟ = filter(p -> p โˆˆ ๐’ซ, inputs(n)) + + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] == + n.loss_factor * m[:energy_potential_node_in][n, t, p] + ) + + # Bounds are added as constraints because they rely on `p`, + # which is an index in `energy_potential` variables. + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] >= lower_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] <= upper_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ], + m[:energy_potential_node_in][n, t, p] >= lower_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ], + m[:energy_potential_node_in][n, t, p] <= upper_limit(p) + ) + +end + +function EMB.constraints_resource( + m, + n::EMB.Node, + ๐’ฏ, + ๐’ซ::Vector{<:PotentialPower}, + modeltype::EnergyModel, +) + ๐’ซแต’แต˜แต— = filter(p -> p โˆˆ ๐’ซ, outputs(n)) + ๐’ซโฑโฟ = filter(p -> p โˆˆ ๐’ซ, inputs(n)) + + # Bounds are added as constraints because they rely on `p`, + # which is an index in `energy_potential` variables. + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] >= lower_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] <= upper_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ], + m[:energy_potential_node_in][n, t, p] >= lower_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ], + m[:energy_potential_node_in][n, t, p] <= upper_limit(p) + ) +end + +function EMB.constraints_resource( + m, + l::Link, + ๐’ฏ, + ๐’ซ::Vector{<:PotentialPower}, + modeltype::EnergyModel, +) + ๐’ซหกโฑโฟแต = filter(p -> p โˆˆ ๐’ซ, EMB.link_res(l)) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซหกโฑโฟแต], + m[:energy_potential_link_in][l, t, p] == + m[:energy_potential_link_out][l, t, p] + ) +end +``` + +## 5. Couple node and link variables + +Use [`constraints_couple_resource`](@ref) to connect node and link resource variables. + +```julia +function EMB.constraints_couple_resource( + m, + ๐’ฉ::Vector{<:EMB.Node}, + โ„’::Vector{<:Link}, + ๐’ซ::Vector{<:PotentialPower}, + ๐’ฏ, + modeltype::EnergyModel, +) + for n โˆˆ ๐’ฉ + โ„’แถ สณแต’แต, โ„’แต—แต’ = EMB.link_sub(โ„’, n) + ๐’ซแต’แต˜แต— = filter(p -> p โˆˆ ๐’ซ, outputs(n)) + ๐’ซโฑโฟ = filter(p -> p โˆˆ ๐’ซ, inputs(n)) + + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—, l โˆˆ โ„’แถ สณแต’แต], + m[:energy_potential_node_out][n, t, p] == + m[:energy_potential_link_in][l, t, p] + ) + + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ, l โˆˆ โ„’แต—แต’], + m[:energy_potential_link_out][l, t, p] == + m[:energy_potential_node_in][n, t, p] + ) + end +end +``` diff --git a/docs/src/index.md b/docs/src/index.md index b58b721f..6a964788 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -61,6 +61,7 @@ Depth = 1 Pages = [ "how-to/create_new_element.md", "how-to/create-new-node.md", + "how-to/extend-resource-functionality.md", "how-to/utilize-timestruct.md", "how-to/update-models.md", "how-to/contribute.md", diff --git a/docs/src/library/internals/functions.md b/docs/src/library/internals/functions.md index 5756b331..3c9d1883 100644 --- a/docs/src/library/internals/functions.md +++ b/docs/src/library/internals/functions.md @@ -100,5 +100,5 @@ res_sub collect_types sort_types res_types -res_types_seg +res_types_vec ``` diff --git a/src/model.jl b/src/model.jl index e9008b56..5df85e07 100644 --- a/src/model.jl +++ b/src/model.jl @@ -251,7 +251,7 @@ function variables_flow(m, ๐’ฉ::Vector{<:Node}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, mode end # Create new flow variables for specific resource types - for p_sub in res_types_seg(๐’ซ) + for p_sub in res_types_vec(๐’ซ) variables_flow_resource(m, ๐’ฉ, p_sub, ๐’ฏ, modeltype) end @@ -274,11 +274,16 @@ function variables_flow(m, โ„’::Vector{<:Link}, ๐’ณแต›แต‰แถœ, ๐’ซ, ๐’ฏ, model end # Create new flow variables for specific resource types - for p_sub in res_types_seg(๐’ซ) + for p_sub in res_types_vec(๐’ซ) variables_flow_resource(m, โ„’, p_sub, ๐’ฏ, modeltype) end end +# 5-parameter backward compatibility wrapper (for extension packages with old signature) +function variables_flow(m, ๐’ณ::Vector{<:AbstractElement}, ๐’ณแต›แต‰แถœ, ๐’ฏ, modeltype::EnergyModel) + variables_flow(m, ๐’ณ, ๐’ณแต›แต‰แถœ, Resource[], ๐’ฏ, modeltype) +end + """ variables_flow_resource(m, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) variables_flow_resource(m, ๐’ฉ::Vector{<:Node}, ๐’ซ::Vector{<:Resource}, ๐’ฏ, modeltype::EnergyModel) @@ -605,7 +610,7 @@ function create_element(m, n::Node, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) # Constraints based on the resource types node_resources = Vector{Resource}(unique(vcat(inputs(n), outputs(n)))) - for ๐’ซหขแต˜แต‡ in res_types_seg(node_resources) + for ๐’ซหขแต˜แต‡ in res_types_vec(node_resources) constraints_resource(m, n, ๐’ฏ, ๐’ซหขแต˜แต‡, modeltype) end end @@ -615,7 +620,7 @@ function create_element(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) create_link(m, l, ๐’ฏ, ๐’ซ, modeltype) # Constraints based on the resource types - for ๐’ซหขแต˜แต‡ in res_types_seg(link_res(l)) + for ๐’ซหขแต˜แต‡ in res_types_vec(link_res(l)) constraints_resource(m, l, ๐’ฏ, ๐’ซหขแต˜แต‡, modeltype) end end @@ -666,7 +671,7 @@ function constraints_couple(m, ๐’ฉ::Vector{<:Node}, โ„’::Vector{<:Link}, ๐’ซ, end # Create new constraints for specific resource types - for p_sub in res_types_seg(๐’ซ) + for p_sub in res_types_vec(๐’ซ) constraints_couple_resource(m, ๐’ฉ, โ„’, p_sub, ๐’ฏ, modeltype) end end diff --git a/src/structures/resource.jl b/src/structures/resource.jl index f9900703..b60fa43a 100644 --- a/src/structures/resource.jl +++ b/src/structures/resource.jl @@ -95,8 +95,8 @@ Return the unique resource types in an Vector of resources `๐’ซ`. res_types(๐’ซ::Vector{<:Resource}) = unique(map(x -> typeof(x), ๐’ซ)) """ - res_types_seg(๐’ซ::Vector{<:Resource}) + res_types_vec(๐’ซ::Vector{<:Resource}) -Return a Vector-of-Vectors of resources segmented by the sub-types. +Return a Vector-of-Vectors of resources by the concrete sub-types, if the input is empty it returns an empty Vector. """ -res_types_seg(๐’ซ::Vector{<:Resource}) = [Vector{rt}(filter(x -> isa(x, rt), ๐’ซ)) for rt in res_types(๐’ซ)] \ No newline at end of file +res_types_vec(๐’ซ::Vector{<:Resource}) = [Vector{rt}(filter(x -> isa(x, rt), ๐’ซ)) for rt in res_types(๐’ซ)] \ No newline at end of file diff --git a/test/test_resource.jl b/test/test_resource.jl index aa2a23bf..a0651960 100644 --- a/test/test_resource.jl +++ b/test/test_resource.jl @@ -7,28 +7,34 @@ CO2 = ResourceEmit("CO2", 1.0) @testset "Resource - get resource types" begin # returns a Vector of DataTypes - @test typeof(EMB.res_types(๐’ซ)) == Vector{DataType} + @test EMB.res_types(๐’ซ) isa Vector{DataType} # returns the correct number of unique resource types @test length(EMB.res_types(๐’ซ)) == 2 end @testset "Resource - get resource vectors by type" begin - # returns a Vector of Vectors - @test typeof(EMB.res_types_seg(๐’ซ)) == Vector{Vector} + # returns a Vector + @test EMB.res_types_vec(๐’ซ) isa Vector{Vector} # returns the correct number of segments - @test length(EMB.res_types_seg(๐’ซ)) == 2 + @test length(EMB.res_types_vec(๐’ซ)) == 2 # the length of the first segment should be 2 (2 ResourceCarriers) - @test length(EMB.res_types_seg(๐’ซ)[1]) == 2 + @test length(EMB.res_types_vec(๐’ซ)[1]) == 2 # the length of the second segment should be 1 (1 ResourceEmit) - @test length(EMB.res_types_seg(๐’ซ)[2]) == 1 + @test length(EMB.res_types_vec(๐’ซ)[2]) == 1 end -# Add a new resource type and check that it is correctly identified by res_types and res_types_seg +@testset "Resource - get resource vectors by type w/ empty input" begin + + # returns an empty vector when given an empty resource vector + @test isempty(EMB.res_types_vec(Resource[])) +end + +# Add a new resource type and check that it is correctly identified by res_types and res_types_vec struct TestResource <: Resource id::String a::Float64 @@ -40,7 +46,7 @@ end @testset "Resource - get resource types w/ custom resource type" begin # returns a Vector of DataTypes (now including TestResource) - @test typeof(EMB.res_types(๐’ซ)) == Vector{DataType} + @test EMB.res_types(๐’ซ) isa Vector{DataType} # returns the correct number of unique resource types (now 3) @test length(EMB.res_types(๐’ซ)) == 3 @@ -49,14 +55,233 @@ end @testset "Resource - get resource vectors by type w/ custom resource type" begin # returns the correct number of segments (now 3) - @test length(EMB.res_types_seg(๐’ซ)) == 3 + @test length(EMB.res_types_vec(๐’ซ)) == 3 # the length of the first segment should be 2 (2 ResourceCarriers) - @test length(EMB.res_types_seg(๐’ซ)[1]) == 2 + @test length(EMB.res_types_vec(๐’ซ)[1]) == 2 # the length of the second segment should be 1 (1 ResourceEmit) - @test length(EMB.res_types_seg(๐’ซ)[2]) == 1 + @test length(EMB.res_types_vec(๐’ซ)[2]) == 1 # the length of the third segment should be 1 (1 TestResource) - @test length(EMB.res_types_seg(๐’ซ)[3]) == 1 + @test length(EMB.res_types_vec(๐’ซ)[3]) == 1 +end + + +# Implement a custom resource type and check that it is correctly handled in the model via dispatch +@testset "Resource - energy potential via dispatch" begin + + + 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 + + # A costum node type that represents a potential loss node + # which has an input and output resource and a loss factor that determines how much of the input potential is lost in the node + # but there is no loss in energy + struct PotentialLossNode{T <: PotentialPower} <: NetworkNode + id::Any + cap::TimeProfile + opex_var::TimeProfile + opex_fixed::TimeProfile + resource::T + input::Dict{<:Resource,<:Real} + output::Dict{<:Resource,<:Real} + data::Vector{<:ExtensionData} + loss_factor::Float64 + end + function PotentialLossNode( + id, + cap::TimeProfile, + opex_var::TimeProfile, + opex_fixed::TimeProfile, + resource::T, + loss_factor::Float64, + ) where {T <: PotentialPower} + return PotentialLossNode{T}(id, cap, opex_var, opex_fixed, resource, Dict(resource=>1.0), Dict(resource=>1.0), ExtensionData[], loss_factor) + end + + + function extension_resource_graph(loss_factor::Float64) + + pp = PotentialPower("PotentialPower", 0.0, 0.9, 1.1) + source = RefSource( + "pp_source", + FixedProfile(4), + FixedProfile(10), + FixedProfile(0), + Dict(pp => 1), + ) + loss_node = PotentialLossNode( + "pp_loss", + FixedProfile(4), + FixedProfile(0), + FixedProfile(0), + pp, + loss_factor, + ) + sink = RefSink( + "pp_sink", + FixedProfile(3), + Dict(:surplus => FixedProfile(4), :deficit => FixedProfile(100)), + Dict(pp => 1), + ) + + ops = SimpleTimes(5, 2) + T = TwoLevel(2, 2, ops; op_per_strat = 10) + nodes = [source, loss_node, sink] + links = [ + Direct("src-loss", source, loss_node, Linear()) + Direct("loss-snk", loss_node, sink, Linear()) + ] + modeltype = OperationalModel( + Dict(CO2 => FixedProfile(100)), + Dict(CO2 => FixedProfile(0)), + CO2, + ) + case = Case(T, [pp, CO2], [nodes, links], [[get_nodes, get_links]]) + + return case, modeltype + end + + # Delcare new variables for the potential power resource + function EMB.variables_flow_resource( + m, ๐’ฉ::Vector{<:EMB.Node}, ๐’ซ::Vector{<:PotentialPower}, ๐’ฏ, modeltype::EnergyModel + ) + output_nodes = filter(n -> any(p โˆˆ ๐’ซ for p โˆˆ outputs(n)), ๐’ฉ) + input_nodes = filter(n -> any(p โˆˆ ๐’ซ for p โˆˆ inputs(n)), ๐’ฉ) + + @variable( + m, energy_potential_node_out[ + n โˆˆ output_nodes, t โˆˆ ๐’ฏ, p โˆˆ ๐’ซ; p โˆˆ outputs(n) + ] + ) + + @variable( + m, energy_potential_node_in[ + n โˆˆ input_nodes, t โˆˆ ๐’ฏ, p โˆˆ intersect(inputs(n), ๐’ซ) + ] + ) + end + + function EMB.variables_flow_resource( + m, โ„’::Vector{<:Link}, ๐’ซ::Vector{<:PotentialPower}, ๐’ฏ, modeltype::EnergyModel + ) + โ„’แต‰แต– = filter(l -> any(p โˆˆ ๐’ซ for p โˆˆ EMB.link_res(l)), โ„’) + @variable(m, energy_potential_link_in[โ„’แต‰แต–, ๐’ฏ, ๐’ซ]) + @variable(m, energy_potential_link_out[โ„’แต‰แต–, ๐’ฏ, ๐’ซ]) + end + + # Declare new constraints for the potential power resource using the newly declared variables + function EMB.constraints_resource( + m, n::PotentialLossNode, ๐’ฏ, ๐’ซ::Vector{<:PotentialPower}, modeltype::EnergyModel + ) + ๐’ซแต’แต˜แต— = filter(p -> p โˆˆ ๐’ซ, outputs(n)) + ๐’ซโฑโฟ = filter(p -> p โˆˆ ๐’ซ, inputs(n)) + + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] >= lower_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] <= upper_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ], + m[:energy_potential_node_in][n, t, p] >= lower_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ], + m[:energy_potential_node_in][n, t, p] <= upper_limit(p) + ) + + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] == n.loss_factor * m[:energy_potential_node_in][n, t, p] + ) + end + + function EMB.constraints_resource( + m, n::EMB.Node, ๐’ฏ, ๐’ซ::Vector{<:PotentialPower}, modeltype::EnergyModel + ) + ๐’ซแต’แต˜แต— = filter(p -> p โˆˆ ๐’ซ, outputs(n)) + ๐’ซโฑโฟ = filter(p -> p โˆˆ ๐’ซ, inputs(n)) + + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] >= lower_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—], + m[:energy_potential_node_out][n, t, p] <= upper_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ], + m[:energy_potential_node_in][n, t, p] >= lower_limit(p) + ) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ], + m[:energy_potential_node_in][n, t, p] <= upper_limit(p) + ) + end + + function EMB.constraints_resource( + m, l::Link, ๐’ฏ, ๐’ซ::Vector{<:PotentialPower}, modeltype::EnergyModel + ) + ๐’ซหกโฑโฟแต = filter(p -> p โˆˆ ๐’ซ, EMB.link_res(l)) + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซหกโฑโฟแต], + m[:energy_potential_link_in][l, t, p] == m[:energy_potential_link_out][l, t, p] + ) + end + + function EMB.constraints_couple_resource( + m, ๐’ฉ::Vector{<:EMB.Node}, โ„’::Vector{<:Link}, + ๐’ซ::Vector{<:PotentialPower}, ๐’ฏ, modeltype::EnergyModel + ) + for n โˆˆ ๐’ฉ + โ„’แถ สณแต’แต, โ„’แต—แต’ = EMB.link_sub(โ„’, n) + ๐’ซแต’แต˜แต— = filter(p -> p โˆˆ ๐’ซ, outputs(n)) + ๐’ซโฑโฟ = filter(p -> p โˆˆ ๐’ซ, inputs(n)) + + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซแต’แต˜แต—, l โˆˆ โ„’แถ สณแต’แต], + m[:energy_potential_node_out][n, t, p] == m[:energy_potential_link_in][l, t, p] + ) + + @constraint(m, [t โˆˆ ๐’ฏ, p โˆˆ ๐’ซโฑโฟ, l โˆˆ โ„’แต—แต’], + m[:energy_potential_link_out][l, t, p] == m[:energy_potential_node_in][n, t, p] + ) + end + end + + + case, modeltype = extension_resource_graph(0.9) + pp, co2 = get_products(case) + source, loss_node, sink = get_nodes(case) + + m = run_model(case, modeltype, HiGHS.Optimizer) + ๐’ฏ = get_time_struct(case) + โ„’ = get_links(case) + n_t = length(๐’ฏ) + + @test haskey(m, :energy_potential_node_in) + @test haskey(m, :energy_potential_node_out) + @test haskey(m, :energy_potential_link_in) + @test haskey(m, :energy_potential_link_out) + + @test length(m[:energy_potential_node_in]) == 2 * n_t + @test length(m[:energy_potential_node_out]) == 2 * n_t + @test length(m[:energy_potential_link_in]) == length(โ„’) * n_t + @test length(m[:energy_potential_link_out]) == length(โ„’) * n_t + + @test all(value(m[:energy_potential_node_out][source, t, pp]) >= lower_limit(pp) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_node_out][source, t, pp]) <= upper_limit(pp) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_node_in][sink, t, pp]) >= lower_limit(pp) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_node_in][sink, t, pp]) <= upper_limit(pp) for t โˆˆ ๐’ฏ) + + @test all(value(m[:energy_potential_node_out][source, t, pp]) โ‰ˆ value(m[:energy_potential_link_in][โ„’[1], t, pp]) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_link_out][โ„’[1], t, pp]) โ‰ˆ value(m[:energy_potential_node_in][loss_node, t, pp]) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_node_out][loss_node, t, pp]) โ‰ˆ loss_node.loss_factor * value(m[:energy_potential_node_in][loss_node, t, pp]) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_node_out][loss_node, t, pp]) โ‰ˆ value(m[:energy_potential_link_in][โ„’[2], t, pp]) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_link_out][โ„’[2], t, pp]) โ‰ˆ value(m[:energy_potential_node_in][sink, t, pp]) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_node_out][loss_node, t, pp]) < value(m[:energy_potential_node_in][loss_node, t, pp]) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_node_out][source, t, pp]) < value(m[:flow_out][source, t, pp]) for t โˆˆ ๐’ฏ) + @test all(value(m[:energy_potential_node_in][sink, t, pp]) < value(m[:flow_in][sink, t, pp]) for t โˆˆ ๐’ฏ) end From e71bbde2188a3d2925f681b8c53de820dee2593c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Flo=20B=C3=B8dal?= Date: Tue, 24 Mar 2026 18:19:16 +0100 Subject: [PATCH 17/17] Updated create_elements docstring --- src/model.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/model.jl b/src/model.jl index 5df85e07..327ab14c 100644 --- a/src/model.jl +++ b/src/model.jl @@ -594,9 +594,8 @@ end create_element(m, n::Node, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) create_element(m, l::Link, ๐’ฏ, ๐’ซ, modeltype::EnergyModel) -Default fallback method for an element type if no other method is defined for a given type. -This function calls subfunctions to maintain backwards compatibility and simplify the -differentiation in extension packages. +Calls the create functions for the specific elements to add element specific constraints, +also add resource specific constraints through constraints_resource. `EnergyModelsBase` provides the user with two element types, [`Link`](@ref) and [`Node`](@ref EnergyModelsBase.Node):