Create a new node
The energy system model is based on the JuMP optimization framework, so some basic knowledge on this Julia package is needed to implement a new technology node.
To create a new technology node named
NewTechNode
, we need to
Implement a new
struct
(composite type), that is a subtypes ofNode
,Source
,Sink
, etc. Here, a central choice is to decide on what abstract node type to subtype to.Optional: implement the method
variables_node(m, 𝒩ˢᵘᵇ::Vector{<:NewTechNode}, 𝒯, modeltype::EnergyModel)
Implement this method if you want to create additional optimization variables for the new node. See how to create JuMP variables in the JuMP documentation.
Implement the method
create_node(m, n::NewTechNode, 𝒯, 𝒫, modeltype::EnergyModel)
In this method the constraints for the new node are created. See how to create JuMP constraints in the JuMP documentation.
While step 1 is always required, it is possible to omit step 2 if no new variables are required. It is also possible to create unregistered variables for each instance of the node. This is however only advised if you do not need to access the value of the variables after the optimization.
When creating a new node type, you are free to change the field names to whatever name you desire. However, if you change the field names, there are several things to consider:
- Certain functions are used within the core structure of the code for accessing the fields. These functions can be found in the Public Interface.
- The function
EMB.check_node
conducts some checks on the individual node data. If the fields and structure from the reference nodes are not present, you also have to create a new function for your node.
Additional tips for creating new nodes
- If the
NewNodeType
should be able to include investments, it is necessary to i) call the functionconstraints_capacity_installed
. and ii) have the fielddata
. The function is used for dispatching on the constraints for investments while the fielddata
is used for providing theInvestmentData
. - Emissions can be included in any way. It is however beneficial to reutilize the
EmissionsData
type to improve usability with other packages. This requries again the inclusion of the fielddata
inNewNodeType
. It is possible to also create new subtypes forEmissionsData
as well as dispatch on the functionconstraints_data(m, n::Node, 𝒯, 𝒫, modeltype, data::Data)
. - It is in a first stage not important to include functions for handling all possible
TimeStructure
s, that is, e.g.,RepresentativePeriods
. Instead, and error can be provided if an unsupportedTimeStructure
is chosen. - The existing reference nodes and their respective constraint functions can serve as idea generators.
- It is possible to include constraints that are coupled to another
Node
by introduing a field with theNode
as type in theNewNodeType
, e.g., a fieldnode::Storage
when you plan to include additional constraints including aStorage
node. EnergyModelsBase
utilize functions for accessing the fields of the individual nodes. These functions can be found in Functions for accessing fields ofNode
types. In general, these functions dispatch onabstract type
s.- It is beneficial to include the fields
input
andoutput
for theNewTechNode
. This is not strictly required, but otherwise one has to provide new methods for the functionsinputs()
andoutputs()
.
Advanced creation of new nodes
Step 3 in the procedure is not necessarily required. It is also possible to use the available constraint functions for the new node type. In this case, we have to first obtain an overview over the constraint functions called in
create_node(m, n::ParentNode, 𝒯, 𝒫, modeltype::EnergyModel)
in which ParentNode
corresponds to the abstract type
that is used as parent for the new NewTechNode
. Subsequently, we can add a method to the existing constraint function which is called by the ParentNode
. This constraint function has to dispatch on the created NewTechNode
type.
It is in general advised to create a new function create_node(m, n::NewTechNode, 𝒯, 𝒫, modeltype::EnergyModel)
. The advantage here is that the user requires less understanding of the individual constraint functions. This may lead to repetetive code, but is the safer choice.
What abstract node type should you subtype to?
The choice of node supertype depends on what optimization variables you need for the constraints describing the functionality of the new node.
A new node is defined as a composite type (struct
) and subtype of one of the standard node types,
Furthermore, we have the types
Availability
<: NetworkNode
Storage
<: NetworkNode
which correspond to a routing node (Availability
) and a storage node (Storage
).
The overall structure of the individual nodes can be printed to the REPL using the following code:
julia> using EnergyModelsBase
julia> const EMB = EnergyModelsBase
julia> using AbstractTrees
julia> AbstractTrees.children(x::Type) = subtypes(x)
julia> print_tree(EMB.Node)
Node
├─ NetworkNode
│ ├─ Availability
│ │ └─ GenAvailability
│ ├─ RefNetworkNode
│ └─ Storage
│ └─ RefStorage
├─ Sink
│ └─ RefSink
└─ Source
└─ RefSource
The leaf nodes of the above type hierarchy tree are composite type
s, while the inner vertices are abstract type
s. The chosen parent type
of the NewNodeType
node decides what optimization variables are created for use by default. You can find the created default optimization variables in Optimization Variables and Node types and respective variables. The main difference between the individual parent types is whether they have only an energy/mass output (Source
), input and output (NetworkNode
), or input (Sink
). A more detailed explanation of the different abstract type
s can be found in Description of Technologies
Example
As an example, you can check out how EnergyModelsRenewableProducers
introduces two new technology types, a Source
and a Storage
.