Node
Caution
Proper transformation (from Julia docs to Python docs) of math mode rendering, and therefore the “detailed model reference”, is partially broken. Until this is fixed, please refer to the original Julia documentation for any math mode rendering.
Overview
Note
This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl and its documentation for any further details (which may require some familiarity with Julia).
A Node
represents a basic intersection/hub for energy flows. This can for example be some sort of bus (for electrical systems). It enforces a nodal balance equation (= “energy that flows into it must flow out”) for every Snapshot
. Enabling the internal state of the Node
allows it to act as energy storage, modifying the nodal balance equation. This allows using Node
s for various storage tasks (like batteries, hydro reservoirs, heat storages, …).
Basic Examples
A Node
that represents an electrical bus:
bus:
type: Node
carrier: electricity
A Node
that represents a simplified hydrogen storage:
store:
type: Node
carrier: hydrogen
has_state: true
state_lb: 0
state_ub: 50
Parameters
carrier
Carrier
of this Node
. All connecting components need to respect that.
- Mandatory:
yes
- Values:
string
- Unit:
- Default:
has_state
If true
, the Node
is considered to have an internal state (“stateful Node
”). This allows it to act as energy storage. Connect Connection
s or Unit
s to it, acting as charger/discharger.
- Mandatory:
no
- Values:
true
,false
- Unit:
- Default:
false
state_lb
Lower bound of the internal state, requires has_state = true
.
- Mandatory:
no
- Values:
numeric,
col@file
,decision:value
- Unit:
energy
- Default:
\(-\infty\)
state_ub
Upper bound of the internal state, requires has_state = true
.
- Mandatory:
no
- Values:
numeric,
col@file
,decision:value
- Unit:
energy
- Default:
\(+\infty\)
state_cyclic
Controls how the state considers the boundary between last and first Snapshot
. disabled
disables cyclic behaviour of the state (see also state_initial
), eq
leads to the state at the end of the year being the initial state at the beginning of the year, while geq
does the same while allowing the end-of-year state to be higher (= “allowing to destroy energy at the end of the year”).
- Mandatory:
no
- Values:
eq
,geq
, ordisabled
- Unit:
- Default:
eq
state_initial
Sets the initial state. Must be used in combination with state_cyclic = disabled
.
- Mandatory:
no
- Values:
numeric
- Unit:
energy
- Default:
state_final
Sets the final state. Must be used in combination with state_cyclic = disabled
.
- Mandatory:
no
- Values:
numeric
- Unit:
energy
- Default:
state_percentage_loss
Per Snapshot
percentage loss of state (loosing 1% should be set as 0.01
).
- Mandatory:
no
- Values:
\(\in [0, 1]\)
- Unit:
- Default:
0
nodal_balance
Can only be used for has_state = false
. enforce
forces total injections to always be zero (similar to Kirchhoff’s current law), create
allows “supply < demand”, destroy
allows “supply > demand”, at this Node
.
- Mandatory:
no
- Values:
enforce
,destroy
, orcreate
- Unit:
- Default:
enforce
sum_window_size
TODO.
- Mandatory:
no
- Values:
integer
- Unit:
- Default:
sum_window_step
TODO.
- Mandatory:
no
- Values:
integer
- Unit:
- Default:
1
build_priority
Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others.
- Mandatory:
no
- Values:
numeric
- Unit:
- Default:
0
Detailed model reference
Variables
pf_theta
How to?
Access this variable by using:
# Julia
component(model, "your_node").var.pf_theta
# Python
model.get_component("your_node").var.pf_theta
You can find the full implementation and all details here: IESopt.jl
.
state
How to?
Access this variable by using:
# Julia
component(model, "your_node").var.state
# Python
model.get_component("your_node").var.state
You can find the full implementation and all details here: IESopt.jl
.
Add the variable representing the state of this node
to the model
, if node.has_state == true
. This can be accessed via node.var.state[t]
.
Additionally, if the state’s initial value is specified via state_initial
the following gets added:
$\(
\text{state}_1 = \text{state}_{initial}
\)$
math
Expressions
injection
How to?
Access this expression by using:
# Julia
component(model, "your_node").exp.injection
# Python
model.get_component("your_node").exp.injection
You can find the full implementation and all details here: IESopt.jl
.
Add an empty (JuMP.AffExpr(0)
) expression to the node
that keeps track of feed-in and withdrawal of energy.
This constructs the expression \(\text{injection}_t, \forall t \in T\) that is utilized in node.con.nodalbalance
. Core components (Connection
s, Profile
s, and Unit
s) that feed energy into this node add to it, all others subtract from it. A stateless node forces this nodal balance to always equal 0
which essentially describes “generation = demand”.
Constraints
last_state
How to?
Access this constraint by using:
# Julia
component(model, "your_node").con.last_state
# Python
model.get_component("your_node").con.last_state
You can find the full implementation and all details here: IESopt.jl
.
Add the constraint defining the bounds of the node
’s state during the last Snapshot to the model
, if node.has_state == true
.
This is necessary since it could otherwise happen, that the state following the last Snapshot is actually not feasible (e.g. we could charge a storage by more than it’s state allows for). The equations are based on the construction of the overall state variable.
$\(
\begin{aligned}
& \text{state}_{end} \cdot \text{factor}^\omega_t + \text{injection}_{end} \cdot \omega_t \geq \text{state}_{lb} \\
& \text{state}_{end} \cdot \text{factor}^\omega_t + \text{injection}_{end} \cdot \omega_t \leq \text{state}_{ub}
\end{aligned}
\)\(
math
Here \)\omega_t\( is the `weight` of `Snapshot` `t`, and \)\text{factor}$ is either 1.0
(if there are now percentage losses configured), or (1.0 - node.state_percentage_loss)
otherwise.
!!! note “Constraint safety”
The lower and upper bound constraint are subject to penalized slacks.
nodalbalance
How to?
Access this constraint by using:
# Julia
component(model, "your_node").con.nodalbalance
# Python
model.get_component("your_node").con.nodalbalance
You can find the full implementation and all details here: IESopt.jl
.
Add the constraint describing the nodal balance to the model
.
Depending on whether the node
is stateful or not, this constructs different representations:
if node.has_state == true
\[\begin{split} > \begin{aligned} > & \text{state}_t = \text{state}_{t-1} \cdot \text{factor}^\omega_{t-1} + \text{injection}_{t-1} \cdot \omega_{t-1}, \qquad \forall t \in T \setminus \{1\} \\ > \\ > & \text{state}_1 = \text{state}_{end} \cdot \text{factor}^\omega_{end} + \text{injection}_{end} \cdot \omega_{end} > \end{aligned} > \end{split}\]math Here \(\omega_t\) is the
weight
ofSnapshot
t
, and \(\text{factor}\) is either1.0
(if there are now percentage losses configured), or(1.0 - node.state_percentage_loss)
otherwise. \(\text{injection}_{t}\) describes the overall injection (all feed-ins minus all withdrawals). \(end\) indicates the last snapshot in \(T\). Depending on the setting ofstate_cyclic
the second constraint is written as \(=\) ("eq"
) or \(\leq\) ("leq"
). The latter allows the destruction of excess energy at the end of the total time period to help with feasibility.if node.has_state == false
\[\begin{split} > \begin{aligned} > & \text{injection}_{t} = 0, \qquad \forall t \in T \\ > \end{aligned} > \end{split}\]math This equation can further be configured using the
nodal_balance
parameter, which acceptsenforce
(resulting in \(=\)),create
(resulting in \(\leq\); allowing the creation of energy - or “negative injections”), anddestroy
( resulting in \(\geq\); allowing the destruction of energy - or “positive injections”). This can be used to model some form of energy that can either be sold (using adestroy
Profile
connected to thisNode
), or “wasted into the air” using thedestroy
setting of thisNode
.
state_bounds
How to?
Access this constraint by using:
# Julia
component(model, "your_node").con.state_bounds
# Python
model.get_component("your_node").con.state_bounds
You can find the full implementation and all details here: IESopt.jl
.
Add the constraint defining the bounds of the node
’s state to the model
, if node.has_state == true
.
$\(
\begin{aligned}
& \text{state}_t \geq \text{state}_{lb}, \qquad \forall t \in T \\
& \text{state}_t \leq \text{state}_{ub}, \qquad \forall t \in T
\end{aligned}
\)$
math
!!! note “Constraint safety”
The lower and upper bound constraint are subject to penalized slacks.