Unit
Note
This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).
If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.
Overview
A Unit
allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.
Basic Examples
A Unit
that represents a basic gas turbine:
gas_turbine:
type: Unit
inputs: {gas: gas_grid}
outputs: {electricity: node, co2: total_co2}
conversion: 1 gas -> 0.4 electricity + 0.2 co2
capacity: 10 out:electricity
A Unit
that represents a basic wind turbine:
wind_turbine:
type: Unit
outputs: {electricity: node}
conversion: ~ -> 1 electricity
capacity: 10 out:electricity
availability_factor: wind_factor@input_data
marginal_cost: 1.7 per out:electricity
A Unit
that represents a basic heat pump, utilizing a varying COP:
heatpump:
type: Unit
inputs: {electricity: grid}
outputs: {heat: heat_system}
conversion: 1 electricity -> cop@inputfile heat
capacity: 10 in:electricity
Parameters
conversion
The conversion expression describing how this Unit
transforms energy. Specified in the form of “\(\alpha \cdot carrier_1 + \beta \cdot carrier_2\) -> \(\gamma \cdot carrier_3 + \delta \cdot carrier_4\)”. Coefficients allow simple numerical calculations, but are not allowed to include spaces (so e.g. (1.0/9.0)
is valid). Coefficients are allowed to be NumericalInput
s, resulting in column@data_file
being a valid coefficient (this can be used e.g. for time-varying COPs of heatpumps).
mandatory: yes
default: \(-\)
values: string
unit: -
capacity
Maximum capacity of this Unit
, to be given in the format X in/out:carrier
where X
is the amount, in
or out
(followed by :
) specifies whether the limit is to be placed on the in- our output of this Unit
, and carrier
specifies the respective Carrier
. Example: 100 in:electricity
(to limit the “input rating”).
mandatory: yes
default: \(-\)
values: value dir:carrier
unit: -
outputs
Dictionary specifying the output “ports” of this Unit
. Refer to the basic examples for the general syntax.
mandatory: yes
default: \(-\)
values: dict
unit: -
inputs
Dictionary specifying the input “ports” of this Unit
. If not specified (= no explicit input), the conversion
has to follow the form of conversion: ~ -> ...
, indicating an “open” input. This may, e.g., be used for renewable energy sources, where the primary energy input (e.g., solar) is not explicitly modeled.
mandatory: no
default: \(-\)
values: dict
unit: -
availability
Time series (or fixed value) that limits the available capacity. If, e.g., capacity: 100 out:electricity
and availability: 70
, the available capacity will only be 70 electricity
. Can be used to model non-availability of power plants, e.g., due to maintenance. For time-varying availability of intermittent generators (e.g., wind), it’s recommended (most of the time) to use availability_factor
instead.
mandatory: no
default: \(+\infty\)
values: numeric
unit: power
availability_factor
Similar to availability
, but given as factor of capacity
instead. If, e.g., capacity: 100 out:electricity
and availability_factor: 0.7
, the available capacity will only be 70 electricity
. This is especially useful for intermittent generators, where the availability is not a fixed value, but depends on the weather, and can be passed, e.g., by setting availability_factor: wind@input_data_file
.
mandatory: no
default: \(1\)
values:
\in [0, 1]
unit: -
adapt_min_to_availability
If true
, the minimal partial load will be influenced by the availability. Example: Consider a Unit
with capacity: 100 out:electricity
, a min_conversion
of 0.4
, and an availability_factor
of 0.5
. This entails having 50 electricity
available, while the minimal partial load is 40 electricity
. This results in the Unit
at best operating only closely above the minimal partial load. Furthermore, an availability_factor
below 0.4
would result in no feasible generation, besides shutting the Unit
off. While this might be the intended mode of operation in many use cases, adapt_min_to_availability
can change this: If set to true
, this dynamically changes the minimal partial load. In the previous example, that means (100 * 0.5) * 0.4 = 20 electricity
(the 50% minimum load are now based on the available 40), changing the overall behaviour (including efficiencies) as well as leading to feasible generations even when the availability_factor
is below 0.4
.
mandatory: no
default: \(false\)
values:
true
,false
unit: -
marginal_cost
Marginal cost of the consumption/generation of one unit of energy of the specified carrier. Has to be given in the format value per dir:carrier
, e.g. 3.5 per out:electricity
for a marginal cost of 3.5 monetary units per unit of electricity generated.
mandatory: no
default: \(0\)
values:
value per dir:carrier
unit: monetary per energy
enable_ramp_up
Enables calculation of upward ramps. Ramping is based on the carrier specified in capacity
.
mandatory: no
default: \(false\)
values:
true
,false
unit: -
enable_ramp_down
Enables calculation of downward ramps. Ramping is based on the carrier specified in capacity
.
mandatory: no
default: \(false\)
values:
true
,false
unit: -
ramp_up_cost
Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.
mandatory: no
default: \(0\)
values: numeric
unit: monetary per power
ramp_down_cost
Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.
mandatory: no
default: \(0\)
values: numeric
unit: monetary per power
ramp_up_limit
Limits the allowed ramping up based on this factor of the total capacity. If capacity: 100 in:electricity
with ramp_up_limit: 0.2
, this limits the total increase of usage of electricity (on the input) to 20 units (power) per hour. For example, starting at an input of 35, after one hour the input has to be lesser than or equal to 55. If a Snapshot
’s duration is set to, e.g., two hours, this would allow a total increase of 40 units.
mandatory: no
default: \(1\)
values:
\in [0, 1]
unit: -
ramp_down_limit
Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit
.
mandatory: no
default: \(1\)
values:
\in [0, 1]
unit: -
min_on_time
Minimum on-time of the Unit
. If set, the Unit
has to be on for at least this amount of time, after turning on. It is highly recommended to only use this with unit_commitment: binary
, unless you know why it’s fine to use with another mode.
mandatory: no
default: \(0\)
values: numeric
unit: hours
min_off_time
Minimum off-time of the Unit
. If set, the Unit
has to be off for at least this amount of time, after turning off. It is highly recommended to only use this with unit_commitment: binary
, unless you know why it’s fine to use with another mode.
mandatory: no
default: \(0\)
values: numeric
unit: hours
on_time_before
Time that this Unit
has already been running before the optimization starts. Can be used in combination with min_on_time
.
mandatory: no
default: \(0\)
values: numeric
unit: hours
off_time_before
Time that this Unit
has already been off before the optimization starts. Can be used in combination with min_off_time
.
mandatory: no
default: \(0\)
values: numeric
unit: hours
is_on_before
Number of Unit
s that should be considered to have been running before the optimization starts. Can be used in combination with on_time_before
, especially for unit_count
greater than 1.
mandatory: no
default: \(1\)
values: numeric
unit: -
unit_commitment
Controls how the unit commitment of this Unit
is handled. linear
results in the ability to startup parts of the unit (so 0.314159
is a feasible amount of “turned on unit”), while binary
restricts the Unit
to either be on (converting the conversion_at_min
+ possible additional conversion above that minimum) or off (converting nothing); integer
is needed to consider binary
unit commitment for Unit
s with more than 1 “grouped unit” (see unit_count
).
mandatory: no
default: \(off\)
values:
off
,linear
,binary
,integer
unit: -
unit_count
Number of units aggregated in this Unit
. Besides interacting with the mode of unit_commitment
, this mainly is responsible for scaling the output (e.g. grouping 47 of the same wind turbine, …).
mandatory: no
default: \(1\)
values: numeric
unit: -
min_conversion
If unit_commitment
is not set to off
, this specifies the percentage that is considered to be the minimal feasible partial load this Unit
can operate at. Operating below that setpoint is not allowed, at that point the conversion_at_min
coefficients are used, and above that they are scaled to result in conversion
when running at full capacity.
mandatory: no
default: \(-\)
values:
\in [0, 1]
unit: -
conversion_at_min
The conversion expression while running on the minimal partial load. Only applicable if unit_commitment
is not off
and min_conversion
is explicitly set. Follows the same form as conversion
.
mandatory: no
default: \(-\)
values: string
unit: -
startup_cost
Costs per startup (also applicable if startups are not binary or integer). This is necessary to allow conversion_at_min
to have (at least partially) the effect that one expects, if unit_commitment: linear
.
mandatory: no
default: \(0\)
values: numeric
unit: monetary per start
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
default: \(0\)
values: numeric
unit: -
Detailed reference
Expressions
Variables
conversion
How to access this variable?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").var.conversion
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").var.conversion
Full implementation and all details: unit/var_conversion @ IESopt.jl
Add the variable describing the
unit
’s conversion to themodel
.
This can be accessed via
unit.var.conversion[t]
; this does not describe the full output of theUnit
since that maybe also include fixed generation based on theison
variable.
ison
How to access this variable?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").var.ison
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").var.ison
Full implementation and all details: unit/var_ison @ IESopt.jl
Add the variable describing the current “online” state of the
unit
to themodel
.
The variable can be further parameterized using the
unit.unit_commitment
setting (“linear”, “binary”, “integer”). It will automatically enforce the constraints \(0 \leq \text{ison} \leq \text{unitcount}\), with \(\text{unitcount}\) describing the number of units that are aggregated in thisunit
(set byunit.unit_count
). This can be accessed viaunit.var.ison[t]
.
ramp
How to access this variable?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").var.ramp
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").var.ramp
Full implementation and all details: unit/var_ramp @ IESopt.jl
Add the variable describing the per-snapshot ramping to the
model
.
This adds two variables per snapshot to the model (if the respective setting
unit.enable_ramp_up
orunit.enable_ramp_down
is activated). Both are preconstructed with a fixed lower bound of0
. This describes the amount of change in conversion that occurs during the current snapshot. These can be accessed viaunit.var.ramp_up[t]
andunit.var.ramp_down[t]
.
These variables are only used for ramping costs. The limits are enforced directly on the conversion, which means this variable only exists if costs are specified!
startup
How to access this variable?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").var.startup
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").var.startup
Full implementation and all details: unit/var_startup @ IESopt.jl
Add the variable describing the per-snapshot startup to the
model
.
This adds a variable per snapshot to the model (if the respective setting
unit.unit_commitment
is activated). The variable can be further parameterized using theunit.unit_commitment
setting (“linear”, “binary”, “integer”). It will automatically enforce the constraints \(0 \leq \text{startup} \leq \text{unitcount}\), with \(\text{unitcount}\) describing the number of units that are aggregated in thisunit
(set byunit.unit_count
). This describes the startup that happens during the current snapshot and can be accessed viaunit.var.startup
.
Constraints
conversion_bounds
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.conversion_bounds
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.conversion_bounds
Full implementation and all details: unit/con_conversion_bounds @ IESopt.jl
Add the constraint defining the
unit
’s conversion bounds to themodel
.
This makes use of the current
min_capacity
(describing the lower limit of conversion; either 0 if no minimum load applies or the respective value of the minimum load) as well as theonline_capacity
(that can either be the full capacity if unit commitment is disabled, or the amount that is currently active).
Depending on how the “availability” of this
unit
is handled it constructs the following constraints:
if !isnothing(unit.availability)
This effectively results in \(\text{conversion}_t \leq \min(\text{capacity}_{\text{online}, t}, \text{availability}_t)\).
if !isnothing(unit.availability_factor)
If no kind of availability limiting takes place, the following bounds are enforced:
ison
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.ison
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.ison
Full implementation and all details: unit/con_ison @ IESopt.jl
Construct the upper bound for
var_ison
, based onunit.unit_count
, if it is handled by an externalDecision
.
min_onoff_time
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.min_onoff_time
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.min_onoff_time
Full implementation and all details: unit/con_min_onoff_time @ IESopt.jl
Add the constraints modeling min on- or off-time of a
Unit
to themodel
.
This constructs the constraints
respecting
on_time_before
andoff_time_before
, andis_on_before
. See the code for more details.
Aggregated units
This is currently not fully adapted to account for Unit
s with unit_count > 1
.
ramp
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.ramp
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.ramp
Full implementation and all details: unit/con_ramp @ IESopt.jl
Add the auxiliary constraint that enables calculation of per snapshot ramping to the
model
.
Depending on whether ramps are enabled, none, one, or both of the following constraints are constructed:
This calculates the ramping that happens from the PREVIOUS snapshot to this one. That means that if:
out[5] = 100
andout[4] = 50
, thenramp_up[5] = 50
andramp_down[5] = 0
ramp_up[1] = ramp_down[1] = 0
ramp_limit
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.ramp_limit
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.ramp_limit
Full implementation and all details: unit/con_ramp_limit @ IESopt.jl
Add the constraint describing the ramping limits of this
unit
to themodel
.
This makes use of the maximum capacity of the
unit
, which is just the total installed capacity. Both, up- and downwards ramps can be enabled separately (viaunit.ramp_up_limit
andunit.ramp_down_limit
), resulting in either or both of:
This does not make use of the ramping variable (that is only used for costs - if there are costs).
This calculates the ramping that happens from the PREVIOUS snapshot to this one. That means that if:
out[5] = 100
andout[4] = 50
, thenramp_up[5] = 50
andramp_down[5] = 0
ramp_up[1] = ramp_down[1] = 0
startup
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.startup
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.startup
Full implementation and all details: unit/con_startup @ IESopt.jl
Add the auxiliary constraint that enables calculation of per snapshot startup to the
model
.
Depending on whether startup handling is enabled, the following constraint is constructed:
This calculates the startup that happens from the PREVIOUS snapshot to this one. That means that if:
ison[5] = 1
andison[4] = 0
, thenstartup[5] = 1
Objectives
marginal_cost
How to access this objective?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").obj.marginal_cost
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").obj.marginal_cost
Full implementation and all details: unit/obj_marginal_cost @ IESopt.jl
Add the (potential) cost of this
unit
’s conversion (unit.marginal_cost
) to the global objective function.
ramp_cost
How to access this objective?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").obj.ramp_cost
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").obj.ramp_cost
Full implementation and all details: unit/obj_ramp_cost @ IESopt.jl
Add the (potential) cost of this
unit
’s ramping to the global objective function.
To allow for finer control, costs of up- and downwards ramping can be specified separately (using
unit.ramp_up_cost
andunit.ramp_down_cost
):
startup_cost
How to access this objective?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").obj.startup_cost
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").obj.startup_cost
Full implementation and all details: unit/obj_startup_cost @ IESopt.jl
Add the (potential) cost of this
unit
’s startup behaviour (configured byunit.startup_cost
ifunit.unit_commitment != :off
).